Skip to content

Commit

Permalink
Don't retypecheck erroneous arguments when fixing function
Browse files Browse the repository at this point in the history
Don't retypecheck erroneous arguments when trying conversions or extensions
on the function part.

Generally, we should avoid typechecking untyped trees several times, this can quickly
explode exponentially.

Fixes scala#12941
  • Loading branch information
odersky committed Dec 19, 2021
1 parent c3f614b commit c1d3abd
Show file tree
Hide file tree
Showing 15 changed files with 101 additions and 19 deletions.
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -908,8 +908,9 @@ trait Applications extends Compatibility {
* part. Return an optional value to indicate success.
*/
def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(using Context): Option[Tree] =
if (ctx.mode.is(Mode.SynthesizeExtMethodReceiver))
if ctx.mode.is(Mode.SynthesizeExtMethodReceiver) || proto.hasErrorArg then
// Suppress insertion of apply or implicit conversion on extension method receiver
// or if argument is erroneous by itself.
None
else
tryInsertImplicitOnQualifier(fun1, proto, ctx.typerState.ownedVars) flatMap { fun2 =>
Expand Down
13 changes: 11 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Contexts._, Types._, Denotations._, Names._, StdNames._, NameOps._, Symbo
import NameKinds.DepParamName
import Trees._
import Constants._
import util.{Stats, SimpleIdentityMap}
import util.{Stats, SimpleIdentityMap, SimpleIdentitySet}
import Decorators._
import Uniques._
import config.Printers.typr
Expand Down Expand Up @@ -282,6 +282,9 @@ object ProtoTypes {
/** A map in which typed arguments can be stored to be later integrated in `typedArgs`. */
var typedArg: SimpleIdentityMap[untpd.Tree, Tree] = SimpleIdentityMap.empty

/** The argument that produced errors during typing */
var errorArgs: SimpleIdentitySet[untpd.Tree] = SimpleIdentitySet.empty

/** The tupled or untupled version of this prototype, if it has been computed */
var tupledDual: Type = NoType

Expand Down Expand Up @@ -341,6 +344,9 @@ object ProtoTypes {
case _ => false
}

/** Did an argument produce an error when typing? */
def hasErrorArg = !state.errorArgs.isEmpty

private def cacheTypedArg(arg: untpd.Tree, typerFn: untpd.Tree => Tree, force: Boolean)(using Context): Tree = {
var targ = state.typedArg(arg)
if (targ == null)
Expand All @@ -357,8 +363,11 @@ object ProtoTypes {
targ = arg.withType(WildcardType)
case _ =>
targ = typerFn(arg)
if (!ctx.reporter.hasUnreportedErrors)
if ctx.reporter.hasUnreportedErrors then
state.errorArgs += arg
else
state.typedArg = state.typedArg.updated(arg, targ)
state.errorArgs -= arg
}
targ
}
Expand Down
8 changes: 8 additions & 0 deletions tests/neg-custom-args/deprecation/t3235-minimal-v2.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Error: tests/neg-custom-args/deprecation/t3235-minimal-v2.scala:3:16 ------------------------------------------------
3 | assert(math.round(123456789) == 123456789) // error
| ^^^^^^^^^^
|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?
-- Error: tests/neg-custom-args/deprecation/t3235-minimal-v2.scala:4:16 ------------------------------------------------
4 | assert(math.round(1234567890123456789L) == 1234567890123456789L) // error
| ^^^^^^^^^^
|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?
6 changes: 6 additions & 0 deletions tests/neg-custom-args/deprecation/t3235-minimal-v2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
object Test {
def main(args: Array[String]): Unit = {
assert(math.round(123456789) == 123456789) // error
assert(math.round(1234567890123456789L) == 1234567890123456789L) // error
}
}
5 changes: 5 additions & 0 deletions tests/neg-custom-args/deprecation/t3235-minimal-v3.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object Test {
def main(args: Array[String]): Unit = {
assert(1234567890123456789L.round == 1234567890123456789L) // error
}
}
4 changes: 4 additions & 0 deletions tests/neg-custom-args/deprecation/t3235-minimal.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Error: tests/neg-custom-args/deprecation/t3235-minimal.scala:3:21 ---------------------------------------------------
3 | assert(123456789.round == 123456789) // error
| ^^^^^^^^^^^^^^^
|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?
3 changes: 0 additions & 3 deletions tests/neg-custom-args/deprecation/t3235-minimal.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
object Test {
def main(args: Array[String]): Unit = {
assert(123456789.round == 123456789) // error
assert(math.round(123456789) == 123456789) // error
assert(1234567890123456789L.round == 1234567890123456789L) // error
assert(math.round(1234567890123456789L) == 1234567890123456789L) // error
}
}
28 changes: 28 additions & 0 deletions tests/neg/i12941.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
object A:
def myFun(op: String ?=> Unit) = ()

@main def func: Unit =
A.myFun {
val res: String = summon[String]
println(ress) // error
}

class I:
def runSth: Int = 1

abstract class A:
def myFun(op: I ?=> Unit) =
op(using I())
1

class B extends A

def assertEquals(x: String, y: Int, z: Int): Unit = ()

@main def hello: Unit =

B().myFun {
val res = summon[I].runSth
assertEquals("", 1, res, "asd") // error
println("Hello!")
}
2 changes: 1 addition & 1 deletion tests/neg/i8861.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ object Test {
)
def minimalFail[M](c: Container { type A = M }): M = c.visit(
int = vi => vi.i : vi.A,
str = vs => vs.t : vs.A // error // error
str = vs => vs.t : vs.A // error
)

def main(args: Array[String]): Unit = {
Expand Down
7 changes: 7 additions & 0 deletions tests/neg/i903.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- [E007] Type Mismatch Error: tests/neg/i903.scala:3:16 ---------------------------------------------------------------
3 | "".contains("", (_: Int)) // error
| ^^^^^^^^^^^^
| Found: (String, Int)
| Required: CharSequence

longer explanation available when compiling with `-explain`
4 changes: 4 additions & 0 deletions tests/neg/i903.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
object Test {
def test2 =
"".contains("", (_: Int)) // error
}
11 changes: 11 additions & 0 deletions tests/neg/zipped.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- [E007] Type Mismatch Error: tests/neg/zipped.scala:6:10 -------------------------------------------------------------
6 | .map( (x: (Int, Int, Int)) => x match { case (x, y, z) => x + y + z }) // error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| Found: ((Int, Int, Int)) => Int
| Required: (Int, Int, Int) => Int

longer explanation available when compiling with `-explain`
-- [E086] Syntax Error: tests/neg/zipped.scala:9:12 --------------------------------------------------------------------
9 | .map( x => x match { case (x, y, z) => x + y + z }) // error
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| Wrong number of parameters, expected: 3
10 changes: 10 additions & 0 deletions tests/neg/zipped.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
object Test {
val xs: List[Int] = ???

// Does not work anynore since auto(un)tupling does not work after conversions
xs.lazyZip(xs).lazyZip(xs)
.map( (x: (Int, Int, Int)) => x match { case (x, y, z) => x + y + z }) // error

xs.lazyZip(xs).lazyZip(xs)
.map( x => x match { case (x, y, z) => x + y + z }) // error
}
8 changes: 0 additions & 8 deletions tests/pos/i903.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,4 @@ object Test {
// f.apply(0)
// ^
}

def test2 = {
val f = "".contains("", (_: Int)) // dotc:
f.apply(0)
// sandbox/eta.scala:18: error: apply is not a member of Boolean(f)
// f.apply(0)
// ^
}
}
8 changes: 4 additions & 4 deletions tests/pos/zipped.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ object Test {
.map( (x: Int, y: Int, z: Int) => x + y + z ) // OK

// 4. The single parameter map works through an inserted conversion
xs.lazyZip(xs).lazyZip(xs)
.map( (x: (Int, Int, Int)) => x match { case (x, y, z) => x + y + z }) // now also OK
//xs.lazyZip(xs).lazyZip(xs)
// .map( (x: (Int, Int, Int)) => x match { case (x, y, z) => x + y + z }) // now also OK

// 5. If we leave out the parameter type, it now works as well.
xs.lazyZip(xs).lazyZip(xs)
.map( x => x match { case (x, y, z) => x + y + z }) // now also OK
//xs.lazyZip(xs).lazyZip(xs)
// .map( x => x match { case (x, y, z) => x + y + z }) // now also OK

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

0 comments on commit c1d3abd

Please sign in to comment.