package dotty.tools.dotc package transform import TreeTransforms._ import core.DenotTransformers._ import core.Symbols._ import core.Contexts._ import core.Types._ import core.Flags._ import core.Decorators._ import core.StdNames.nme import core.Names._ import core.NameOps._ import core.NameKinds.OuterSelectName import ast.Trees._ import SymUtils._ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Phases.Phase import util.Property import collection.mutable import scala.annotation.tailrec /** This phase adds outer accessors to classes and traits that need them. * Compared to Scala 2.x, it tries to minimize the set of classes * that take outer accessors by scanning class implementations for * outer references. * * The following things are delayed until erasure and are performed * by class OuterOps: * * - add outer parameters to constructors * - pass outer arguments in constructor calls * * replacement of outer this by outer paths is done in Erasure. * needs to run after pattern matcher as it can add outer checks and force creation of $outer */ class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransformer => import ExplicitOuter._ import ast.tpd._ val Outer = new Property.Key[Tree] override def phaseName: String = "explicitOuter" /** List of names of phases that should have finished their processing of all compilation units * before this phase starts */ override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[PatternMatcher]) /** Add outer accessors if a class always needs an outer pointer */ override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { case tp @ ClassInfo(_, cls, _, decls, _) if needsOuterAlways(cls) && !sym.is(JavaDefined) => val newDecls = decls.cloneScope newOuterAccessors(cls).foreach(newDecls.enter) tp.derivedClassInfo(decls = newDecls) case _ => tp } override def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = sym.isClass /** Convert a selection of the form `qual.C_` to an outer path from `qual` to `C` */ override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = tree.name match { case OuterSelectName(_, nhops) => outer.path(start = tree.qualifier, count = nhops).ensureConforms(tree.tpe) case _ => tree } /** First, add outer accessors if a class does not have them yet and it references an outer this. * If the class has outer accessors, implement them. * Furthermore, if a parent trait might have an outer accessor, * provide an implementation for the outer accessor by computing the parent's * outer from the parent type prefix. If the trait ends up not having an outer accessor * after all, the implementation is redundant, but does not harm. * The same logic is not done for non-trait parent classes because for them the outer * pointer is passed in the super constructor, which will be implemented later in * a separate phase which needs to run after erasure. However, we make sure here * that the super class constructor is indeed a New, and not just a type. */ override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo): Tree = { val cls = ctx.owner.asClass val isTrait = cls.is(Trait) if (needsOuterIfReferenced(cls) && !needsOuterAlways(cls) && impl.existsSubTree(referencesOuter(cls, _))) ensureOuterAccessors(cls) val clsHasOuter = hasOuter(cls) if (clsHasOuter || cls.mixins.exists(needsOuterIfReferenced)) { val newDefs = new mutable.ListBuffer[Tree] if (clsHasOuter) { if (isTrait) newDefs += DefDef(outerAccessor(cls).asTerm, EmptyTree) else { val outerParamAcc = outerParamAccessor(cls) newDefs += ValDef(outerParamAcc, EmptyTree) newDefs += DefDef(outerAccessor(cls).asTerm, ref(outerParamAcc)) } } for (parentTrait <- cls.mixins) { if (needsOuterIfReferenced(parentTrait)) { val parentTp = cls.denot.thisType.baseTypeRef(parentTrait) val outerAccImpl = newOuterAccessor(cls, parentTrait).enteredAfter(thisTransformer) newDefs += DefDef(outerAccImpl, singleton(fixThis(outerPrefix(parentTp)))) } } val parents1 = for (parent <- impl.parents) yield { val parentCls = parent.tpe.classSymbol.asClass if (parentCls.is(Trait)) { parent } else parent match { // ensure class parent is a constructor case parent: TypeTree => New(parent.tpe, Nil).withPos(impl.pos) case _ => parent } } cpy.Template(impl)(parents = parents1, body = impl.body ++ newDefs) } else impl } override def transformClosure(tree: Closure)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = { if (tree.tpt ne EmptyTree) { val cls = tree.tpt.asInstanceOf[TypeTree].tpe.classSymbol if (cls.exists && hasOuter(cls.asClass)) ctx.error("Not a single abstract method type, requires an outer pointer", tree.pos) } tree } } object ExplicitOuter { import ast.tpd._ /** Ensure that class `cls` has outer accessors */ def ensureOuterAccessors(cls: ClassSymbol)(implicit ctx: Context): Unit = { //todo: implementing #165 would simplify this logic val prevPhase = ctx.phase.prev assert(prevPhase.id <= ctx.explicitOuterPhase.id, "can add $outer symbols only before ExplicitOuter") assert(prevPhase.isInstanceOf[DenotTransformer], "adding outerAccessors requires being DenotTransformer") if (!hasOuter(cls)) { newOuterAccessors(cls).foreach(_.enteredAfter(prevPhase.asInstanceOf[DenotTransformer])) } } /** The outer accessor and potentially outer param accessor needed for class `cls` */ private def newOuterAccessors(cls: ClassSymbol)(implicit ctx: Context) = newOuterAccessor(cls, cls) :: (if (cls is Trait) Nil else newOuterParamAccessor(cls) :: Nil) /** A new outer accessor or param accessor */ private def newOuterSym(owner: ClassSymbol, cls: ClassSymbol, name: TermName, flags: FlagSet)(implicit ctx: Context) = { val target = cls.owner.enclosingClass.typeRef val info = if (flags.is(Method)) ExprType(target) else target ctx.withPhaseNoEarlier(ctx.explicitOuterPhase.next) // outer accessors are entered at explicitOuter + 1, should not be defined before. .newSymbol(owner, name, Synthetic | flags, info, coord = cls.coord) } /** A new param accessor for the outer field in class `cls` */ private def newOuterParamAccessor(cls: ClassSymbol)(implicit ctx: Context) = newOuterSym(cls, cls, nme.OUTER, Private | ParamAccessor) /** A new outer accessor for class `cls` which is a member of `owner` */ private def newOuterAccessor(owner: ClassSymbol, cls: ClassSymbol)(implicit ctx: Context) = { val deferredIfTrait = if (owner.is(Trait)) Deferred else EmptyFlags val outerAccIfOwn = if (owner == cls) OuterAccessor else EmptyFlags newOuterSym(owner, cls, outerAccName(cls), Final | Method | Stable | outerAccIfOwn | deferredIfTrait) } private def outerAccName(cls: ClassSymbol)(implicit ctx: Context): TermName = nme.OUTER.expandedName(cls) /** Class needs an outer pointer, provided there is a reference to an outer this in it. */ def needsOuterIfReferenced(cls: ClassSymbol)(implicit ctx: Context): Boolean = !(cls.isStatic || cls.owner.enclosingClass.isStaticOwner || cls.is(PureInterface) ) /** Class unconditionally needs an outer pointer. This is the case if * the class needs an outer pointer if referenced and one of the following holds: * - we might not know at all instantiation sites whether outer is referenced or not * - we need to potentially pass along outer to a parent class or trait */ private def needsOuterAlways(cls: ClassSymbol)(implicit ctx: Context): Boolean = needsOuterIfReferenced(cls) && (!hasLocalInstantiation(cls) || // needs outer because we might not know whether outer is referenced or not cls.mixins.exists(needsOuterIfReferenced) || // needs outer for parent traits cls.classInfo.parents.exists(parent => // needs outer to potentially pass along to parent needsOuterIfReferenced(parent.classSymbol.asClass))) /** Class is always instantiated in the compilation unit where it is defined */ private def hasLocalInstantiation(cls: ClassSymbol)(implicit ctx: Context): Boolean = // scala2x modules always take an outer pointer(as of 2.11) // dotty modules are always locally instantiated cls.owner.isTerm || cls.is(Private) || cls.is(Module, butNot = Scala2x) /** The outer parameter accessor of cass `cls` */ private def outerParamAccessor(cls: ClassSymbol)(implicit ctx: Context): TermSymbol = cls.info.decl(nme.OUTER).symbol.asTerm /** The outer accessor of class `cls`. To find it is a bit tricky. The * class might have been moved with new owners between ExplicitOuter and Erasure, * where the method is also called. For instance, it might have been part * of a by-name argument, and therefore be moved under a closure method * by ElimByName. In that case looking up the method again at Erasure with the * fully qualified name `outerAccName` will fail, because the `outerAccName`'s * result is phase dependent. In that case we use a backup strategy where we search all * definitions in the class to find the one with the OuterAccessor flag. */ def outerAccessor(cls: ClassSymbol)(implicit ctx: Context): Symbol = if (cls.isStatic) NoSymbol // fast return to avoid scanning package decls else cls.info.member(outerAccName(cls)).suchThat(_ is OuterAccessor).symbol orElse cls.info.decls.find(_ is OuterAccessor) /** Class has an outer accessor. Can be called only after phase ExplicitOuter. */ private def hasOuter(cls: ClassSymbol)(implicit ctx: Context): Boolean = needsOuterIfReferenced(cls) && outerAccessor(cls).exists /** Class constructor takes an outer argument. Can be called only after phase ExplicitOuter. */ private def hasOuterParam(cls: ClassSymbol)(implicit ctx: Context): Boolean = !cls.is(Trait) && needsOuterIfReferenced(cls) && outerAccessor(cls).exists /** Tree references an outer class of `cls` which is not a static owner. */ def referencesOuter(cls: Symbol, tree: Tree)(implicit ctx: Context): Boolean = { def isOuterSym(sym: Symbol) = !sym.isStaticOwner && cls.isProperlyContainedIn(sym) def isOuterRef(ref: Type): Boolean = ref match { case ref: ThisType => isOuterSym(ref.cls) case ref: TermRef => if (ref.prefix ne NoPrefix) !ref.symbol.isStatic && isOuterRef(ref.prefix) else ( (ref.symbol is Hoistable) && // ref.symbol will be placed in enclosing class scope by LambdaLift, so it might need // an outer path then. isOuterSym(ref.symbol.owner.enclosingClass) || // If not hoistable, ref.symbol will get a proxy in immediately enclosing class. If this properly // contains the current class, it needs an outer path. // If the symbol is hoistable, it might have free variables for which the same // reasoning applies. See pos/i1664.scala ctx.owner.enclosingClass.owner.enclosingClass.isContainedIn(ref.symbol.owner) ) case _ => false } def hasOuterPrefix(tp: Type) = tp match { case TypeRef(prefix, _) => isOuterRef(prefix) case _ => false } tree match { case _: This | _: Ident => isOuterRef(tree.tpe) case nw: New => val newCls = nw.tpe.classSymbol isOuterSym(newCls.owner.enclosingClass) || hasOuterPrefix(nw.tpe) || newCls.owner.isTerm && cls.isProperlyContainedIn(newCls) // newCls might get proxies for free variables. If current class is // properly contained in newCls, it needs an outer path to newCls access the // proxies and forward them to the new instance. case _ => false } } private final val Hoistable = Method | Lazy | Module /** The outer prefix implied by type `tpe` */ private def outerPrefix(tpe: Type)(implicit ctx: Context): Type = tpe match { case tpe: TypeRef => tpe.symbol match { case cls: ClassSymbol => if (tpe.prefix eq NoPrefix) cls.owner.enclosingClass.thisType else tpe.prefix case _ => // Need to be careful to dealias before erasure, otherwise we lose prefixes. outerPrefix(tpe.underlying(ctx.withPhaseNoLater(ctx.erasurePhase))) } case tpe: TypeProxy => outerPrefix(tpe.underlying) } /** It's possible (i1755.scala gives an example) that the type * given by outerPrefix contains a This-reference to a module outside * the context where that module is defined. This needs to be translated * to an access to the module object from the enclosing class or object. * * This solution is a bit of a hack; it would be better to avoid * such references to the This of a module from outside the module * in the first place. I was not yet able to find out how such references * arise and how to avoid them. */ private def fixThis(tpe: Type)(implicit ctx: Context): Type = tpe match { case tpe: ThisType if tpe.cls.is(Module) && !ctx.owner.isContainedIn(tpe.cls) => fixThis(TermRef(tpe.cls.owner.thisType, tpe.cls.sourceModule.asTerm)) case tpe: TermRef => tpe.derivedSelect(fixThis(tpe.prefix)) case _ => tpe } def outer(implicit ctx: Context): OuterOps = new OuterOps(ctx) /** The operations in this class * - add outer parameters * - pass outer arguments to these parameters * - replace outer this references by outer paths. * They are called from erasure. There are two constraints which * suggest these operations should be done in erasure. * - Replacing this references with outer paths loses aliasing information, * so programs will not typecheck with unerased types unless a lot of type * refinements are added. Therefore, outer paths should be computed no * earlier than erasure. * - outer parameters should not show up in signatures, so again * they cannot be added before erasure. * - outer arguments need access to outer parameters as well as to the * original type prefixes of types in New expressions. These prefixes * get erased during erasure. Therefore, outer arguments have to be passed * no later than erasure. */ class OuterOps(val ictx: Context) extends AnyVal { private implicit def ctx: Context = ictx /** If `cls` has an outer parameter add one to the method type `tp`. */ def addParam(cls: ClassSymbol, tp: Type): Type = if (hasOuterParam(cls)) { val mt @ MethodTpe(pnames, ptypes, restpe) = tp mt.derivedLambdaType( nme.OUTER :: pnames, cls.owner.enclosingClass.typeRef :: ptypes, restpe) } else tp /** If function in an apply node is a constructor that needs to be passed an * outer argument, the singleton list with the argument, otherwise Nil. */ def args(fun: Tree): List[Tree] = { if (fun.symbol.isConstructor) { val cls = fun.symbol.owner.asClass def outerArg(receiver: Tree): Tree = receiver match { case New(_) | Super(_, _) => singleton(fixThis(outerPrefix(receiver.tpe))) case This(_) => ref(outerParamAccessor(cls)) // will be rewired to outer argument of secondary constructor in phase Constructors case TypeApply(Select(r, nme.asInstanceOf_), args) => outerArg(r) // cast was inserted, skip } if (hasOuterParam(cls)) methPart(fun) match { case Select(receiver, _) => outerArg(receiver).withPos(fun.pos) :: Nil } else Nil } else Nil } /** A path of outer accessors starting from node `start`. `start` defaults to the * context owner's this node. There are two alternative conditions that determine * where the path ends: * * - if the initial `count` parameter is non-negative: where the number of * outer accessors reaches count. * - if the initial `count` parameter is negative: where the class symbol of * the type of the reached tree matches `toCls`. */ def path(start: Tree = This(ctx.owner.lexicallyEnclosingClass.asClass), toCls: Symbol = NoSymbol, count: Int = -1): Tree = try { @tailrec def loop(tree: Tree, count: Int): Tree = { val treeCls = tree.tpe.widen.classSymbol val outerAccessorCtx = ctx.withPhaseNoLater(ctx.lambdaLiftPhase) // lambdalift mangles local class names, which means we cannot reliably find outer acessors anymore ctx.log(i"outer to $toCls of $tree: ${tree.tpe}, looking for ${outerAccName(treeCls.asClass)(outerAccessorCtx)} in $treeCls") if (count == 0 || count < 0 && treeCls == toCls) tree else { val acc = outerAccessor(treeCls.asClass)(outerAccessorCtx) assert(acc.exists, i"failure to construct path from ${ctx.owner.ownersIterator.toList}%/% to `this` of ${toCls.showLocated};\n${treeCls.showLocated} does not have an outer accessor") loop(tree.select(acc).ensureApplied, count - 1) } } ctx.log(i"computing outerpath to $toCls from ${ctx.outersIterator.map(_.owner).toList}") loop(start, count) } catch { case ex: ClassCastException => throw new ClassCastException(i"no path exists from ${ctx.owner.enclosingClass} to $toCls") } /** The outer parameter definition of a constructor if it needs one */ def paramDefs(constr: Symbol): List[ValDef] = if (constr.isConstructor && hasOuterParam(constr.owner.asClass)) { val MethodTpe(outerName :: _, outerType :: _, _) = constr.info val outerSym = ctx.newSymbol(constr, outerName, Param, outerType) ValDef(outerSym) :: Nil } else Nil } }