diff options
-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 +} |