Skip to content

Commit d8356b6

Browse files
committed
Reorganization of template parents.
Template parents always were constructor calls before. This is not correct because in a situation like the one elaborated in templateParents, the trait D has the class C as supertype, but it does not call its constructor (in fact, if we added a () parameter list to make it into a constructor this would be wrong because C takes parameters. Now parents can be either types or constructor calls. The logic in Namer and Typer that deals with parents is cleaned up. In particular, we now construct any synthetic class parent as a full type, before calling normalizeToClassRefs. This obviates the forwardRefs logic that needed to be done in a cleanup of Namers. Also added two more checks: (1) All parents except the first one must point to traits. (2) A trait may not call a parent class constructor.
1 parent 5a8f4c8 commit d8356b6

File tree

11 files changed

+142
-78
lines changed

11 files changed

+142
-78
lines changed

src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ object desugar {
226226
def productConstr(n: Int) = {
227227
val tycon = ref(defn.ProductNClass(n).typeRef)
228228
val targs = vparamss.head map (_.tpt)
229-
New(AppliedTypeTree(tycon, targs), Nil)
229+
AppliedTypeTree(tycon, targs)
230230
}
231231

232232
// Case classes get a ProductN parent
@@ -241,7 +241,7 @@ object desugar {
241241
moduleDef(
242242
ModuleDef(
243243
Modifiers(Synthetic), name.toTermName,
244-
Template(emptyConstructor, New(parentTpt, Nil) :: Nil, EmptyValDef, defs))).toList
244+
Template(emptyConstructor, parentTpt :: Nil, EmptyValDef, defs))).toList
245245

246246
// The companion object defifinitions, if a companion is needed, Nil otherwise.
247247
// companion definitions include:
@@ -713,13 +713,13 @@ object desugar {
713713
/** Create a class definition with the same info as this refined type.
714714
* parent { refinements }
715715
* ==>
716-
* class <refinement> extends parent { refinements }
716+
* trait <refinement> extends parent { refinements }
717717
*
718718
* The result is used for validity checking, is thrown away afterwards.
719719
*/
720720
def refinedTypeToClass(tree: RefinedTypeTree)(implicit ctx: Context): TypeDef = {
721721
val impl = Template(emptyConstructor, tree.tpt :: Nil, EmptyValDef, tree.refinements)
722-
TypeDef(Modifiers(), tpnme.REFINE_CLASS, impl)
722+
TypeDef(Modifiers(Trait), tpnme.REFINE_CLASS, impl)
723723
}
724724

725725
/** If tree is a variable pattern, return its name and type, otherwise return None.

src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ object SymDenotations {
269269
/** Is this denotation a class? */
270270
final def isClass: Boolean = isInstanceOf[ClassDenotation]
271271

272+
/** Is this denotation a non-trait class? */
273+
final def isRealClass(implicit ctx: Context) = isClass && !is(Trait)
274+
272275
/** Cast to class denotation */
273276
final def asClass: ClassDenotation = asInstanceOf[ClassDenotation]
274277

src/dotty/tools/dotc/core/TypeApplications.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ class TypeApplications(val self: Type) extends AnyVal {
150150
NoType
151151
}
152152

153+
/** The base type including all type arguments of this type */
154+
final def baseTypeWithArgs(base: Symbol)(implicit ctx: Context): Type =
155+
self.baseType(base).appliedTo(baseTypeArgs(base))
156+
153157
/** Translate a type of the form From[T] to To[T], keep other types as they are.
154158
* `from` and `to` must be static classes, both with one type parameter, and the same variance.
155159
*/

src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,9 +1050,12 @@ object Parsers {
10501050
case NEW =>
10511051
canApply = false
10521052
val start = in.skipToken()
1053-
templateOrNew(emptyConstructor()) match {
1054-
case impl: Template => atPos(start) { New(impl) }
1055-
case nu => adjustStart(start) { nu }
1053+
val (impl, missingBody) = template(emptyConstructor())
1054+
impl.parents match {
1055+
case parent :: Nil if missingBody =>
1056+
if (parent.isType) ensureApplied(wrapNew(parent)) else parent
1057+
case _ =>
1058+
New(impl)
10561059
}
10571060
case _ =>
10581061
if (isLiteral) literal()
@@ -1825,38 +1828,36 @@ object Parsers {
18251828

18261829
/** ConstrApp ::= SimpleType {ParArgumentExprs}
18271830
*/
1828-
val constrApp = () =>
1829-
ensureApplied(parArgumentExprss(wrapNew(simpleType())))
1831+
val constrApp = () => {
1832+
val t = simpleType()
1833+
if (in.token == LPAREN) parArgumentExprss(wrapNew(t))
1834+
else t
1835+
}
18301836

18311837
/** Template ::= ConstrApps [TemplateBody] | TemplateBody
18321838
* ConstrApps ::= ConstrApp {`with' ConstrApp}
1839+
*
1840+
* @return a pair consisting of the template, and a boolean which indicates
1841+
* whether the template misses a body (i.e. no {...} part).
18331842
*/
1834-
def template(constr: DefDef): Template = templateOrNew(constr) match {
1835-
case impl: Template => impl
1836-
case parent => Template(constr, parent :: Nil, EmptyValDef, Nil)
1837-
}
1838-
1839-
/** Same as template, but if {...} is missing and there's only one
1840-
* parent return the parent instead of a template. Called from New.
1841-
*/
1842-
def templateOrNew(constr: DefDef): Tree = {
1843+
def template(constr: DefDef): (Template, Boolean) = {
18431844
newLineOptWhenFollowedBy(LBRACE)
1844-
if (in.token == LBRACE) templateBodyOpt(constr, Nil)
1845+
if (in.token == LBRACE) (templateBodyOpt(constr, Nil), false)
18451846
else {
18461847
val parents = tokenSeparated(WITH, constrApp)
18471848
newLineOptWhenFollowedBy(LBRACE)
1848-
if (in.token != LBRACE && parents.length == 1) parents.head
1849-
else templateBodyOpt(constr, parents)
1849+
val missingBody = in.token != LBRACE
1850+
(templateBodyOpt(constr, parents), missingBody)
18501851
}
18511852
}
18521853

18531854
/** TemplateOpt = [`extends' Template | TemplateBody]
18541855
*/
18551856
def templateOpt(constr: DefDef): Template =
1856-
if (in.token == EXTENDS) { in.nextToken(); template(constr) }
1857+
if (in.token == EXTENDS) { in.nextToken(); template(constr)._1 }
18571858
else {
18581859
newLineOptWhenFollowedBy(LBRACE)
1859-
if (in.token == LBRACE) template(constr)
1860+
if (in.token == LBRACE) template(constr)._1
18601861
else Template(constr, Nil, EmptyValDef, Nil).withPos(constr.pos.toSynthetic)
18611862
}
18621863

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

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -410,13 +410,15 @@ object Inferencing {
410410
def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit =
411411
if (!tp.isStable) ctx.error(i"Prefix of type ${tp.widenIfUnstable} is not stable", pos)
412412

413-
/** Check that `tp` is a class type with a stable prefix.
414-
* @return Underlying class type if type checks out OK, ObjectClass.typeRef if not.
413+
/** Check that `tp` is a class type with a stable prefix. Also, if `isFirst` is
414+
* false check that `tp` is a trait.
415+
* @return `tp` itself if it is a class or trait ref, ObjectClass.typeRef if not.
415416
*/
416-
def checkClassTypeWithStablePrefix(tp: Type, pos: Position)(implicit ctx: Context): TypeRef =
417+
def checkClassTypeWithStablePrefix(tp: Type, pos: Position, traitReq: Boolean)(implicit ctx: Context): Type =
417418
tp.underlyingClassRef match {
418-
case tp: TypeRef =>
419-
checkStable(tp.prefix, pos)
419+
case tref: TypeRef =>
420+
checkStable(tref.prefix, pos)
421+
if (traitReq && !(tref.symbol is Trait)) ctx.error(i"$tref is not a trait", pos)
420422
tp
421423
case _ =>
422424
ctx.error(i"$tp is not a class type", pos)
@@ -441,45 +443,45 @@ object Inferencing {
441443
case _ =>
442444
}
443445

444-
/** Ensure that first typeref in a list of parents points to a non-trait class.
445-
* If that's not already the case, add one.
446+
/** Ensure that the first type in a list of parent types Ps points to a non-trait class.
447+
* If that's not already the case, add one. The added class type CT is determined as follows.
448+
* First, let C be the unique class such that
449+
* - there is a parent P_i such that P_i derives from C, and
450+
* - for every class D: If some parent P_j, j <= i derives from D, then C derives from D.
451+
* Then, let CT be the smallest type which
452+
* - has C as its class symbol, and
453+
* - for all parents P_i: If P_i derives from C then P_i <:< CT.
446454
*/
447-
def ensureFirstIsClass(prefs: List[TypeRef])(implicit ctx: Context): List[TypeRef] = {
448-
def isRealClass(sym: Symbol) = sym.isClass && !(sym is Trait)
449-
def realClassParent(tref: TypeRef): TypeRef =
450-
if (isRealClass(tref.symbol)) tref
451-
else tref.info.parents match {
452-
case pref :: _ => if (isRealClass(pref.symbol)) pref else realClassParent(pref)
453-
case nil => defn.ObjectClass.typeRef
455+
def ensureFirstIsClass(parents: List[Type])(implicit ctx: Context): List[Type] = {
456+
def realClassParent(cls: Symbol): ClassSymbol =
457+
if (!cls.isClass) defn.ObjectClass
458+
else if (!(cls is Trait)) cls.asClass
459+
else cls.asClass.classParents match {
460+
case parentRef :: _ => realClassParent(parentRef.symbol)
461+
case nil => defn.ObjectClass
454462
}
455-
def improve(clsRef: TypeRef, parent: TypeRef): TypeRef = {
456-
val pclsRef = realClassParent(parent)
457-
if (pclsRef.symbol derivesFrom clsRef.symbol) pclsRef else clsRef
463+
def improve(candidate: ClassSymbol, parent: Type): ClassSymbol = {
464+
val pcls = realClassParent(parent.classSymbol)
465+
if (pcls derivesFrom candidate) pcls else candidate
458466
}
459-
prefs match {
460-
case pref :: _ if isRealClass(pref.symbol) => prefs
461-
case _ => (defn.ObjectClass.typeRef /: prefs)(improve) :: prefs
467+
parents match {
468+
case p :: _ if p.classSymbol.isRealClass => parents
469+
case _ =>
470+
val pcls = (defn.ObjectClass /: parents)(improve)
471+
val ptype = ctx.typeComparer.glb(
472+
defn.ObjectType :: (parents map (_ baseTypeWithArgs pcls)))
473+
ptype :: parents
462474
}
463475
}
464476

465-
/** Forward bindings of all type parameters of `pcls`. That is, if the type parameter
466-
* if instantiated in a parent class, include its type binding in the current class.
467-
*/
468-
def forwardTypeParams(pcls: ClassSymbol, cls: ClassSymbol, decls: Scope)(implicit ctx: Context): Unit = {
469-
for (tparam <- pcls.typeParams) {
470-
val argSym: Symbol = cls.thisType.member(tparam.name).symbol
471-
argSym.info match {
472-
case TypeAlias(TypeRef(ThisType(_), name)) =>
473-
val from = cls.thisType.member(name).symbol
474-
from.info match {
475-
case bounds: TypeBounds =>
476-
typr.println(s"forward ref $argSym $from $bounds")
477-
ctx.forwardRef(argSym, from, bounds, cls, decls)
478-
case _ =>
479-
}
480-
case _ =>
481-
}
482-
}
477+
/** Ensure that first parent tree refers to a real class. */
478+
def ensureFirstIsClass(parents: List[Tree], pos: Position)(implicit ctx: Context): List[Tree] = parents match {
479+
case p :: ps if p.tpe.classSymbol.isRealClass => parents
480+
case _ =>
481+
// add synthetic class type
482+
val parentTypes = ensureFirstIsClass(parents.tpes)
483+
assert(parentTypes.length > parents.length)
484+
(TypeTree(parentTypes.head) withPos pos) :: parents
483485
}
484486

485487
/** Check that class does not define */

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

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -409,41 +409,39 @@ class Namer { typer: Typer =>
409409
/** The type of a parent constructor. Types constructor arguments
410410
* only if parent type contains uninstantiated type parameters.
411411
*/
412-
def parentType(constr: untpd.Tree)(implicit ctx: Context): Type =
413-
if (constr.isType) { // this case applies to desugared refined types
414-
typedAheadType(constr).tpe
412+
def parentType(parent: untpd.Tree)(implicit ctx: Context): Type =
413+
if (parent.isType) {
414+
typedAheadType(parent).tpe
415415
} else {
416-
val (core, targs) = stripApply(constr) match {
416+
val (core, targs) = stripApply(parent) match {
417417
case TypeApply(core, targs) => (core, targs)
418418
case core => (core, Nil)
419419
}
420420
val Select(New(tpt), nme.CONSTRUCTOR) = core
421421
val targs1 = targs map (typedAheadType(_))
422422
val ptype = typedAheadType(tpt).tpe appliedTo targs1.tpes
423423
if (ptype.uninstantiatedTypeParams.isEmpty) ptype
424-
else typedAheadExpr(constr).tpe
424+
else typedAheadExpr(parent).tpe
425425
}
426426

427+
def checkedParentType(parent: untpd.Tree): Type = {
428+
val ptype = parentType(parent)(ctx.fresh addMode Mode.InSuperCall)
429+
checkClassTypeWithStablePrefix(ptype, parent.pos, traitReq = parent ne parents.head)
430+
}
431+
427432
val selfInfo =
428433
if (self.isEmpty) NoType
429434
else if (cls is Module) cls.owner.thisType select sourceModule
430435
else createSymbol(self)
431436
// pre-set info, so that parent types can refer to type params
432437
denot.info = ClassInfo(cls.owner.thisType, cls, Nil, decls, selfInfo)
433-
val parentTypes = parents map (parentType(_)(ctx.fresh addMode Mode.InSuperCall))
438+
val parentTypes = ensureFirstIsClass(parents map checkedParentType)
434439
val parentRefs = ctx.normalizeToClassRefs(parentTypes, cls, decls)
435-
val parentClsRefs =
436-
for ((parentRef, constr) <- parentRefs zip parents)
437-
yield checkClassTypeWithStablePrefix(parentRef, constr.pos)
438-
val normalizedParentClsRefs = ensureFirstIsClass(parentClsRefs)
440+
typr.println(s"completing $denot, parents = $parents, parentTypes = $parentTypes, parentRefs = $parentRefs")
439441

440442
index(constr)
441443
index(rest)(inClassContext(selfInfo))
442-
denot.info = ClassInfo(cls.owner.thisType, cls, normalizedParentClsRefs, decls, selfInfo)
443-
if (parentClsRefs ne normalizedParentClsRefs) {
444-
forwardTypeParams(normalizedParentClsRefs.head.symbol.asClass, cls, decls)
445-
typr.println(i"expanded parents of $denot: $normalizedParentClsRefs%, %")
446-
}
444+
denot.info = ClassInfo(cls.owner.thisType, cls, parentRefs, decls, selfInfo)
447445
}
448446
}
449447

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ class Typer extends Namer with Applications with Implicits {
431431
typed(cpy.Block(tree, clsDef :: Nil, New(Ident(x), Nil)), pt)
432432
case _ =>
433433
val tpt1 = typedType(tree.tpt)
434-
val clsref = checkClassTypeWithStablePrefix(tpt1.tpe, tpt1.pos)
434+
val clsref = checkClassTypeWithStablePrefix(tpt1.tpe, tpt1.pos, traitReq = false)
435435
// todo in a later phase: checkInstantiatable(cls, tpt1.pos)
436436
cpy.New(tree, tpt1).withType(tpt1.tpe)
437437
}
@@ -904,10 +904,31 @@ class Typer extends Namer with Applications with Implicits {
904904
}
905905

906906
def typedClassDef(cdef: untpd.TypeDef, cls: ClassSymbol)(implicit ctx: Context) = track("typedClassDef") {
907+
val superCtx = ctx.fresh addMode Mode.InSuperCall
908+
def typedParent(tree: untpd.Tree): Tree =
909+
if (tree.isType) typedType(tree)(superCtx)
910+
else {
911+
val result = typedExpr(tree)(superCtx)
912+
if ((cls is Trait) && result.tpe.classSymbol.isRealClass)
913+
ctx.error(s"trait may not call constructor of ${result.tpe.classSymbol}", tree.pos)
914+
result
915+
}
916+
917+
/** If this is a real class, make sure its first parent is a
918+
* constructor call. Cannot simply use a type.
919+
*/
920+
def ensureConstrCall(parents: List[Tree]): List[Tree] = {
921+
val firstParent :: otherParents = parents
922+
if (firstParent.isType && !(cls is Trait))
923+
typed(untpd.New(untpd.TypedSplice(firstParent), Nil))(superCtx) :: otherParents
924+
else parents
925+
}
926+
907927
val TypeDef(mods, name, impl @ Template(constr, parents, self, body)) = cdef
908928
val mods1 = typedModifiers(mods)
909929
val constr1 = typed(constr).asInstanceOf[DefDef]
910-
val parents1 = parents mapconserve (typed(_)(ctx.fresh addMode Mode.InSuperCall))
930+
val parents1 = ensureConstrCall(ensureFirstIsClass(
931+
parents mapconserve typedParent, cdef.pos.toSynthetic))
911932
val self1 = typed(self).asInstanceOf[ValDef]
912933
val localDummy = ctx.newLocalDummy(cls, impl.pos)
913934
val body1 = typedStats(body, localDummy)(inClassContext(self1.symbol))

test/dotc/tests.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,16 @@ class tests extends CompilerTest {
3838
@Test def pos_assignments() = compileFile(posDir, "assignments")
3939
@Test def pos_packageobject() = compileFile(posDir, "packageobject")
4040
@Test def pos_overloaded() = compileFile(posDir, "overloaded")
41+
@Test def pos_templateParents() = compileFile(posDir, "templateParents")
4142

4243
@Test def neg_blockescapes() = compileFile(negDir, "blockescapesNeg", xerrors = 1)
4344
@Test def neg_typedapply() = compileFile(negDir, "typedapply", xerrors = 4)
4445
@Test def neg_typedidents() = compileFile(negDir, "typedIdents", xerrors = 2)
4546
@Test def neg_assignments() = compileFile(negDir, "assignments", xerrors = 3)
4647
@Test def neg_typers() = compileFile(negDir, "typers", xerrors = 10)
48+
@Test def neg_privates() = compileFile(negDir, "privates", xerrors = 2)
4749
@Test def neg_rootImports = compileFile(negDir, "rootImplicits", xerrors = 2)
50+
@Test def neg_templateParents() = compileFile(posDir, "templateParents", xerrors = 2)
4851

4952
@Test def dotc = compileDir(dotcDir + "tools/dotc")
5053
@Test def dotc_ast = compileDir(dotcDir + "tools/dotc/ast")

tests/neg/privates.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
trait T {
2+
private def foo = 0;
3+
private[this] def bar = 0
4+
5+
}
6+
7+
class C { self: T =>
8+
foo
9+
bar
10+
}
11+

tests/neg/templateParents.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
object templateParentsNeg {
2+
3+
class C(x: String)
4+
class C2
5+
trait D extends C("a") // error: traits may not call class constructors
6+
7+
new C("b") with C2 // error: C2 is not a trait
8+
9+
}

tests/pos/templateParents.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
object templateParents {
2+
3+
// traits do not call a constructor
4+
class C[+T](x: T)
5+
trait D extends C[String]
6+
trait E extends C[Int]
7+
new C("abc") with D
8+
9+
//val x = new D with E
10+
11+
//val y: C = x
12+
}

0 commit comments

Comments
 (0)