package dotty.tools.dotc package transform import core._ import TreeTransforms._ import Contexts.Context import Flags._ import SymUtils._ import Symbols._ import SymDenotations._ import Types._ import Decorators._ import DenotTransformers._ import StdNames._ import NameOps._ import NameKinds._ import Phases._ import ast.untpd import ast.Trees._ import collection.mutable /** This phase performs the following transformations: * * 1. (done in `traitDefs` and `transformSym`) Map every concrete trait getter * * def x(): T = expr * * to the pair of definitions: * * def x(): T * protected def initial$x(): T = { stats; expr } * * where `stats` comprises all statements between either the start of the trait * or the previous field definition which are not definitions (i.e. are executed for * their side effects). * * 2. (done in `traitDefs`) Make every concrete trait setter * * def x_=(y: T) = () * * deferred by mapping it to * * def x_=(y: T) * * 3. For a non-trait class C: * * For every trait M directly implemented by the class (see SymUtils.mixin), in * reverse linearization order, add the following definitions to C: * * 3.1 (done in `traitInits`) For every parameter accessor ` def x(): T` in M, * in order of textual occurrence, add * * def x() = e * * where `e` is the constructor argument in C that corresponds to `x`. Issue * an error if no such argument exists. * * 3.2 (done in `traitInits`) For every concrete trait getter ` def x(): T` in M * which is not a parameter accessor, in order of textual occurrence, produce the following: * * 3.2.1 If `x` is also a member of `C`, and M is a Dotty trait: * * def x(): T = super[M].initial$x() * * 3.2.2 If `x` is also a member of `C`, and M is a Scala 2.x trait: * * def x(): T = _ * * 3.2.3 If `x` is not a member of `C`, and M is a Dotty trait: * * super[M].initial$x() * * 3.2.4 If `x` is not a member of `C`, and M is a Scala2.x trait, nothing gets added. * * * 3.3 (done in `superCallOpt`) The call: * * super[M]. * * 3.4 (done in `setters`) For every concrete setter ` def x_=(y: T)` in M: * * def x_=(y: T) = () * * 4. (done in `transformTemplate` and `transformSym`) Drop all parameters from trait * constructors. * * 5. (done in `transformSym`) Drop ParamAccessor flag from all parameter accessors in traits. * * Conceptually, this is the second half of the previous mixin phase. It needs to run * after erasure because it copies references to possibly private inner classes and objects * into enclosing classes where they are not visible. This can only be done if all references * are symbolic. */ class Mixin extends MiniPhaseTransform with SymTransformer { thisTransform => import ast.tpd._ override def phaseName: String = "mixin" override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Erasure]) override def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = if (sym.is(Accessor, butNot = Deferred) && sym.owner.is(Trait)) { val sym1 = if (sym is Lazy) sym else sym.copySymDenotation(initFlags = sym.flags &~ ParamAccessor | Deferred) sym1.ensureNotPrivate } else if (sym.isConstructor && sym.owner.is(Trait)) sym.copySymDenotation( name = nme.TRAIT_CONSTRUCTOR, info = MethodType(Nil, sym.info.resultType)) else sym private def initializer(sym: Symbol)(implicit ctx: Context): TermSymbol = { if (sym is Lazy) sym else { val initName = InitializerName(sym.name.asTermName) sym.owner.info.decl(initName).symbol .orElse( ctx.newSymbol( sym.owner, initName, Protected | Synthetic | Method, sym.info, coord = sym.symbol.coord).enteredAfter(thisTransform)) } }.asTerm override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo) = { val cls = impl.symbol.owner.asClass val ops = new MixinOps(cls, thisTransform) import ops._ def traitDefs(stats: List[Tree]): List[Tree] = { val initBuf = new mutable.ListBuffer[Tree] stats.flatMap({ case stat: DefDef if stat.symbol.isGetter && !stat.rhs.isEmpty && !stat.symbol.is(Flags.Lazy) => // make initializer that has all effects of previous getter, // replace getter rhs with empty tree. val vsym = stat.symbol val isym = initializer(vsym) val rhs = Block( initBuf.toList.map(_.changeOwnerAfter(impl.symbol, isym, thisTransform)), stat.rhs.changeOwnerAfter(vsym, isym, thisTransform).wildcardToDefault) initBuf.clear() cpy.DefDef(stat)(rhs = EmptyTree) :: DefDef(isym, rhs) :: Nil case stat: DefDef if stat.symbol.isSetter => cpy.DefDef(stat)(rhs = EmptyTree) :: Nil case stat: DefTree => stat :: Nil case stat => initBuf += stat Nil }) ++ initBuf } /** Map constructor call to a pair of a supercall and a list of arguments * to be used as initializers of trait parameters if the target of the call * is a trait. */ def transformConstructor(tree: Tree): (Tree, List[Tree]) = { val Apply(sel @ Select(New(_), nme.CONSTRUCTOR), args) = tree val (callArgs, initArgs) = if (tree.symbol.owner.is(Trait)) (Nil, args) else (args, Nil) (superRef(tree.symbol, tree.pos).appliedToArgs(callArgs), initArgs) } val superCallsAndArgs = ( for (p <- impl.parents if p.symbol.isConstructor) yield p.symbol.owner -> transformConstructor(p) ).toMap val superCalls = superCallsAndArgs.mapValues(_._1) val initArgs = superCallsAndArgs.mapValues(_._2) def superCallOpt(baseCls: Symbol): List[Tree] = superCalls.get(baseCls) match { case Some(call) => if (defn.PhantomClasses.contains(baseCls)) Nil else call :: Nil case None => if (baseCls.is(NoInitsTrait) || defn.NoInitClasses.contains(baseCls)) Nil else { //println(i"synth super call ${baseCls.primaryConstructor}: ${baseCls.primaryConstructor.info}") transformFollowingDeep(superRef(baseCls.primaryConstructor).appliedToNone) :: Nil } } def was(sym: Symbol, flags: FlagSet) = ctx.atPhase(thisTransform) { implicit ctx => sym is flags } def traitInits(mixin: ClassSymbol): List[Tree] = { var argNum = 0 def nextArgument() = initArgs.get(mixin) match { case Some(arguments) => val result = arguments(argNum) argNum += 1 result case None => assert( impl.parents.forall(_.tpe.typeSymbol != mixin), i"missing parameters for $mixin from $impl should have been caught in typer") ctx.error( em"""parameterized $mixin is indirectly implemented, |needs to be implemented directly so that arguments can be passed""", cls.pos) EmptyTree } for (getter <- mixin.info.decls.toList if getter.isGetter && !was(getter, Deferred)) yield { val isScala2x = mixin.is(Scala2x) def default = Underscore(getter.info.resultType) def initial = transformFollowing(superRef(initializer(getter)).appliedToNone) /** A call to the implementation of `getter` in `mixin`'s implementation class */ def lazyGetterCall = { def canbeImplClassGetter(sym: Symbol) = sym.info.firstParamTypes match { case t :: Nil => t.isDirectRef(mixin) case _ => false } val implClassGetter = mixin.implClass.info.nonPrivateDecl(getter.name) .suchThat(canbeImplClassGetter).symbol ref(mixin.implClass).select(implClassGetter).appliedTo(This(cls)) } if (isCurrent(getter) || getter.name.is(ExpandedName)) { val rhs = if (was(getter, ParamAccessor)) nextArgument() else if (isScala2x) if (getter.is(Lazy, butNot = Module)) lazyGetterCall else if (getter.is(Module)) New(getter.info.resultType, List(This(cls))) else Underscore(getter.info.resultType) else initial // transformFollowing call is needed to make memoize & lazy vals run transformFollowing(DefDef(implementation(getter.asTerm), rhs)) } else if (isScala2x || was(getter, ParamAccessor)) EmptyTree else initial } } def setters(mixin: ClassSymbol): List[Tree] = for (setter <- mixin.info.decls.filter(setr => setr.isSetter && !was(setr, Deferred)).toList) yield transformFollowing(DefDef(implementation(setter.asTerm), unitLiteral.withPos(cls.pos))) cpy.Template(impl)( constr = if (cls.is(Trait)) cpy.DefDef(impl.constr)(vparamss = Nil :: Nil) else impl.constr, parents = impl.parents.map(p => TypeTree(p.tpe).withPos(p.pos)), body = if (cls is Trait) traitDefs(impl.body) else { val mixInits = mixins.flatMap { mixin => flatten(traitInits(mixin)) ::: superCallOpt(mixin) ::: setters(mixin) } superCallOpt(superCls) ::: mixInits ::: impl.body }) } }