aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/dotty/tools/dotc/Compiler.scala5
-rw-r--r--src/dotty/tools/dotc/transform/OuterAccessors.scala164
-rw-r--r--src/dotty/tools/dotc/transform/SymUtils.scala3
-rw-r--r--test/dotc/tests.scala2
-rw-r--r--tests/pos/explicitOuter.scala48
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
+}