Skip to content

Commit 0e4e5d5

Browse files
Merge pull request #14043 from dotty-staging/fix-12941
Don't retypecheck erroneous arguments when fixing function
2 parents 22b9b01 + 89ec460 commit 0e4e5d5

File tree

8 files changed

+113
-7
lines changed

8 files changed

+113
-7
lines changed

compiler/src/dotty/tools/dotc/typer/Applications.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -908,8 +908,9 @@ trait Applications extends Compatibility {
908908
* part. Return an optional value to indicate success.
909909
*/
910910
def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(using Context): Option[Tree] =
911-
if (ctx.mode.is(Mode.SynthesizeExtMethodReceiver))
911+
if ctx.mode.is(Mode.SynthesizeExtMethodReceiver) || proto.hasErrorArg then
912912
// Suppress insertion of apply or implicit conversion on extension method receiver
913+
// or if argument is erroneous by itself.
913914
None
914915
else
915916
tryInsertImplicitOnQualifier(fun1, proto, ctx.typerState.ownedVars) flatMap { fun2 =>

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

+33-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Contexts._, Types._, Denotations._, Names._, StdNames._, NameOps._, Symbo
88
import NameKinds.DepParamName
99
import Trees._
1010
import Constants._
11-
import util.{Stats, SimpleIdentityMap}
11+
import util.{Stats, SimpleIdentityMap, SimpleIdentitySet}
1212
import Decorators._
1313
import Uniques._
1414
import config.Printers.typr
@@ -282,6 +282,9 @@ object ProtoTypes {
282282
/** A map in which typed arguments can be stored to be later integrated in `typedArgs`. */
283283
var typedArg: SimpleIdentityMap[untpd.Tree, Tree] = SimpleIdentityMap.empty
284284

285+
/** The argument that produced errors during typing */
286+
var errorArgs: SimpleIdentitySet[untpd.Tree] = SimpleIdentitySet.empty
287+
285288
/** The tupled or untupled version of this prototype, if it has been computed */
286289
var tupledDual: Type = NoType
287290

@@ -341,6 +344,30 @@ object ProtoTypes {
341344
case _ => false
342345
}
343346

347+
/** Did an argument produce an error when typing? This means: an error was reported
348+
* and a tree got an error type. Errors of adaptation whree a tree has a good type
349+
* but that type does not conform to the expected type are not counted.
350+
*/
351+
def hasErrorArg = !state.errorArgs.isEmpty
352+
353+
/** Does tree have embedded error trees that are not at the outside.
354+
* A nested tree t1 is "at the outside" relative to a tree t2 if
355+
* - t1 and t2 have the same span, or
356+
* - t2 is a ascription (t22: T) and t1 is at the outside of t22
357+
* - t2 is a closure (...) => t22 and t1 is at the outside of t22
358+
*/
359+
def hasInnerErrors(t: Tree): Boolean = t match
360+
case Typed(expr, tpe) => hasInnerErrors(expr)
361+
case closureDef(mdef) => hasInnerErrors(mdef.rhs)
362+
case _ =>
363+
t.existsSubTree { t1 =>
364+
if t1.typeOpt.isError && t1.span.toSynthetic != t.span.toSynthetic then
365+
typr.println(i"error subtree $t1 of $t with ${t1.typeOpt}, spans = ${t1.span}, ${t.span}")
366+
true
367+
else
368+
false
369+
}
370+
344371
private def cacheTypedArg(arg: untpd.Tree, typerFn: untpd.Tree => Tree, force: Boolean)(using Context): Tree = {
345372
var targ = state.typedArg(arg)
346373
if (targ == null)
@@ -357,8 +384,12 @@ object ProtoTypes {
357384
targ = arg.withType(WildcardType)
358385
case _ =>
359386
targ = typerFn(arg)
360-
if (!ctx.reporter.hasUnreportedErrors)
387+
if ctx.reporter.hasUnreportedErrors then
388+
if hasInnerErrors(targ) then
389+
state.errorArgs += arg
390+
else
361391
state.typedArg = state.typedArg.updated(arg, targ)
392+
state.errorArgs -= arg
362393
}
363394
targ
364395
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-- Error: tests/neg-custom-args/deprecation/t3235-minimal.scala:3:21 ---------------------------------------------------
2+
3 | assert(123456789.round == 123456789) // error
3+
| ^^^^^^^^^^^^^^^
4+
|method round in class RichInt is deprecated since 2.11.0: this is an integer type; there is no reason to round it. Perhaps you meant to call this on a floating-point value?
5+
-- Error: tests/neg-custom-args/deprecation/t3235-minimal.scala:4:16 ---------------------------------------------------
6+
4 | assert(math.round(123456789) == 123456789) // error
7+
| ^^^^^^^^^^
8+
|method round in package scala.math is deprecated since 2.11.0: This is an integer type; there is no reason to round it. Perhaps you meant to call this with a floating-point value?
9+
-- Error: tests/neg-custom-args/deprecation/t3235-minimal.scala:5:32 ---------------------------------------------------
10+
5 | assert(1234567890123456789L.round == 1234567890123456789L) // error
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
|method round in class RichLong is deprecated since 2.11.0: this is an integer type; there is no reason to round it. Perhaps you meant to call this on a floating-point value?
13+
-- Error: tests/neg-custom-args/deprecation/t3235-minimal.scala:6:16 ---------------------------------------------------
14+
6 | assert(math.round(1234567890123456789L) == 1234567890123456789L) // error
15+
| ^^^^^^^^^^
16+
|method round in package scala.math is deprecated since 2.11.0: This is an integer type; there is no reason to round it. Perhaps you meant to call this with a floating-point value?

tests/neg/i12941.scala

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
object A:
2+
def myFun(op: String ?=> Unit) = ()
3+
4+
@main def func: Unit =
5+
A.myFun {
6+
val res: String = summon[String]
7+
println(ress) // error
8+
}
9+
10+
class I:
11+
def runSth: Int = 1
12+
13+
abstract class A:
14+
def myFun(op: I ?=> Unit) =
15+
op(using I())
16+
1
17+
18+
class B extends A
19+
20+
def assertEquals(x: String, y: Int, z: Int): Unit = ()
21+
22+
@main def hello: Unit =
23+
24+
B().myFun {
25+
val res = summon[I].runSth
26+
assertEquals("", 1, res, "asd") // error
27+
println("Hello!")
28+
}

tests/neg/zipped.scala

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
object Test {
2+
val xs: List[Int] = ???
3+
4+
xs.lazyZip(xs).lazyZip(xs)
5+
.map( (x: (Int, Int, Int)) => x match { case (x, y, z) => x + y + z }) // ok
6+
7+
xs.lazyZip(xs).lazyZip(xs)
8+
.map( x => x match { case (x, y, z) => x + y + z }) // error
9+
}

tests/pos/specs2-failure.scala

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import util.matching.Regex
2+
import util.matching.Regex.Match
3+
4+
// Demonstrate what used to be a failure in specs2, before we refined
5+
// the scheme when not to typecheck a function argument again.
6+
object Test:
7+
8+
extension (s: String)
9+
10+
def replaceAll(pairs: (String, String)*): String =
11+
pairs.foldLeft(s) { (res, cur) =>
12+
res.replaceAll(cur._1, cur._2)
13+
}
14+
15+
def replaceAll(exp: String, f: String => String): String =
16+
new Regex(exp).replaceAllIn(s, (m: Match) => f(m.group(0).replace("\\", "\\\\")))
17+
18+
def replaceInsideTag(tag: String, p: (String, String)*): String =
19+
s.replaceAll(tag, (s: String) => java.util.regex.Matcher.quoteReplacement(s.replaceAll(p*)))
20+

tests/pos/zipped.scala

+4-3
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ object Test {
2626
xs.lazyZip(xs).lazyZip(xs)
2727
.map( (x: (Int, Int, Int)) => x match { case (x, y, z) => x + y + z }) // now also OK
2828

29-
// 5. If we leave out the parameter type, it now works as well.
30-
xs.lazyZip(xs).lazyZip(xs)
31-
.map( x => x match { case (x, y, z) => x + y + z }) // now also OK
29+
// 5. But if that pone is deeper nested, it does not work since we don't retypecheck
30+
// arguments deeply.
31+
//xs.lazyZip(xs).lazyZip(xs)
32+
// .map( x => x match { case (x, y, z) => x + y + z }) // now also OK
3233

3334
// This means that the following works in Dotty 3.0 as well as 3.x
3435
for ((x, y, z) <- xs.lazyZip(xs).lazyZip(xs)) yield x + y + z

0 commit comments

Comments
 (0)