diff options
author | Martin Odersky <odersky@gmail.com> | 2014-08-31 18:08:32 +0200 |
---|---|---|
committer | Martin Odersky <odersky@gmail.com> | 2014-08-31 18:08:32 +0200 |
commit | 47b1d735e8323b2587aeb4b2a7ce5e214d9f1f8d (patch) | |
tree | acca2788c66b97ee4b20f8e29acdb6130b3832f9 | |
parent | 8f321f2afd0cd363492665b410a70476c8a4b751 (diff) | |
download | dotty-47b1d735e8323b2587aeb4b2a7ce5e214d9f1f8d.tar.gz dotty-47b1d735e8323b2587aeb4b2a7ce5e214d9f1f8d.tar.bz2 dotty-47b1d735e8323b2587aeb4b2a7ce5e214d9f1f8d.zip |
New phase: outerAccessors
The new phase replaces attachOuter. It creates outer accessors where needed
but does not yet define outer parameters or pass outer arguments.
It should run before pattern matcher, so that pattern matcher can access the
outer fields of scrutinees.
-rw-r--r-- | src/dotty/tools/dotc/Compiler.scala | 5 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/OuterAccessors.scala | 164 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/SymUtils.scala | 3 | ||||
-rw-r--r-- | test/dotc/tests.scala | 2 | ||||
-rw-r--r-- | tests/pos/explicitOuter.scala | 48 |
5 files changed, 218 insertions, 4 deletions
diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index ac2e91cec..060f1fcbd 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -54,15 +54,14 @@ class Compiler { new ElimRepeated, new ElimLocals), List(new ExtensionMethods), - List(new TailRec), + List(new TailRec, new OuterAccessors), List(new PatternMatcher, // new LazyValTranformContext().transformer, // disabled, awaiting fixes new Splitter), List(new ElimByName, new TypeTestsCasts, new InterceptedMethods, - new Literalize, - new AttachOuter), + new Literalize), List(new Erasure) ) diff --git a/src/dotty/tools/dotc/transform/OuterAccessors.scala b/src/dotty/tools/dotc/transform/OuterAccessors.scala new file mode 100644 index 000000000..5e6257e7f --- /dev/null +++ b/src/dotty/tools/dotc/transform/OuterAccessors.scala @@ -0,0 +1,164 @@ +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 ast.Trees._ +import SymUtils._ +import util.Attachment +import collection.mutable + +/** This phase decorates News and parent constructors of non-static inner classes + * with an attachment indicating the outer reference as a tree. This is necessary because + * outer prefixes are erased, and explicit outer runs only after erasure. + */ +class OuterAccessors extends MiniPhaseTransform with InfoTransformer { thisTransformer => + import OuterAccessors._ + import ast.tpd._ + + val Outer = new Attachment.Key[Tree] + + override def phaseName: String = "outerAccessors" + + override def treeTransformPhase = thisTransformer.next + + /** 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) => + val newDecls = decls.cloneScope + newOuterAccessors(cls).foreach(newDecls.enter) + tp.derivedClassInfo(decls = newDecls) + case _ => + tp + } + + /** A new outer accessor or param accessor */ + private def newOuterSym(owner: ClassSymbol, cls: ClassSymbol, name: TermName, flags: FlagSet)(implicit ctx: Context) = { + ctx.newSymbol(owner, name, Synthetic | flags, cls.owner.enclosingClass.typeRef, coord = cls.coord) + } + + /** 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 (cls.is(Trait)) Deferred else EmptyFlags + newOuterSym(owner, cls, cls.outerAccName, Final | Stable | deferredIfTrait) + } + + /** 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) + + /** 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) + + /** 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 outer accessors (decided by needsOuterIfReferenced), + * 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) && referencesOuter(cls, impl)) + newOuterAccessors(cls).foreach(_.enteredAfter(thisTransformer)) + if (hasOuter(cls)) { + val outerAcc = cls.info.member(cls.outerAccName).symbol.asTerm + val newDefs = new mutable.ListBuffer[Tree] + if (isTrait) + newDefs += DefDef(outerAcc, EmptyTree) + else { + val outerParamAcc = cls.info.decl(nme.OUTER).symbol.asTerm + newDefs += ValDef(outerParamAcc, EmptyTree) + newDefs += DefDef(outerAcc, ref(outerParamAcc)) + } + val parents1 = + for (parent <- impl.parents) yield { + val parentCls = parent.tpe.classSymbol.asClass + if (parentCls.is(Trait)) { + if (needsOuterIfReferenced(parentCls)) { + val outerAccImpl = newOuterAccessor(cls, parentCls).enteredAfter(thisTransformer) + newDefs += DefDef(outerAccImpl, singleton(outerPrefix(parent.tpe))) + } + 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 + } +} + +object OuterAccessors { + import ast.tpd._ + + private val LocalInstantiationSite = Module | Private + + /** 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(Interface) + ) + + /** 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 + */ + 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 */ + def hasLocalInstantiation(cls: ClassSymbol)(implicit ctx: Context): Boolean = + cls.owner.isTerm || cls.is(LocalInstantiationSite) + + /** Class has outer accessor. Can be called only after phase OuterAccessors. */ + def hasOuter(cls: ClassSymbol)(implicit ctx: Context): Boolean = + cls.info.decl(cls.outerAccName).exists + + /** Template `impl` of class `cls` references an outer this which goes to + * a class that is not a static owner. + */ + def referencesOuter(cls: ClassSymbol, impl: Template)(implicit ctx: Context): Boolean = + existsSubTreeOf(impl) { + case thisTree @ This(_) => + val thisCls = thisTree.symbol + thisCls != cls && !thisCls.isStaticOwner && cls.isContainedIn(thisCls) + case _ => + false + } + + /** The outer prefix implied by type `tpe` */ + 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) + } +}
\ No newline at end of file diff --git a/src/dotty/tools/dotc/transform/SymUtils.scala b/src/dotty/tools/dotc/transform/SymUtils.scala index 181072983..97f2a9f47 100644 --- a/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/src/dotty/tools/dotc/transform/SymUtils.scala @@ -6,6 +6,7 @@ import Types._ import Contexts._ import Symbols._ import Decorators._ +import Names._ import StdNames.nme import NameOps._ import language.implicitConversions @@ -21,4 +22,6 @@ class SymUtils(val self: Symbol) extends AnyVal { def isTypeTestOrCast(implicit ctx: Context): Boolean = self == defn.Any_asInstanceOf || self == defn.Any_isInstanceOf + + def outerAccName(implicit ctx: Context): TermName = nme.OUTER.expandedName(self) } diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 89dd42bc2..843489df9 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -13,7 +13,7 @@ class tests extends CompilerTest { // "-Yshow-suppressed-errors", "-pagewidth", "160") - implicit val defaultOptions = noCheckOptions ++ List("-Ycheck:all") + implicit val defaultOptions = noCheckOptions ++ List("-Ycheck:outerAcc,erasure") val twice = List("#runs", "2", "-YnoDoubleBindings", "-Ystop-before:terminal") val doErase = List("-Ystop-before:terminal") diff --git a/tests/pos/explicitOuter.scala b/tests/pos/explicitOuter.scala new file mode 100644 index 000000000..747f07e8f --- /dev/null +++ b/tests/pos/explicitOuter.scala @@ -0,0 +1,48 @@ +class Outer(elem: Int, val next: Outer) { + + trait InnerTrait { + def foo = elem + } + + class InnerClass extends next.InnerTrait { + def bar = elem + } + + class EmptyInnerClass { + def foo = 1 // still needs outer because it is not private + } + + def inner = { + trait InnerTrait { + def foo = elem + } + + class InnerClass extends next.InnerTrait { + def bar = elem + } + + class EmptyInnerClass { + def foo = 1 // does not need outer + } + + val ic = new InnerClass + println(ic.bar) + println(ic.foo) + val it = new InnerTrait {} + println(it.foo) + val ec = new EmptyInnerClass + } + +} + +object Test extends App { + + val o = new Outer(1, new Outer(2, null)) + val ic = new o.InnerClass + println(ic.bar) + println(ic.foo) + val it = new o.InnerTrait {} + println(it.foo) + val ec = new o.EmptyInnerClass + o.inner +} |