All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dotty.tools.dotc.transform.Mixin.scala Maven / Gradle / Ivy

The newest version!
package dotty.tools.dotc
package transform

import core._
import MegaPhase._
import Contexts.Context
import Flags._
import SymUtils._
import Symbols._
import SymDenotations._
import Types._
import Decorators._
import DenotTransformers._
import StdNames._
import NameKinds._
import ast.Trees._
import collection.mutable

object Mixin {
  val name: String = "mixin"
}

/** 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 is a lazy val,
 *
 *                 lazy val x: T = super[M].x
 *
 *              3.2.2 If `x` is also a member of `C`, and M is a Dotty trait,
 *
 *                 def x(): T = super[M].initial$x()
 *
 *              3.2.3 If `x` is also a member of `C`, and M is a Scala 2.x trait:
 *
 *                 def x(): T = _
 *
 *              3.2.4 If `x` is not a member of `C`, and M is a Dotty trait:
 *
 *                super[M].initial$x()
 *
 *              3.2.5 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) = ()
 *
 *          3.5 (done in `mixinForwarders`) For every method
 *          ` def f[Ts](ps1)...(psN): U` imn M` that needs to be disambiguated:
 *
 *                 def f[Ts](ps1)...(psN): U = super[M].f[Ts](ps1)...(psN)
 *
 *          A method in M needs to be disambiguated if it is concrete, not overridden in C,
 *          and if it overrides another concrete method.
 *
 *   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 MiniPhase with SymTransformer { thisPhase =>
  import ast.tpd._

  override def phaseName: String = Mixin.name

  override def relaxedTypingInGroup: Boolean = true
    // Because it changes number of parameters in trait initializers

  override def runsAfter: Set[String] = Set(Erasure.name)

  override def changesMembers: Boolean = true  // the phase adds implementions of mixin accessors

  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, butNot = Scala2x))
      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.coord).enteredAfter(thisPhase))
    }
  }.asTerm

  override def transformTemplate(impl: Template)(implicit ctx: Context): Template = {
    val cls = impl.symbol.owner.asClass
    val ops = new MixinOps(cls, thisPhase)
    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(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, thisPhase)),
            stat.rhs.changeOwnerAfter(vsym, isym, thisPhase).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]) = tree match {
      case Block(stats, expr) =>
        val (scall, inits) = transformConstructor(expr)
        (cpy.Block(tree)(stats, scall), inits)
      case _ =>
        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.span).appliedToArgs(callArgs), initArgs)
    }

    val superCallsAndArgs = (
      for (p <- impl.parents; constr = stripBlock(p).symbol if constr.isConstructor)
      yield constr.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.NotRuntimeClasses.contains(baseCls) || baseCls.isAllOf(NoInitsTrait)) Nil
        else call :: Nil
      case None =>
        if (baseCls.isAllOf(NoInitsTrait) || defn.NoInitClasses.contains(baseCls) || defn.isFunctionClass(baseCls)) Nil
        else {
          //println(i"synth super call ${baseCls.primaryConstructor}: ${baseCls.primaryConstructor.info}")
          transformFollowingDeep(superRef(baseCls.primaryConstructor).appliedToNone) :: Nil
        }
    }

    def wasOneOf(sym: Symbol, flags: FlagSet) =
      ctx.atPhase(thisPhase) { implicit ctx => sym.isOneOf(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.sourcePos)
          EmptyTree
      }

      for (getter <- mixin.info.decls.toList if getter.isGetter && !wasOneOf(getter, Deferred)) yield {
        val isScala2x = mixin.is(Scala2x)
        def default = Underscore(getter.info.resultType)
        def initial = transformFollowing(superRef(initializer(getter)).appliedToNone)

        if (isCurrent(getter) || getter.name.is(ExpandedName)) {
          val rhs =
            if (wasOneOf(getter, ParamAccessor))
              nextArgument()
            else if (isScala2x) {
              if (getter.is(Lazy, butNot = Module))
                initial
              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(mkForwarderSym(getter.asTerm), rhs))
        }
        else if (isScala2x || wasOneOf(getter, ParamAccessor | Lazy)) EmptyTree
        else initial
      }
    }

    def setters(mixin: ClassSymbol): List[Tree] =
      for (setter <- mixin.info.decls.filter(setr => setr.isSetter && !wasOneOf(setr, Deferred)))
        yield transformFollowing(DefDef(mkForwarderSym(setter.asTerm), unitLiteral.withSpan(cls.span)))

    def mixinForwarders(mixin: ClassSymbol): List[Tree] =
      for (meth <- mixin.info.decls.toList if needsMixinForwarder(meth))
      yield {
        util.Stats.record("mixin forwarders")
        transformFollowing(polyDefDef(mkForwarderSym(meth.asTerm, Bridge), forwarderRhsFn(meth)))
      }


    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).withSpan(p.span)),
      body =
        if (cls.is(Trait)) traitDefs(impl.body)
        else {
          val mixInits = mixins.flatMap { mixin =>
            flatten(traitInits(mixin)) ::: superCallOpt(mixin) ::: setters(mixin) ::: mixinForwarders(mixin)
          }
          superCallOpt(superCls) ::: mixInits ::: impl.body
        })
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy