Skip to content

Commit 1451788

Browse files
committed
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.
1 parent 3d251d6 commit 1451788

File tree

10 files changed

+66
-34
lines changed

10 files changed

+66
-34
lines changed

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,16 +307,17 @@ object Inferencing {
307307
}
308308

309309
/** If `tree` has a type lambda type, infer its type parameters by comparing with expected type `pt` */
310-
def inferTypeParams(tree: Tree, pt: Type)(using Context): Tree = tree.tpe match {
310+
def inferTypeParams(tree: Tree, pt: Type)(using Context): Tree = tree.tpe match
311311
case tl: TypeLambda =>
312312
val (tl1, tvars) = constrained(tl, tree)
313313
var tree1 = AppliedTypeTree(tree.withType(tl1), tvars)
314314
tree1.tpe <:< pt
315-
fullyDefinedType(tree1.tpe, "template parent", tree.srcPos)
316-
tree1
315+
if isFullyDefined(tree1.tpe, force = ForceDegree.failBottom) then
316+
tree1
317+
else
318+
EmptyTree
317319
case _ =>
318320
tree
319-
}
320321

321322
def isSkolemFree(tp: Type)(using Context): Boolean =
322323
!tp.existsPart(_.isInstanceOf[SkolemType])

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

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,27 +1453,41 @@ class Namer { typer: Typer =>
14531453
* only if parent type contains uninstantiated type parameters.
14541454
*/
14551455
def parentType(parent: untpd.Tree)(using Context): Type =
1456-
if (parent.isType)
1457-
typedAheadType(parent, AnyTypeConstructorProto).tpe
1458-
else {
1459-
val (core, targs) = stripApply(parent) match {
1456+
1457+
def typedParentApplication(parent: untpd.Tree): Type =
1458+
val (core, targs) = stripApply(parent) match
14601459
case TypeApply(core, targs) => (core, targs)
14611460
case core => (core, Nil)
1462-
}
1463-
core match {
1461+
core match
14641462
case Select(New(tpt), nme.CONSTRUCTOR) =>
14651463
val targs1 = targs map (typedAheadType(_))
14661464
val ptype = typedAheadType(tpt).tpe appliedTo targs1.tpes
14671465
if (ptype.typeParams.isEmpty) ptype
1468-
else {
1466+
else
14691467
if (denot.is(ModuleClass) && denot.sourceModule.isOneOf(GivenOrImplicit))
14701468
missingType(denot.symbol, "parent ")(using creationContext)
14711469
fullyDefinedType(typedAheadExpr(parent).tpe, "class parent", parent.srcPos)
1472-
}
14731470
case _ =>
14741471
UnspecifiedErrorType.assertingErrorsReported
1475-
}
1476-
}
1472+
1473+
def typedParentType(tree: untpd.Tree): tpd.Tree =
1474+
val parentTpt = typer.typedType(parent, AnyTypeConstructorProto)
1475+
val ptpe = parentTpt.tpe
1476+
if ptpe.typeParams.nonEmpty
1477+
&& ptpe.underlyingClassRef(refinementOK = false).exists
1478+
then
1479+
// Try to infer type parameters from a synthetic application.
1480+
// This might yield new info if implicit parameters are resolved.
1481+
// A test case is i16778.scala.
1482+
val app = untpd.Apply(untpd.Select(untpd.New(parentTpt), nme.CONSTRUCTOR), Nil)
1483+
typedParentApplication(app)
1484+
app.getAttachment(TypedAhead).getOrElse(parentTpt)
1485+
else
1486+
parentTpt
1487+
1488+
if parent.isType then typedAhead(parent, typedParentType).tpe
1489+
else typedParentApplication(parent)
1490+
end parentType
14771491

14781492
/** Check parent type tree `parent` for the following well-formedness conditions:
14791493
* (1) It must be a class type with a stable prefix (@see checkClassTypeWithStablePrefix)
@@ -1607,7 +1621,7 @@ class Namer { typer: Typer =>
16071621
case Some(ttree) => ttree
16081622
case none =>
16091623
val ttree = typed(tree)
1610-
xtree.putAttachment(TypedAhead, ttree)
1624+
if !ttree.isEmpty then xtree.putAttachment(TypedAhead, ttree)
16111625
ttree
16121626
}
16131627
}

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -843,14 +843,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
843843
isSkolemFree(pt) &&
844844
isEligible(pt.underlyingClassRef(refinementOK = false)))
845845
templ1 = cpy.Template(templ)(parents = untpd.TypeTree(pt) :: Nil)
846-
templ1.parents foreach {
847-
case parent: RefTree =>
848-
typedAhead(parent, tree => inferTypeParams(typedType(tree), pt))
849-
case _ =>
850-
}
851-
val x = tpnme.ANON_CLASS
852-
val clsDef = TypeDef(x, templ1).withFlags(Final | Synthetic)
853-
typed(cpy.Block(tree)(clsDef :: Nil, New(Ident(x), Nil)), pt)
846+
for case parent: RefTree <- templ1.parents do
847+
typedAhead(parent, tree => inferTypeParams(typedType(tree), pt))
848+
val anon = tpnme.ANON_CLASS
849+
val clsDef = TypeDef(anon, templ1).withFlags(Final | Synthetic)
850+
typed(cpy.Block(tree)(clsDef :: Nil, New(Ident(anon), Nil)), pt)
854851
case _ =>
855852
var tpt1 = typedType(tree.tpt)
856853
val tsym = tpt1.tpe.underlyingClassRef(refinementOK = false).typeSymbol

tests/neg/i1643.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
trait T extends Array { // error // error
1+
trait T extends Array { // error
22
def t1(as: String*): Array[String] = { varargs1(as*) } // error
33
def t2(as: String*): Array[String] = { super.varargs1(as*) } // error
44
}
@@ -7,7 +7,7 @@ class C extends Base_1 { // error
77
def c2(as: String*): Array[String] = { super.varargs1(as*) } // error
88
}
99
object Test extends App {
10-
val t = new T {} // error
10+
val t = new T {}
1111
println(t.t1("a", "b").mkString(","))
1212
println(t.t2("a", "b").mkString(","))
1313
val c = new C {}

tests/neg/i4820.scala

Lines changed: 0 additions & 2 deletions
This file was deleted.

tests/neg/i4820b.scala

Lines changed: 0 additions & 5 deletions
This file was deleted.

tests/neg/i4820c.scala

Lines changed: 0 additions & 2 deletions
This file was deleted.

tests/pos/i16778.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
final abstract class ForcedRecompilationToken[T]
2+
3+
object ForcedRecompilationToken {
4+
implicit def materialize: ForcedRecompilationToken["x"] = null.asInstanceOf[ForcedRecompilationToken["x"]]
5+
}
6+
7+
class PluginDef[T](implicit val recompilationToken: ForcedRecompilationToken[T])
8+
9+
object X {
10+
val no = {
11+
final class anon extends PluginDef {} // was: missing type parameters
12+
new anon
13+
}
14+
15+
val bad = new PluginDef {} // was: No given instance
16+
val good = new PluginDef() {} // ok
17+
}
18+
19+
object DependingPlugin {
20+
class NestedDoublePlugin extends PluginDef
21+
object NestedDoublePlugin extends PluginDef
22+
}

tests/pos/i4820.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class Foo[A]
2+
class Bar[A] extends Foo // was error, now expanded to Foo[Nothing]

tests/pos/i4820b.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
trait SetOps[A, +C <: SetOps[A, C]] {
2+
def concat(that: Iterable[A]): C = ???
3+
}
4+
5+
class Set1[A] extends SetOps // ideally should be SetOps[A, Set1[A]], but SetOps[Nothing, Nothin] is inferred

0 commit comments

Comments
 (0)