From 8ec91d472f28ec1bf1c957815e5ca987410b7c47 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 23 Sep 2014 12:44:31 +0200 Subject: Completed constructors phase 1) Type parameters are now copied to accessors 2) Constructors also work for traits 2) makes it possible do to mixin after constructors. --- src/dotty/tools/dotc/core/Flags.scala | 7 +- src/dotty/tools/dotc/transform/Constructors.scala | 205 ++++++++++++++-------- 2 files changed, 141 insertions(+), 71 deletions(-) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index 64c87ecb8..de27282fb 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -543,12 +543,15 @@ object Flags { /** Labeled private[this] */ final val PrivateLocal = allOf(Private, Local) - /** A private parameter accessor */ + /** A private[this] parameter accessor */ final val PrivateLocalParamAccessor = allOf(Private, Local, ParamAccessor) - /** A private parameter */ + /** A private[this] parameter */ final val PrivateLocalParam = allOf(Private, Local, Param) + /** A private parameter accessor */ + final val PrivateParamAccessor = allOf(Private, ParamAccessor) + /** A local parameter */ final val ParamAndLocal = allOf(Param, Local) diff --git a/src/dotty/tools/dotc/transform/Constructors.scala b/src/dotty/tools/dotc/transform/Constructors.scala index d03b90a27..3d2f5a0d7 100644 --- a/src/dotty/tools/dotc/transform/Constructors.scala +++ b/src/dotty/tools/dotc/transform/Constructors.scala @@ -16,9 +16,11 @@ import SymDenotations._ import Types._ import Decorators._ import DenotTransformers._ -import ExplicitOuter.outerParamAccessor +import collection.mutable -/** This transform moves initializers from body to constructor. +/** This transform + * - moves initializers from body to constructor. + * - makes all supercalls explicit */ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransform => import tpd._ @@ -28,21 +30,37 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor override def treeTransformPhase = thisTransform.next + /** Symbols that are owned by either or a class field move into the + * primary constructor. + */ override def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = { def ownerBecomesConstructor(owner: Symbol): Boolean = - (owner.isLocalDummy || - owner.isTerm && !owner.is(Method) && owner.owner.isClass) && - !owner.enclosingClass.is(Trait) // TODO: Remove qualification once Mixin is operational + (owner.isLocalDummy || owner.isTerm && !owner.is(Method)) && + owner.owner.isClass if (ownerBecomesConstructor(sym.owner)) sym.copySymDenotation(owner = sym.owner.enclosingClass.primaryConstructor) else sym } - private def intoConstr(accessors: List[Symbol], params: List[Symbol]) = new TreeMap { + /** @return true if after ExplicitOuter, all references from this tree go via an + * outer link, so no parameter accessors need to be rewired to parameters + */ + private def noDirectRefsFrom(tree: Tree)(implicit ctx: Context) = + tree.isDef && tree.symbol.isClass && !tree.symbol.is(InSuperCall) + + /** Adjustments performed when moving code into the constructor: + * (1) Replace references to param accessors by constructor parameters + * except possibly references to mutable variables, if `excluded = Mutable`. + * (Mutable parameters should be replaced only during the super call) + * (2) If the parameter accessor reference was to an alias getter, + * drop the () when replacing by the parameter. + */ + class IntoConstrMap(accessors: List[Symbol], params: List[Symbol]) extends TreeMap { + private var excluded: FlagSet = _ override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { case Ident(_) | Select(This(_), _) => val sym = tree.symbol - if (sym is ParamAccessor) { + if (sym is (ParamAccessor, butNot = excluded)) { val param = sym.subst(accessors, params) if (param ne sym) ref(param).withPos(tree.pos) else tree @@ -50,79 +68,128 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor else tree case Apply(fn, Nil) => val fn1 = transform(fn) - if ((fn1 ne fn) && - fn1.symbol.is(Param) && - fn1.symbol.owner.isPrimaryConstructor) { - // Two possible cases, which each need their adaptation: - if (fn1.symbol.initial.info.isInstanceOf[ExprType]) - // it's either a call-by-name parameter, which is erased to Function0, - // then we need to insert an apply. - cpy.Apply(tree)(Select(fn1, nme.apply), Nil).ensureConforms(tree.tpe) - else - // or original accessor was an alias accessor, then we need to drop the () - fn1 - } + if ((fn1 ne fn) && fn1.symbol.is(Param) && fn1.symbol.owner.isPrimaryConstructor) + fn1 // in this case, fn1.symbol was an alias for a parameter in a superclass else cpy.Apply(tree)(fn1, Nil) case _ => - super.transform(tree) + if (noDirectRefsFrom(tree)) tree else super.transform(tree) } - } - private def splitStats(stats: List[Tree])(implicit ctx: Context): (List[Tree], List[Tree]) = stats match { - case stat :: stats1 => - val (constrStats, clsStats) = splitStats(stats1) - stat match { - case stat @ ValDef(mods, name, tpt, rhs) if !rhs.isEmpty => - val inits = - if (isWildcardArg(rhs)) Nil - else Assign(ref(stat.symbol), rhs).withPos(stat.pos) :: Nil - (inits ::: constrStats, cpy.ValDef(stat)(rhs = EmptyTree) :: clsStats) - case _: DefTree => - (constrStats, stat :: clsStats) - case _ => - (stat :: constrStats, clsStats) - } - case Nil => - (Nil, Nil) + def apply(tree: Tree, excluded: FlagSet)(implicit ctx: Context): Tree = { + this.excluded = excluded + transform(tree) + } } override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo): Tree = { val cls = ctx.owner.asClass - if (cls is Trait) tree - else { - val constr @ DefDef(_, nme.CONSTRUCTOR, Nil, vparams :: Nil, _, EmptyTree) = tree.constr - val (superApp @ Apply( - superSel @ Select( - superNew @ New(superType), - nme.CONSTRUCTOR), - superArgs)) :: traitParents = tree.parents - var accessors = cls.paramAccessors.filterNot(_.isSetter) - var vparamsWithOuter = vparams - if (!accessors.hasSameLengthAs(vparams)) { - accessors.reverse match { - case last :: _ if (last.name == nme.OUTER) => - accessors = last :: accessors.init - vparamsWithOuter = ValDef(last.asTerm) :: vparams + val constr @ DefDef(_, nme.CONSTRUCTOR, Nil, vparams :: Nil, _, EmptyTree) = tree.constr + + // Produce aligned accessors and constructor parameters. We have to adjust + // for any outer parameters, which are last in the sequence of original + // parameter accessors but should come first in the constructor parameter list. + var accessors = cls.paramAccessors.filterNot(_.isSetter) + var vparamsWithOuter = vparams + if (!accessors.hasSameLengthAs(vparams)) { + accessors.reverse match { + case last :: _ if (last.name == nme.OUTER) => + accessors = last :: accessors.init // align wth calling convention + vparamsWithOuter = ValDef(last.asTerm) :: vparams + case _ => + } + assert(accessors.hasSameLengthAs(vparamsWithOuter), + i"lengths differ for $cls, param accs = $accessors, params = $vparamsWithOuter") + } + val paramSyms = vparamsWithOuter map (_.symbol) + + val intoConstr = new IntoConstrMap(accessors, paramSyms) + val superCalls = new mutable.ListBuffer[Tree] + + // If parent is a constructor call, pull out the call into a separate + // supercall constructor, which gets appended to `superCalls`, and keep + // only the type. + def normalizeParent(tree: Tree) = tree match { + case superApp @ Apply( + superSel @ Select( + superNew @ New(superType), + nme.CONSTRUCTOR), + superArgs) => + val toClass = !superType.symbol.is(Trait) + val mappedArgs = superArgs.map( + intoConstr(_, excluded = if (toClass) Mutable else EmptyFlags)) + val receiver = + if (toClass) Super(This(cls), tpnme.EMPTY, inConstrCall = true) + else This(cls) + superCalls += cpy.Apply(superApp)( + receiver.withPos(superNew.pos) + .select(superSel.symbol).withPos(superSel.pos), + mappedArgs) + superType + case tree: TypeTree => tree + } + val parentTypeTrees = tree.parents.map(normalizeParent) + + // Split class body into statements that go into constructor and + // definitions that are kept as members of the class. + def splitStats(stats: List[Tree]): (List[Tree], List[Tree]) = stats match { + case stat :: stats1 => + val (constrStats, clsStats) = splitStats(stats1) + stat match { + case stat @ ValDef(mods, name, tpt, rhs) => + val inits = + if (rhs.isEmpty || isWildcardArg(rhs)) Nil + else Assign(ref(stat.symbol), intoConstr(rhs, excluded = Mutable)) + .withPos(stat.pos) :: Nil + (inits ::: constrStats, cpy.ValDef(stat)(rhs = EmptyTree) :: clsStats) + case _: DefTree => + (constrStats, stat :: clsStats) case _ => + (intoConstr(stat, excluded = Mutable) :: constrStats, clsStats) } - assert(accessors.hasSameLengthAs(vparamsWithOuter), - i"lengths differ for $cls, param accs = $accessors, params = $vparamsWithOuter") - } - val mappedArgs = superArgs.map( - intoConstr(accessors, vparamsWithOuter.map(_.symbol)).transform) - val superCall = - cpy.Apply(superApp)( - cpy.Select(superSel)( - Super(This(cls), tpnme.EMPTY, inConstrCall = true).withPos(superNew.pos), - nme.CONSTRUCTOR), - mappedArgs) - val (constrStats, clsStats) = splitStats(tree.body) - def normalizeOwner(stat: Tree) = { + case Nil => + (Nil, Nil) + } + val (constrStats, clsStats) = splitStats(tree.body) + + // Collect all private parameter accessors that need to be retained + // because they are accessed after the constructor has finished. + val collectRetained = new TreeAccumulator[Set[Symbol]] { + override def apply(retained: Set[Symbol], tree: Tree) = tree match { + case tree: RefTree => + val sym = tree.symbol + foldOver( + if (sym.is(PrivateParamAccessor) && sym.owner == cls) retained + sym else retained, + tree) + case _ => + if (noDirectRefsFrom(tree)) retained else foldOver(retained, tree) } - cpy.Template(tree)( - constr = cpy.DefDef(constr)(rhs = Block(superCall :: constrStats, unitLiteral)), - parents = superType :: traitParents, - body = clsStats) } + val retainedPrivate = collectRetained(collectRetained(Set[Symbol](), constrStats), clsStats) + def isRetained(acc: Symbol) = + (!acc.is(Private) || acc.is(NotJavaPrivate) || retainedPrivate(acc)) + + val accessorFields = accessors.filterNot(_ is Method) + val (retainedAccessors, droppedAccessors) = accessorFields.partition(isRetained) + + // The initializers for the retained accessors */ + val copyParams = retainedAccessors.map(acc => + Assign(ref(acc), ref(acc.subst(accessors, paramSyms))).withPos(tree.pos)) + + // Drop accessors that are not retained from class scope + if (droppedAccessors.nonEmpty) { + val clsInfo = cls.classInfo // TODO investigate: expand clsInfo to cls.info => dotty type error + cls.copy( + info = clsInfo.derivedClassInfo( + decls = clsInfo.decls.filteredScope(!droppedAccessors.contains(_)))) + } + + cpy.Template(tree)( + constr = cpy.DefDef(constr)( + rhs = Block(superCalls.toList ::: copyParams ::: constrStats, unitLiteral)), + parents = parentTypeTrees, + body = clsStats filter { + case vdef: ValDef => !droppedAccessors.contains(vdef.symbol) + case _ => true + }) } } \ No newline at end of file -- cgit v1.2.3