Skip to content

Commit 25d1975

Browse files
committed
Insert traits with implicit parameters as extra parents of classes
If a trait A extends some other trait B which takes an implicit parameter, and a class C extends A but not B, we need to insert B into the parents of A, so that the implicit argument can be generated. This required a generalization of the syncing between parent trees and parent types. Namer will augment the parent types of a class with possibly other types. Previously the only such change was a possibly leading class type, but we now also generate trait parents that take context parameters. The new method parentTrees reflects any such changes in the parent trees in Typer.
1 parent 0195dff commit 25d1975

File tree

7 files changed

+153
-53
lines changed

7 files changed

+153
-53
lines changed

compiler/src/dotty/tools/dotc/transform/TypeUtils.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,10 @@ object TypeUtils {
8080
case self: TypeProxy =>
8181
self.underlying.companionRef
8282
}
83+
84+
/** Is this type a methodic type that takes implicit parameters (both old and new) at some point? */
85+
def takesImplicitParams(using Context): Boolean = self.stripPoly match
86+
case mt: MethodType => mt.isImplicitMethod || mt.resType.takesImplicitParams
87+
case _ => false
8388
}
8489
}

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

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1237,12 +1237,81 @@ class Namer { typer: Typer =>
12371237
}
12381238
}
12391239

1240+
/** Ensure that the first type in a list of parent types Ps points to a non-trait class.
1241+
* If that's not already the case, add one. The added class type CT is determined as follows.
1242+
* First, let C be the unique class such that
1243+
* - there is a parent P_i such that P_i derives from C, and
1244+
* - for every class D: If some parent P_j, j <= i derives from D, then C derives from D.
1245+
* Then, let CT be the smallest type which
1246+
* - has C as its class symbol, and
1247+
* - for all parents P_i: If P_i derives from C then P_i <:< CT.
1248+
*/
1249+
def ensureFirstIsClass(parents: List[Type]): List[Type] =
1250+
1251+
def realClassParent(sym: Symbol): ClassSymbol =
1252+
if !sym.isClass then defn.ObjectClass
1253+
else if !sym.is(Trait) then sym.asClass
1254+
else sym.info.parents match
1255+
case parentRef :: _ => realClassParent(parentRef.typeSymbol)
1256+
case nil => defn.ObjectClass
1257+
1258+
def improve(candidate: ClassSymbol, parent: Type): ClassSymbol =
1259+
val pcls = realClassParent(parent.classSymbol)
1260+
if (pcls derivesFrom candidate) pcls else candidate
1261+
1262+
parents match
1263+
case p :: _ if p.classSymbol.isRealClass => parents
1264+
case _ =>
1265+
val pcls = parents.foldLeft(defn.ObjectClass)(improve)
1266+
typr.println(i"ensure first is class $parents%, % --> ${parents map (_ baseType pcls)}%, %")
1267+
val first = TypeComparer.glb(defn.ObjectType :: parents.map(_.baseType(pcls)))
1268+
checkFeasibleParent(first, cls.srcPos, em" in inferred superclass $first") :: parents
1269+
end ensureFirstIsClass
1270+
1271+
/** If `parents` contains references to traits that have supertraits with implicit parameters
1272+
* add those supertraits in lineraization order unless they are already covered by other
1273+
* parent types. For instance, in
1274+
*
1275+
* class A
1276+
* trait B(using I) extends A
1277+
* trait C extends B
1278+
* class D extends A, C
1279+
*
1280+
* the class declaration of `D` is augmented to
1281+
*
1282+
* class D extends A, B, C
1283+
*
1284+
* so that an implicit `I` can be passed to `B`. See i7613.scala for more examples.
1285+
*/
1286+
def addUsingTraits(parents: List[Type]): List[Type] =
1287+
lazy val existing = parents.map(_.classSymbol).toSet
1288+
def recur(parents: List[Type]): List[Type] = parents match
1289+
case parent :: parents1 =>
1290+
val psym = parent.classSymbol
1291+
val addedTraits =
1292+
if psym.is(Trait) then
1293+
psym.asClass.baseClasses.tail.iterator
1294+
.takeWhile(_.is(Trait))
1295+
.filter(p =>
1296+
p.primaryConstructor.info.takesImplicitParams
1297+
&& !cls.superClass.isSubClass(p)
1298+
&& !existing.contains(p))
1299+
.toList.reverse
1300+
else Nil
1301+
addedTraits.map(parent.baseType) ::: parent :: recur(parents1)
1302+
case nil =>
1303+
Nil
1304+
if cls.isRealClass then recur(parents) else parents
1305+
end addUsingTraits
1306+
12401307
completeConstructor(denot)
12411308
denot.info = tempInfo
12421309

12431310
val parentTypes = defn.adjustForTuple(cls, cls.typeParams,
12441311
defn.adjustForBoxedUnit(cls,
1245-
ensureFirstIsClass(parents.map(checkedParentType(_)), cls.span)
1312+
addUsingTraits(
1313+
ensureFirstIsClass(parents.map(checkedParentType(_)))
1314+
)
12461315
)
12471316
)
12481317
typr.println(i"completing $denot, parents = $parents%, %, parentTypes = $parentTypes%, %")

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ class ReTyper extends Typer with ReChecking {
103103

104104
override def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(using Context): Unit = ()
105105

106-
override def ensureConstrCall(cls: ClassSymbol, parents: List[Tree])(using Context): List[Tree] =
107-
parents
106+
override def ensureConstrCall(cls: ClassSymbol, parent: Tree)(using Context): Tree =
107+
parent
108108

109109
override def handleUnexpectedFunType(tree: untpd.Apply, fun: Tree)(using Context): Tree = fun.tpe match {
110110
case mt: MethodType =>

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

Lines changed: 41 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2212,20 +2212,18 @@ class Typer extends Namer
22122212
* @param psym Its type symbol
22132213
* @param cinfo The info of its constructor
22142214
*/
2215-
def maybeCall(ref: Tree, psym: Symbol, cinfo: Type): Tree = cinfo.stripPoly match {
2215+
def maybeCall(ref: Tree, psym: Symbol): Tree = psym.primaryConstructor.info.stripPoly match
22162216
case cinfo @ MethodType(Nil) if cinfo.resultType.isImplicitMethod =>
22172217
typedExpr(untpd.New(untpd.TypedSplice(ref)(using superCtx), Nil))(using superCtx)
22182218
case cinfo @ MethodType(Nil) if !cinfo.resultType.isInstanceOf[MethodType] =>
22192219
ref
22202220
case cinfo: MethodType =>
2221-
if (!ctx.erasedTypes) { // after constructors arguments are passed in super call.
2221+
if !ctx.erasedTypes then // after constructors arguments are passed in super call.
22222222
typr.println(i"constr type: $cinfo")
22232223
report.error(ParameterizedTypeLacksArguments(psym), ref.srcPos)
2224-
}
22252224
ref
22262225
case _ =>
22272226
ref
2228-
}
22292227

22302228
val seenParents = mutable.Set[Symbol]()
22312229

@@ -2250,14 +2248,35 @@ class Typer extends Namer
22502248
if (tree.isType) {
22512249
checkSimpleKinded(result) // Not needed for constructor calls, as type arguments will be inferred.
22522250
if (psym.is(Trait) && !cls.is(Trait) && !cls.superClass.isSubClass(psym))
2253-
result = maybeCall(result, psym, psym.primaryConstructor.info)
2251+
result = maybeCall(result, psym)
22542252
}
22552253
else checkParentCall(result, cls)
22562254
checkTraitInheritance(psym, cls, tree.srcPos)
22572255
if (cls is Case) checkCaseInheritance(psym, cls, tree.srcPos)
22582256
result
22592257
}
22602258

2259+
/** Augment `ptrees` to have the same class symbols as `parents`. Generate TypeTrees
2260+
* or New trees to fill in any parents for which no tree exists yet.
2261+
*/
2262+
def parentTrees(parents: List[Type], ptrees: List[Tree]): List[Tree] = parents match
2263+
case parent :: parents1 =>
2264+
val psym = parent.classSymbol
2265+
def hasSameParent(ptree: Tree) = ptree.tpe.classSymbol == psym
2266+
ptrees match
2267+
case ptree :: ptrees1 if hasSameParent(ptree) =>
2268+
ptree :: parentTrees(parents1, ptrees1)
2269+
case ptree :: ptrees1 if ptrees1.exists(hasSameParent) =>
2270+
ptree :: parentTrees(parents, ptrees1)
2271+
case _ =>
2272+
var added: Tree = TypeTree(parent).withSpan(cdef.nameSpan.focus)
2273+
if psym.is(Trait) && psym.primaryConstructor.info.takesImplicitParams then
2274+
// classes get a constructor separately using a different context
2275+
added = ensureConstrCall(cls, added)
2276+
added :: parentTrees(parents1, ptrees)
2277+
case _ =>
2278+
ptrees
2279+
22612280
/** Checks if one of the decls is a type with the same name as class type member in selfType */
22622281
def classExistsOnSelf(decls: Scope, self: tpd.ValDef): Boolean = {
22632282
val selfType = self.tpt.tpe
@@ -2278,8 +2297,10 @@ class Typer extends Namer
22782297

22792298
completeAnnotations(cdef, cls)
22802299
val constr1 = typed(constr).asInstanceOf[DefDef]
2281-
val parentsWithClass = ensureFirstTreeIsClass(parents.mapconserve(typedParent).filterConserve(!_.isEmpty), cdef.nameSpan)
2282-
val parents1 = ensureConstrCall(cls, parentsWithClass)(using superCtx)
2300+
val parents0 = parentTrees(
2301+
cls.classInfo.declaredParents,
2302+
parents.mapconserve(typedParent).filterConserve(!_.isEmpty))
2303+
val parents1 = ensureConstrCall(cls, parents0)(using superCtx)
22832304
val firstParentTpe = parents1.head.tpe.dealias
22842305
val firstParent = firstParentTpe.typeSymbol
22852306

@@ -2348,52 +2369,23 @@ class Typer extends Namer
23482369
protected def addAccessorDefs(cls: Symbol, body: List[Tree])(using Context): List[Tree] =
23492370
ctx.compilationUnit.inlineAccessors.addAccessorDefs(cls, body)
23502371

2351-
/** Ensure that the first type in a list of parent types Ps points to a non-trait class.
2352-
* If that's not already the case, add one. The added class type CT is determined as follows.
2353-
* First, let C be the unique class such that
2354-
* - there is a parent P_i such that P_i derives from C, and
2355-
* - for every class D: If some parent P_j, j <= i derives from D, then C derives from D.
2356-
* Then, let CT be the smallest type which
2357-
* - has C as its class symbol, and
2358-
* - for all parents P_i: If P_i derives from C then P_i <:< CT.
2372+
/** If this is a real class, make sure its first parent is a
2373+
* constructor call. Cannot simply use a type. Overridden in ReTyper.
23592374
*/
2360-
def ensureFirstIsClass(parents: List[Type], span: Span)(using Context): List[Type] = {
2361-
def realClassParent(cls: Symbol): ClassSymbol =
2362-
if (!cls.isClass) defn.ObjectClass
2363-
else if (!cls.is(Trait)) cls.asClass
2364-
else cls.info.parents match {
2365-
case parentRef :: _ => realClassParent(parentRef.typeSymbol)
2366-
case nil => defn.ObjectClass
2367-
}
2368-
def improve(candidate: ClassSymbol, parent: Type): ClassSymbol = {
2369-
val pcls = realClassParent(parent.classSymbol)
2370-
if (pcls derivesFrom candidate) pcls else candidate
2371-
}
2372-
parents match {
2373-
case p :: _ if p.classSymbol.isRealClass => parents
2374-
case _ =>
2375-
val pcls = parents.foldLeft(defn.ObjectClass)(improve)
2376-
typr.println(i"ensure first is class $parents%, % --> ${parents map (_ baseType pcls)}%, %")
2377-
val first = TypeComparer.glb(defn.ObjectType :: parents.map(_.baseType(pcls)))
2378-
checkFeasibleParent(first, ctx.source.atSpan(span), em" in inferred superclass $first") :: parents
2379-
}
2380-
}
2375+
def ensureConstrCall(cls: ClassSymbol, parents: List[Tree])(using Context): List[Tree] = parents match
2376+
case parents @ (first :: others) =>
2377+
parents.derivedCons(ensureConstrCall(cls, first), others)
2378+
case parents =>
2379+
parents
23812380

2382-
/** Ensure that first parent tree refers to a real class. */
2383-
def ensureFirstTreeIsClass(parents: List[Tree], span: Span)(using Context): List[Tree] = parents match {
2384-
case p :: ps if p.tpe.classSymbol.isRealClass => parents
2385-
case _ => TypeTree(ensureFirstIsClass(parents.tpes, span).head).withSpan(span.focus) :: parents
2386-
}
2387-
2388-
/** If this is a real class, make sure its first parent is a
2381+
/** If this is a real class, make sure its first parent is a
23892382
* constructor call. Cannot simply use a type. Overridden in ReTyper.
23902383
*/
2391-
def ensureConstrCall(cls: ClassSymbol, parents: List[Tree])(using Context): List[Tree] = {
2392-
val firstParent :: otherParents = parents
2393-
if (firstParent.isType && !cls.is(Trait) && !cls.is(JavaDefined))
2394-
typed(untpd.New(untpd.TypedSplice(firstParent), Nil)) :: otherParents
2395-
else parents
2396-
}
2384+
def ensureConstrCall(cls: ClassSymbol, parent: Tree)(using Context): Tree =
2385+
if (parent.isType && !cls.is(Trait) && !cls.is(JavaDefined))
2386+
typed(untpd.New(untpd.TypedSplice(parent), Nil))
2387+
else
2388+
parent
23972389

23982390
def localDummy(cls: ClassSymbol, impl: untpd.Template)(using Context): Symbol =
23992391
newLocalDummy(cls, impl.span)

tests/neg/i6060.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class I1(i2: Int) {
22
def apply(i3: Int) = 1
3-
new I1(1)(2) {} // error: too many arguments in parent constructor
3+
new I1(1)(2) {} // error: too many arguments in parent constructor // error
44
}
55

66
class I0(i1: Int) {

tests/run/i7613.check

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
D: B1
2+
superD: B1
3+
E: B2
4+
F: B1
5+
F: B2

tests/run/i7613.scala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
trait Foo[A]
2+
trait Bar[A] extends Foo[A]
3+
trait Baz[A] extends Bar[A]
4+
5+
trait FooLaws[A](using Foo[A])
6+
trait BarLaws[A](using Bar[A]) extends FooLaws[A]
7+
trait BazLaws[A](using Baz[A]) extends BarLaws[A]
8+
9+
def instance1[A](using Baz[A]): BazLaws[A] =
10+
new FooLaws[A] with BarLaws[A] with BazLaws[A] {}
11+
12+
def instance2[A](using Baz[A]): BazLaws[A] =
13+
new BazLaws[A] {}
14+
15+
trait I:
16+
def show(x: String): Unit
17+
class A
18+
trait B1(using I) extends A { summon[I].show("B1") }
19+
trait B2(using I) extends B1 { summon[I].show("B2") }
20+
trait C1 extends B1
21+
trait C2 extends B2
22+
class D(using I) extends A, C1
23+
class E(using I) extends D(using new I { def show(x: String) = println(s"superD: $x")}), C2
24+
class F(using I) extends A, C2
25+
26+
@main def Test =
27+
D(using new I { def show(x: String) = println(s"D: $x")})
28+
E(using new I { def show(x: String) = println(s"E: $x")})
29+
F(using new I { def show(x: String) = println(s"F: $x")})

0 commit comments

Comments
 (0)