aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2014-09-06 08:47:08 +0200
committerMartin Odersky <odersky@gmail.com>2014-09-06 08:47:08 +0200
commite1040935cbcc1d767933c38a141372538ef63ac2 (patch)
treed2f05fa2319fe80a76f660e043d42a06d5e9ba8a
parent44bdec1a44db7ac880183e0c70b5f5668048961e (diff)
downloaddotty-e1040935cbcc1d767933c38a141372538ef63ac2.tar.gz
dotty-e1040935cbcc1d767933c38a141372538ef63ac2.tar.bz2
dotty-e1040935cbcc1d767933c38a141372538ef63ac2.zip
Better tests and more fixes for ExplicitOuter
Now also testing that after erasure no outer this exists. Tests suit now includes calls to local classes and methods which need an outer pointer, as well as passing an outer pointer along a secondary constructor.
-rw-r--r--src/dotty/tools/dotc/core/SymDenotations.scala5
-rw-r--r--src/dotty/tools/dotc/transform/Erasure.scala2
-rw-r--r--src/dotty/tools/dotc/transform/ExplicitOuter.scala63
-rw-r--r--src/dotty/tools/dotc/transform/TreeChecker.scala10
-rw-r--r--tests/pos/explicitOuter.scala14
5 files changed, 72 insertions, 22 deletions
diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala
index e028c492c..a509881fb 100644
--- a/src/dotty/tools/dotc/core/SymDenotations.scala
+++ b/src/dotty/tools/dotc/core/SymDenotations.scala
@@ -636,6 +636,11 @@ object SymDenotations {
/** The class containing this denotation.
* If this denotation is already a class, return itself
+ * Definitions flagged with InSuperCall are treated specially.
+ * Their enclosing class is not the lexically enclosing class,
+ * but in turn the enclosing class of the latter. This reflects
+ * the context created by `Context#superCallContext`, `Contect#thisCallArgContext`
+ * for these definitions.
*/
final def enclosingClass(implicit ctx: Context): Symbol = {
def enclClass(d: SymDenotation): Symbol =
diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala
index 82acb482d..fe5e516bf 100644
--- a/src/dotty/tools/dotc/transform/Erasure.scala
+++ b/src/dotty/tools/dotc/transform/Erasure.scala
@@ -309,7 +309,7 @@ object Erasure {
case fun1 =>
fun1.tpe.widen match {
case mt: MethodType =>
- val outers = outer.args(fun1) map untpd.TypedSplice
+ val outers = outer.args(fun.asInstanceOf[tpd.Tree]) // can't use fun1 here because its type is already erased
val args1 = (outers ::: args ++ protoArgs(pt)).zipWithConserve(mt.paramTypes)(typedExpr)
untpd.cpy.Apply(tree)(fun1, args1) withType mt.resultType
case _ =>
diff --git a/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/src/dotty/tools/dotc/transform/ExplicitOuter.scala
index 0a1c94dd1..d13598819 100644
--- a/src/dotty/tools/dotc/transform/ExplicitOuter.scala
+++ b/src/dotty/tools/dotc/transform/ExplicitOuter.scala
@@ -18,9 +18,8 @@ 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 and also tries to minimize the number
- * of objects referred to by outer accessors. This helps prevent space
- * leaks.
+ * that take outer accessors by scanning class implementations for
+ * outer references.
*
* The following things are delayed until erasure and are performed
* by class OuterOps:
@@ -43,7 +42,7 @@ class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransf
override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match {
case tp @ ClassInfo(_, cls, _, decls, _) if needsOuterAlways(cls) =>
val newDecls = decls.cloneScope
- newExplicitOuter(cls).foreach(newDecls.enter)
+ newOuterAccessors(cls).foreach(newDecls.enter)
tp.derivedClassInfo(decls = newDecls)
case _ =>
tp
@@ -67,12 +66,12 @@ class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransf
newOuterSym(cls, cls, nme.OUTER, Private | ParamAccessor)
/** The outer accessor and potentially outer param accessor needed for class `cls` */
- private def newExplicitOuter(cls: ClassSymbol)(implicit ctx: Context) =
+ 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),
+ * 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.
@@ -84,8 +83,10 @@ class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransf
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))
- newExplicitOuter(cls).foreach(_.enteredAfter(thisTransformer))
+ if (needsOuterIfReferenced(cls) &&
+ !needsOuterAlways(cls) &&
+ existsSubTreeOf(impl)(referencesOuter(cls, _)))
+ newOuterAccessors(cls).foreach(_.enteredAfter(thisTransformer))
if (hasOuter(cls)) {
val newDefs = new mutable.ListBuffer[Tree]
if (isTrait)
@@ -150,7 +151,15 @@ object ExplicitOuter {
private def outerParamAccessor(cls: ClassSymbol)(implicit ctx: Context): TermSymbol =
cls.info.decl(nme.OUTER).symbol.asTerm
- /** The outer access of class `cls` */
+ /** 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.
+ */
private def outerAccessor(cls: ClassSymbol)(implicit ctx: Context): Symbol =
cls.info.member(outerAccName(cls)).suchThat(_ is OuterAccessor).symbol orElse
cls.info.decls.find(_ is OuterAccessor).getOrElse(NoSymbol)
@@ -159,17 +168,28 @@ object ExplicitOuter {
private def hasOuter(cls: ClassSymbol)(implicit ctx: Context): Boolean =
needsOuterIfReferenced(cls) && outerAccessor(cls).exists
- /** Template `impl` of class `cls` references an outer this which goes to
- * a class that is not a static owner.
+ /** Tree references a an outer class of `cls` which is not a static owner.
*/
- private def referencesOuter(cls: ClassSymbol, impl: Template)(implicit ctx: Context): Boolean =
- existsSubTreeOf(impl) {
+ def referencesOuter(cls: Symbol, tree: Tree)(implicit ctx: Context): Boolean = {
+ def isOuter(sym: Symbol) =
+ sym != cls && !sym.isStaticOwner && cls.isContainedIn(sym)
+ tree match {
case thisTree @ This(_) =>
- val thisCls = thisTree.symbol
- thisCls != cls && !thisCls.isStaticOwner && cls.isContainedIn(thisCls)
+ isOuter(thisTree.symbol)
+ case id: Ident =>
+ id.tpe match {
+ case ref @ TermRef(NoPrefix, _) =>
+ ref.symbol.is(Method) && isOuter(id.symbol.owner.enclosingClass)
+ // methods will be placed in enclosing class scope by LambdaLift, so they will get
+ // an outer path then.
+ case _ => false
+ }
+ case nw: New =>
+ isOuter(nw.tpe.classSymbol.owner.enclosingClass)
case _ =>
- false
+ false
}
+ }
/** The outer prefix implied by type `tpe` */
private def outerPrefix(tpe: Type)(implicit ctx: Context): Type = tpe match {
@@ -222,13 +242,16 @@ object ExplicitOuter {
if (fun.symbol.isConstructor) {
val cls = fun.symbol.owner.asClass
def outerArg(receiver: Tree): Tree = receiver match {
- case New(tpt) => TypeTree(outerPrefix(tpt.tpe)).withPos(tpt.pos)
- case This(_) => ref(outerParamAccessor(cls))
- case TypeApply(Select(r, nme.asInstanceOf_), args) => outerArg(r) // cast was inserted, skip
+ case New(tpt) =>
+ singleton(outerPrefix(tpt.tpe))
+ case This(_) =>
+ ref(outerParamAccessor(cls)) // will be rewried to outer argument of secondary constructor in phase Constructors
+ case TypeApply(Select(r, nme.asInstanceOf_), args) =>
+ outerArg(r) // cast was inserted, skip
}
if (hasOuter(cls))
methPart(fun) match {
- case Select(receiver, _) => outerArg(receiver) :: Nil
+ case Select(receiver, _) => outerArg(receiver).withPos(fun.pos) :: Nil
}
else Nil
} else Nil
diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala
index 5852eba9a..5847796b3 100644
--- a/src/dotty/tools/dotc/transform/TreeChecker.scala
+++ b/src/dotty/tools/dotc/transform/TreeChecker.scala
@@ -67,7 +67,15 @@ class TreeChecker {
assert(isSubType(tree1.tpe, tree.typeOpt), divergenceMsg(tree1.tpe, tree.typeOpt))
tree1
}
- if (ctx.erasedTypes) assertErased(res)
+ if (ctx.erasedTypes) {
+ assertErased(res)
+ res match {
+ case res: This =>
+ assert(!ExplicitOuter.referencesOuter(ctx.owner.enclosingClass, res),
+ i"Reference to $res from ${ctx.owner.showLocated}")
+ case _ =>
+ }
+ }
res
}
diff --git a/tests/pos/explicitOuter.scala b/tests/pos/explicitOuter.scala
index a5fb1dd70..44b441956 100644
--- a/tests/pos/explicitOuter.scala
+++ b/tests/pos/explicitOuter.scala
@@ -5,6 +5,7 @@ class Outer(elem: Int, val next: Outer) {
}
class InnerClass(x: Int) extends next.InnerTrait {
+ def this() = this(3)
def bar = elem + x
}
@@ -18,6 +19,7 @@ class Outer(elem: Int, val next: Outer) {
}
class InnerClass(x: Int) extends next.InnerTrait {
+ def this() = this(3)
def bar = elem + x
}
@@ -33,6 +35,18 @@ class Outer(elem: Int, val next: Outer) {
val ec = new EmptyInnerClass
}
+ def inner2 = {
+ class C {
+ val x = elem
+ }
+ class D {
+ new C
+ }
+ class E {
+ f()
+ }
+ def f() = ()
+ }
}
object Test extends App {