diff options
Diffstat (limited to 'compiler/src/dotty/tools/dotc/transform/Mixin.scala')
-rw-r--r-- | compiler/src/dotty/tools/dotc/transform/Mixin.scala | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala new file mode 100644 index 000000000..27cfc835a --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -0,0 +1,257 @@ +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 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 + * + * <mods> def x(): T = expr + * + * to the pair of definitions: + * + * <mods> 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 + * + * <mods> def x_=(y: T) = () + * + * deferred by mapping it to + * + * <mods> 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 `<mods> def x(): T` in M, + * in order of textual occurrence, add + * + * <mods> 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 `<mods> 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: + * + * <mods> 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: + * + * <mods> 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].<init> + * + * 3.4 (done in `setters`) For every concrete setter `<mods> def x_=(y: T)` in M: + * + * <mods> 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.PhantomClasses.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.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 + }) + } +} |