diff options
author | Felix Mulder <felix.mulder@gmail.com> | 2016-11-02 11:08:28 +0100 |
---|---|---|
committer | Guillaume Martres <smarter@ubuntu.com> | 2016-11-22 01:35:07 +0100 |
commit | 8a61ff432543a29234193cd1f7c14abd3f3d31a0 (patch) | |
tree | a8147561d307af862c295cfc8100d271063bb0dd /compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala | |
parent | 6a455fe6da5ff9c741d91279a2dc6fe2fb1b472f (diff) | |
download | dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.tar.gz dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.tar.bz2 dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.zip |
Move compiler and compiler tests to compiler dir
Diffstat (limited to 'compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala')
-rw-r--r-- | compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala new file mode 100644 index 000000000..3fec47e9f --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -0,0 +1,362 @@ +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 ast.Trees._ +import SymUtils._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Phases.Phase +import util.Property +import collection.mutable + +/** 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_<OUTER>` to an outer path from `qual` to `C` */ + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = + if (tree.name.isOuterSelect) + outer.path(tree.tpe.widen.classSymbol, tree.qualifier).ensureConforms(tree.tpe) + else 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) + if (hasOuter(cls)) { + val newDefs = new mutable.ListBuffer[Tree] + 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(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.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.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).getOrElse(NoSymbol) + + /** 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 _ => + outerPrefix(tpe.underlying) + } + case tpe: TypeProxy => + outerPrefix(tpe.underlying) + } + + 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 @ MethodType(pnames, ptypes) = tp + mt.derivedMethodType( + nme.OUTER :: pnames, cls.owner.enclosingClass.typeRef :: ptypes, mt.resultType) + } 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(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 + } + + /** The path of outer accessors that references `toCls.this` starting from + * the context owner's this node. + */ + def path(toCls: Symbol, start: Tree = This(ctx.owner.enclosingClass.asClass)): Tree = try { + def loop(tree: Tree): 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 (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) + } + } + ctx.log(i"computing outerpath to $toCls from ${ctx.outersIterator.map(_.owner).toList}") + loop(start) + } 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 MethodType(outerName :: _, outerType :: _) = constr.info + val outerSym = ctx.newSymbol(constr, outerName, Param, outerType) + ValDef(outerSym) :: Nil + } + else Nil + } +} |