Skip to content

Commit e104093

Browse files
committed
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.
1 parent 44bdec1 commit e104093

File tree

5 files changed

+72
-22
lines changed

5 files changed

+72
-22
lines changed

src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,11 @@ object SymDenotations {
636636

637637
/** The class containing this denotation.
638638
* If this denotation is already a class, return itself
639+
* Definitions flagged with InSuperCall are treated specially.
640+
* Their enclosing class is not the lexically enclosing class,
641+
* but in turn the enclosing class of the latter. This reflects
642+
* the context created by `Context#superCallContext`, `Contect#thisCallArgContext`
643+
* for these definitions.
639644
*/
640645
final def enclosingClass(implicit ctx: Context): Symbol = {
641646
def enclClass(d: SymDenotation): Symbol =

src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ object Erasure {
309309
case fun1 =>
310310
fun1.tpe.widen match {
311311
case mt: MethodType =>
312-
val outers = outer.args(fun1) map untpd.TypedSplice
312+
val outers = outer.args(fun.asInstanceOf[tpd.Tree]) // can't use fun1 here because its type is already erased
313313
val args1 = (outers ::: args ++ protoArgs(pt)).zipWithConserve(mt.paramTypes)(typedExpr)
314314
untpd.cpy.Apply(tree)(fun1, args1) withType mt.resultType
315315
case _ =>

src/dotty/tools/dotc/transform/ExplicitOuter.scala

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ import collection.mutable
1818

1919
/** This phase adds outer accessors to classes and traits that need them.
2020
* Compared to Scala 2.x, it tries to minimize the set of classes
21-
* that take outer accessors and also tries to minimize the number
22-
* of objects referred to by outer accessors. This helps prevent space
23-
* leaks.
21+
* that take outer accessors by scanning class implementations for
22+
* outer references.
2423
*
2524
* The following things are delayed until erasure and are performed
2625
* by class OuterOps:
@@ -43,7 +42,7 @@ class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransf
4342
override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match {
4443
case tp @ ClassInfo(_, cls, _, decls, _) if needsOuterAlways(cls) =>
4544
val newDecls = decls.cloneScope
46-
newExplicitOuter(cls).foreach(newDecls.enter)
45+
newOuterAccessors(cls).foreach(newDecls.enter)
4746
tp.derivedClassInfo(decls = newDecls)
4847
case _ =>
4948
tp
@@ -67,12 +66,12 @@ class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransf
6766
newOuterSym(cls, cls, nme.OUTER, Private | ParamAccessor)
6867

6968
/** The outer accessor and potentially outer param accessor needed for class `cls` */
70-
private def newExplicitOuter(cls: ClassSymbol)(implicit ctx: Context) =
69+
private def newOuterAccessors(cls: ClassSymbol)(implicit ctx: Context) =
7170
newOuterAccessor(cls, cls) :: (if (cls is Trait) Nil else newOuterParamAccessor(cls) :: Nil)
7271

7372
/** First, add outer accessors if a class does not have them yet and it references an outer this.
7473
* If the class has outer accessors, implement them.
75-
* Furthermore, if a parent trait might have outer accessors (decided by needsOuterIfReferenced),
74+
* Furthermore, if a parent trait might have an outer accessor,
7675
* provide an implementation for the outer accessor by computing the parent's
7776
* outer from the parent type prefix. If the trait ends up not having an outer accessor
7877
* after all, the implementation is redundant, but does not harm.
@@ -84,8 +83,10 @@ class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransf
8483
override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo): Tree = {
8584
val cls = ctx.owner.asClass
8685
val isTrait = cls.is(Trait)
87-
if (needsOuterIfReferenced(cls) && !needsOuterAlways(cls) && referencesOuter(cls, impl))
88-
newExplicitOuter(cls).foreach(_.enteredAfter(thisTransformer))
86+
if (needsOuterIfReferenced(cls) &&
87+
!needsOuterAlways(cls) &&
88+
existsSubTreeOf(impl)(referencesOuter(cls, _)))
89+
newOuterAccessors(cls).foreach(_.enteredAfter(thisTransformer))
8990
if (hasOuter(cls)) {
9091
val newDefs = new mutable.ListBuffer[Tree]
9192
if (isTrait)
@@ -150,7 +151,15 @@ object ExplicitOuter {
150151
private def outerParamAccessor(cls: ClassSymbol)(implicit ctx: Context): TermSymbol =
151152
cls.info.decl(nme.OUTER).symbol.asTerm
152153

153-
/** The outer access of class `cls` */
154+
/** The outer accessor of class `cls`. To find it is a bit tricky. The
155+
* class might have been moved with new owners between ExplicitOuter and Erasure,
156+
* where the method is also called. For instance, it might have been part
157+
* of a by-name argument, and therefore be moved under a closure method
158+
* by ElimByName. In that case looking up the method again at Erasure with the
159+
* fully qualified name `outerAccName` will fail, because the `outerAccName`'s
160+
* result is phase dependent. In that case we use a backup strategy where we search all
161+
* definitions in the class to find the one with the OuterAccessor flag.
162+
*/
154163
private def outerAccessor(cls: ClassSymbol)(implicit ctx: Context): Symbol =
155164
cls.info.member(outerAccName(cls)).suchThat(_ is OuterAccessor).symbol orElse
156165
cls.info.decls.find(_ is OuterAccessor).getOrElse(NoSymbol)
@@ -159,17 +168,28 @@ object ExplicitOuter {
159168
private def hasOuter(cls: ClassSymbol)(implicit ctx: Context): Boolean =
160169
needsOuterIfReferenced(cls) && outerAccessor(cls).exists
161170

162-
/** Template `impl` of class `cls` references an outer this which goes to
163-
* a class that is not a static owner.
171+
/** Tree references a an outer class of `cls` which is not a static owner.
164172
*/
165-
private def referencesOuter(cls: ClassSymbol, impl: Template)(implicit ctx: Context): Boolean =
166-
existsSubTreeOf(impl) {
173+
def referencesOuter(cls: Symbol, tree: Tree)(implicit ctx: Context): Boolean = {
174+
def isOuter(sym: Symbol) =
175+
sym != cls && !sym.isStaticOwner && cls.isContainedIn(sym)
176+
tree match {
167177
case thisTree @ This(_) =>
168-
val thisCls = thisTree.symbol
169-
thisCls != cls && !thisCls.isStaticOwner && cls.isContainedIn(thisCls)
178+
isOuter(thisTree.symbol)
179+
case id: Ident =>
180+
id.tpe match {
181+
case ref @ TermRef(NoPrefix, _) =>
182+
ref.symbol.is(Method) && isOuter(id.symbol.owner.enclosingClass)
183+
// methods will be placed in enclosing class scope by LambdaLift, so they will get
184+
// an outer path then.
185+
case _ => false
186+
}
187+
case nw: New =>
188+
isOuter(nw.tpe.classSymbol.owner.enclosingClass)
170189
case _ =>
171-
false
190+
false
172191
}
192+
}
173193

174194
/** The outer prefix implied by type `tpe` */
175195
private def outerPrefix(tpe: Type)(implicit ctx: Context): Type = tpe match {
@@ -222,13 +242,16 @@ object ExplicitOuter {
222242
if (fun.symbol.isConstructor) {
223243
val cls = fun.symbol.owner.asClass
224244
def outerArg(receiver: Tree): Tree = receiver match {
225-
case New(tpt) => TypeTree(outerPrefix(tpt.tpe)).withPos(tpt.pos)
226-
case This(_) => ref(outerParamAccessor(cls))
227-
case TypeApply(Select(r, nme.asInstanceOf_), args) => outerArg(r) // cast was inserted, skip
245+
case New(tpt) =>
246+
singleton(outerPrefix(tpt.tpe))
247+
case This(_) =>
248+
ref(outerParamAccessor(cls)) // will be rewried to outer argument of secondary constructor in phase Constructors
249+
case TypeApply(Select(r, nme.asInstanceOf_), args) =>
250+
outerArg(r) // cast was inserted, skip
228251
}
229252
if (hasOuter(cls))
230253
methPart(fun) match {
231-
case Select(receiver, _) => outerArg(receiver) :: Nil
254+
case Select(receiver, _) => outerArg(receiver).withPos(fun.pos) :: Nil
232255
}
233256
else Nil
234257
} else Nil

src/dotty/tools/dotc/transform/TreeChecker.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,15 @@ class TreeChecker {
6767
assert(isSubType(tree1.tpe, tree.typeOpt), divergenceMsg(tree1.tpe, tree.typeOpt))
6868
tree1
6969
}
70-
if (ctx.erasedTypes) assertErased(res)
70+
if (ctx.erasedTypes) {
71+
assertErased(res)
72+
res match {
73+
case res: This =>
74+
assert(!ExplicitOuter.referencesOuter(ctx.owner.enclosingClass, res),
75+
i"Reference to $res from ${ctx.owner.showLocated}")
76+
case _ =>
77+
}
78+
}
7179
res
7280
}
7381

tests/pos/explicitOuter.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ class Outer(elem: Int, val next: Outer) {
55
}
66

77
class InnerClass(x: Int) extends next.InnerTrait {
8+
def this() = this(3)
89
def bar = elem + x
910
}
1011

@@ -18,6 +19,7 @@ class Outer(elem: Int, val next: Outer) {
1819
}
1920

2021
class InnerClass(x: Int) extends next.InnerTrait {
22+
def this() = this(3)
2123
def bar = elem + x
2224
}
2325

@@ -33,6 +35,18 @@ class Outer(elem: Int, val next: Outer) {
3335
val ec = new EmptyInnerClass
3436
}
3537

38+
def inner2 = {
39+
class C {
40+
val x = elem
41+
}
42+
class D {
43+
new C
44+
}
45+
class E {
46+
f()
47+
}
48+
def f() = ()
49+
}
3650
}
3751

3852
object Test extends App {

0 commit comments

Comments
 (0)