Skip to content

Try to be more subtle when inferring type parameters of class parents #16896

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Inferencing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
36 changes: 25 additions & 11 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
}
Expand Down
13 changes: 5 additions & 8 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 3 additions & 10 deletions compiler/test-resources/repl/i7644
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,13 @@ 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
| ^
| 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

4 changes: 2 additions & 2 deletions tests/neg/i1643.scala
Original file line number Diff line number Diff line change
@@ -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
}
Expand All @@ -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 {}
Expand Down
2 changes: 0 additions & 2 deletions tests/neg/i4820.scala

This file was deleted.

5 changes: 0 additions & 5 deletions tests/neg/i4820b.scala

This file was deleted.

2 changes: 0 additions & 2 deletions tests/neg/i4820c.scala

This file was deleted.

22 changes: 22 additions & 0 deletions tests/pos/i16778.scala
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 2 additions & 0 deletions tests/pos/i4820.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Foo[A]
class Bar[A] extends Foo // was error, now expanded to Foo[Nothing]
5 changes: 5 additions & 0 deletions tests/pos/i4820b.scala
Original file line number Diff line number Diff line change
@@ -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