From 14517886c7acbad688bb14b05fe98078d8c80edf Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 12 Feb 2023 18:51:46 +0100 Subject: [PATCH 1/2] Try to be more subtle when inferring type parameters of class parents The previous scheme was basically like this: 1. If the parent type is an application, infer type parameters from the term parameters during completion of the class. 2. If the parent type is an Ident or Select and it appears in an anonymous class, infer it from the expected type. It could happen in the second case that we infer Nothing since an expected type was missing, but if the parent type had been an application, we would have inferred something else from implicit arguments to the parent constructor. This is a case handled by Scala 2, but not yet by Dotty. To deal with this we have to perform a complicated dance: - Try step (2) above, but back out if the inferred parent type has Nothing as a type argument. - During completion, if the inferred parent has missing type arguments, convert the parent type to an application with () arguments and try that instead. I normally would have thought this is too much sophistry but there are valid use cases that Scala 2 supports and it would be good if we get to parity for these. --- .../dotty/tools/dotc/typer/Inferencing.scala | 9 ++--- .../src/dotty/tools/dotc/typer/Namer.scala | 36 +++++++++++++------ .../src/dotty/tools/dotc/typer/Typer.scala | 13 +++---- tests/neg/i1643.scala | 4 +-- tests/neg/i4820.scala | 2 -- tests/neg/i4820b.scala | 5 --- tests/neg/i4820c.scala | 2 -- tests/pos/i16778.scala | 22 ++++++++++++ tests/pos/i4820.scala | 2 ++ tests/pos/i4820b.scala | 5 +++ 10 files changed, 66 insertions(+), 34 deletions(-) delete mode 100644 tests/neg/i4820.scala delete mode 100644 tests/neg/i4820b.scala delete mode 100644 tests/neg/i4820c.scala create mode 100644 tests/pos/i16778.scala create mode 100644 tests/pos/i4820.scala create mode 100644 tests/pos/i4820b.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index af4174423cbc..c31477d0270e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -307,16 +307,17 @@ object Inferencing { } /** If `tree` has a type lambda type, infer its type parameters by comparing with expected type `pt` */ - def inferTypeParams(tree: Tree, pt: Type)(using Context): Tree = tree.tpe match { + def inferTypeParams(tree: Tree, pt: Type)(using Context): Tree = tree.tpe match case tl: TypeLambda => val (tl1, tvars) = constrained(tl, tree) var tree1 = AppliedTypeTree(tree.withType(tl1), tvars) tree1.tpe <:< pt - fullyDefinedType(tree1.tpe, "template parent", tree.srcPos) - tree1 + if isFullyDefined(tree1.tpe, force = ForceDegree.failBottom) then + tree1 + else + EmptyTree case _ => tree - } def isSkolemFree(tp: Type)(using Context): Boolean = !tp.existsPart(_.isInstanceOf[SkolemType]) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 6f85efb0fc8a..af6673086339 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1453,27 +1453,41 @@ class Namer { typer: Typer => * only if parent type contains uninstantiated type parameters. */ def parentType(parent: untpd.Tree)(using Context): Type = - if (parent.isType) - typedAheadType(parent, AnyTypeConstructorProto).tpe - else { - val (core, targs) = stripApply(parent) match { + + def typedParentApplication(parent: untpd.Tree): Type = + val (core, targs) = stripApply(parent) match case TypeApply(core, targs) => (core, targs) case core => (core, Nil) - } - core match { + core match case Select(New(tpt), nme.CONSTRUCTOR) => val targs1 = targs map (typedAheadType(_)) val ptype = typedAheadType(tpt).tpe appliedTo targs1.tpes if (ptype.typeParams.isEmpty) ptype - else { + else if (denot.is(ModuleClass) && denot.sourceModule.isOneOf(GivenOrImplicit)) missingType(denot.symbol, "parent ")(using creationContext) fullyDefinedType(typedAheadExpr(parent).tpe, "class parent", parent.srcPos) - } case _ => UnspecifiedErrorType.assertingErrorsReported - } - } + + def typedParentType(tree: untpd.Tree): tpd.Tree = + val parentTpt = typer.typedType(parent, AnyTypeConstructorProto) + val ptpe = parentTpt.tpe + if ptpe.typeParams.nonEmpty + && ptpe.underlyingClassRef(refinementOK = false).exists + then + // Try to infer type parameters from a synthetic application. + // This might yield new info if implicit parameters are resolved. + // A test case is i16778.scala. + val app = untpd.Apply(untpd.Select(untpd.New(parentTpt), nme.CONSTRUCTOR), Nil) + typedParentApplication(app) + app.getAttachment(TypedAhead).getOrElse(parentTpt) + else + parentTpt + + if parent.isType then typedAhead(parent, typedParentType).tpe + else typedParentApplication(parent) + end parentType /** Check parent type tree `parent` for the following well-formedness conditions: * (1) It must be a class type with a stable prefix (@see checkClassTypeWithStablePrefix) @@ -1607,7 +1621,7 @@ class Namer { typer: Typer => case Some(ttree) => ttree case none => val ttree = typed(tree) - xtree.putAttachment(TypedAhead, ttree) + if !ttree.isEmpty then xtree.putAttachment(TypedAhead, ttree) ttree } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a87d6dd7e703..5752409c51c1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -843,14 +843,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer isSkolemFree(pt) && isEligible(pt.underlyingClassRef(refinementOK = false))) templ1 = cpy.Template(templ)(parents = untpd.TypeTree(pt) :: Nil) - templ1.parents foreach { - case parent: RefTree => - typedAhead(parent, tree => inferTypeParams(typedType(tree), pt)) - case _ => - } - val x = tpnme.ANON_CLASS - val clsDef = TypeDef(x, templ1).withFlags(Final | Synthetic) - typed(cpy.Block(tree)(clsDef :: Nil, New(Ident(x), Nil)), pt) + for case parent: RefTree <- templ1.parents do + typedAhead(parent, tree => inferTypeParams(typedType(tree), pt)) + val anon = tpnme.ANON_CLASS + val clsDef = TypeDef(anon, templ1).withFlags(Final | Synthetic) + typed(cpy.Block(tree)(clsDef :: Nil, New(Ident(anon), Nil)), pt) case _ => var tpt1 = typedType(tree.tpt) val tsym = tpt1.tpe.underlyingClassRef(refinementOK = false).typeSymbol diff --git a/tests/neg/i1643.scala b/tests/neg/i1643.scala index a10422de6eab..1745539d73f5 100644 --- a/tests/neg/i1643.scala +++ b/tests/neg/i1643.scala @@ -1,4 +1,4 @@ -trait T extends Array { // error // error +trait T extends Array { // error def t1(as: String*): Array[String] = { varargs1(as*) } // error def t2(as: String*): Array[String] = { super.varargs1(as*) } // error } @@ -7,7 +7,7 @@ class C extends Base_1 { // error def c2(as: String*): Array[String] = { super.varargs1(as*) } // error } object Test extends App { - val t = new T {} // error + val t = new T {} println(t.t1("a", "b").mkString(",")) println(t.t2("a", "b").mkString(",")) val c = new C {} diff --git a/tests/neg/i4820.scala b/tests/neg/i4820.scala deleted file mode 100644 index e19183b17b14..000000000000 --- a/tests/neg/i4820.scala +++ /dev/null @@ -1,2 +0,0 @@ -class Foo[A] -class Bar[A] extends Foo // error diff --git a/tests/neg/i4820b.scala b/tests/neg/i4820b.scala deleted file mode 100644 index 4a7b3da3fb1b..000000000000 --- a/tests/neg/i4820b.scala +++ /dev/null @@ -1,5 +0,0 @@ -trait SetOps[A, +C <: SetOps[A, C]] { - def concat(that: Iterable[A]): C = ??? -} - -class Set1[A] extends SetOps // error: should be SetOps[A, Set1[A]] diff --git a/tests/neg/i4820c.scala b/tests/neg/i4820c.scala deleted file mode 100644 index 6956b23363b5..000000000000 --- a/tests/neg/i4820c.scala +++ /dev/null @@ -1,2 +0,0 @@ -trait Foo[A] -class Bar[A] extends Foo // error \ No newline at end of file diff --git a/tests/pos/i16778.scala b/tests/pos/i16778.scala new file mode 100644 index 000000000000..426f3c86c0bd --- /dev/null +++ b/tests/pos/i16778.scala @@ -0,0 +1,22 @@ +final abstract class ForcedRecompilationToken[T] + +object ForcedRecompilationToken { + implicit def materialize: ForcedRecompilationToken["x"] = null.asInstanceOf[ForcedRecompilationToken["x"]] +} + +class PluginDef[T](implicit val recompilationToken: ForcedRecompilationToken[T]) + +object X { + val no = { + final class anon extends PluginDef {} // was: missing type parameters + new anon + } + + val bad = new PluginDef {} // was: No given instance + val good = new PluginDef() {} // ok +} + +object DependingPlugin { + class NestedDoublePlugin extends PluginDef + object NestedDoublePlugin extends PluginDef +} diff --git a/tests/pos/i4820.scala b/tests/pos/i4820.scala new file mode 100644 index 000000000000..8d368d150a00 --- /dev/null +++ b/tests/pos/i4820.scala @@ -0,0 +1,2 @@ +class Foo[A] +class Bar[A] extends Foo // was error, now expanded to Foo[Nothing] diff --git a/tests/pos/i4820b.scala b/tests/pos/i4820b.scala new file mode 100644 index 000000000000..a1c7d54f0c76 --- /dev/null +++ b/tests/pos/i4820b.scala @@ -0,0 +1,5 @@ +trait SetOps[A, +C <: SetOps[A, C]] { + def concat(that: Iterable[A]): C = ??? +} + +class Set1[A] extends SetOps // ideally should be SetOps[A, Set1[A]], but SetOps[Nothing, Nothin] is inferred From 79bad18adb6aad1c4cff1f105c63d24ade73fa4f Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 12 Feb 2023 19:37:51 +0100 Subject: [PATCH 2/2] Fix repl test --- compiler/test-resources/repl/i7644 | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/compiler/test-resources/repl/i7644 b/compiler/test-resources/repl/i7644 index 8ceaf8b00804..786823073470 100644 --- a/compiler/test-resources/repl/i7644 +++ b/compiler/test-resources/repl/i7644 @@ -5,11 +5,7 @@ scala> class T extends CanEqual | Cannot extend sealed trait CanEqual in a different source file | | longer explanation available when compiling with `-explain` --- [E056] Syntax Error: -------------------------------------------------------- -1 | class T extends CanEqual - | ^^^^^^^^ - | Missing type parameter for CanEqual -2 errors found +1 error found scala> class T extends CanEqual -- [E112] Syntax Error: -------------------------------------------------------- 1 | class T extends CanEqual @@ -17,8 +13,5 @@ scala> class T extends CanEqual | Cannot extend sealed trait CanEqual in a different source file | | longer explanation available when compiling with `-explain` --- [E056] Syntax Error: -------------------------------------------------------- -1 | class T extends CanEqual - | ^^^^^^^^ - | Missing type parameter for CanEqual -2 errors found +1 error found +