Skip to content

Commit 823fb0f

Browse files
committed
Merge pull request scala#4652 from retronym/ticket/9408
SI-9408 Avoid capturing outer class in local classes.
2 parents e1f7cca + 93bee55 commit 823fb0f

File tree

6 files changed

+82
-9
lines changed

6 files changed

+82
-9
lines changed

src/compiler/scala/tools/nsc/transform/Constructors.scala

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,19 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
165165
return
166166
}
167167

168+
// Note: elision of outer reference is based on a class-wise analysis, if a class might have subclasses,
169+
// it doesn't work. For example, `LocalParent` retains the outer reference in:
170+
//
171+
// class Outer { def test = {class LocalParent; class LocalChild extends LocalParent } }
172+
//
173+
// See run/t9408.scala for related test cases.
174+
val isEffectivelyFinal = clazz.isEffectivelyFinal
168175
def isParamCandidateForElision(sym: Symbol) = (sym.isParamAccessor && sym.isPrivateLocal)
169-
def isOuterCandidateForElision(sym: Symbol) = (sym.isOuterAccessor && sym.owner.isEffectivelyFinal && !sym.isOverridingSymbol)
176+
def isOuterCandidateForElision(sym: Symbol) = (sym.isOuterAccessor && isEffectivelyFinal && !sym.isOverridingSymbol)
170177

171-
val paramCandidatesForElision: Set[ /*Field*/ Symbol] = (clazz.info.decls.toSet filter isParamCandidateForElision)
172-
val outerCandidatesForElision: Set[ /*Method*/ Symbol] = (clazz.info.decls.toSet filter isOuterCandidateForElision)
178+
val decls = clazz.info.decls.toSet
179+
val paramCandidatesForElision: Set[ /*Field*/ Symbol] = (decls filter isParamCandidateForElision)
180+
val outerCandidatesForElision: Set[ /*Method*/ Symbol] = (decls filter isOuterCandidateForElision)
173181

174182
omittables ++= paramCandidatesForElision
175183
omittables ++= outerCandidatesForElision

src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ trait TreeAndTypeAnalysis extends Debugging {
138138

139139
if(grouped) {
140140
def enumerateChildren(sym: Symbol) = {
141-
sym.children.toList
141+
sym.sealedChildren.toList
142142
.sortBy(_.sealedSortName)
143143
.filterNot(x => x.isSealed && x.isAbstractClass && !isPrimitiveValueClass(x))
144144
}

src/compiler/scala/tools/nsc/typechecker/Checkable.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,8 @@ trait Checkable {
212212
)
213213
/** Are all children of these symbols pairwise irreconcilable? */
214214
def allChildrenAreIrreconcilable(sym1: Symbol, sym2: Symbol) = (
215-
sym1.children.toList forall (c1 =>
216-
sym2.children.toList forall (c2 =>
215+
sym1.sealedChildren.toList forall (c1 =>
216+
sym2.sealedChildren.toList forall (c2 =>
217217
areIrreconcilableAsParents(c1, c2)
218218
)
219219
)

src/compiler/scala/tools/nsc/typechecker/Typers.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,6 +1694,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
16941694
psym addChild context.owner
16951695
else
16961696
pending += ParentSealedInheritanceError(parent, psym)
1697+
if (psym.isLocalToBlock && !phase.erasedTypes)
1698+
psym addChild context.owner
16971699
val parentTypeOfThis = parent.tpe.dealias.typeOfThis
16981700

16991701
if (!(selfType <:< parentTypeOfThis) &&

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
980980
private def isNotOverridden = (
981981
owner.isClass && (
982982
owner.isEffectivelyFinal
983-
|| owner.isSealed && owner.children.forall(c => c.isEffectivelyFinal && (overridingSymbol(c) == NoSymbol))
983+
|| (owner.isSealed && owner.sealedChildren.forall(c => c.isEffectivelyFinal && (overridingSymbol(c) == NoSymbol)))
984984
)
985985
)
986986

@@ -992,6 +992,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
992992
isPrivate
993993
|| isLocalToBlock
994994
)
995+
|| isClass && originalOwner.isTerm && children.isEmpty // we track known subclasses of term-owned classes, use that infer finality
995996
)
996997
/** Is this symbol effectively final or a concrete term member of sealed class whose children do not override it */
997998
final def isEffectivelyFinalOrNotOverridden: Boolean = isEffectivelyFinal || (isTerm && !isDeferred && isNotOverridden)
@@ -2495,14 +2496,15 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
24952496
def associatedFile: AbstractFile = enclosingTopLevelClass.associatedFile
24962497
def associatedFile_=(f: AbstractFile) { abort("associatedFile_= inapplicable for " + this) }
24972498

2498-
/** If this is a sealed class, its known direct subclasses.
2499+
/** If this is a sealed or local class, its known direct subclasses.
24992500
* Otherwise, the empty set.
25002501
*/
25012502
def children: Set[Symbol] = Set()
2503+
final def sealedChildren: Set[Symbol] = if (!isSealed) Set.empty else children
25022504

25032505
/** Recursively assemble all children of this symbol.
25042506
*/
2505-
def sealedDescendants: Set[Symbol] = children.flatMap(_.sealedDescendants) + this
2507+
final def sealedDescendants: Set[Symbol] = if (!isSealed) Set(this) else children.flatMap(_.sealedDescendants) + this
25062508

25072509
@inline final def orElse(alt: => Symbol): Symbol = if (this ne NoSymbol) this else alt
25082510
@inline final def andAlso(f: Symbol => Unit): Symbol = { if (this ne NoSymbol) f(this) ; this }

test/files/run/t9408.scala

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
class Outer {
2+
def assertNoFields(c: Class[_]) {
3+
assert(c.getDeclaredFields.isEmpty)
4+
}
5+
def assertHasOuter(c: Class[_]) {
6+
assert(c.getDeclaredFields.exists(_.getName.contains("outer")))
7+
}
8+
class Member
9+
final class FinalMember
10+
11+
def test {
12+
assertHasOuter(classOf[Member])
13+
assertNoFields(classOf[FinalMember])
14+
final class C
15+
assertNoFields(classOf[C])
16+
class D
17+
assertNoFields(classOf[D])
18+
(() => {class E; assertNoFields(classOf[E])}).apply()
19+
20+
// The outer reference elision currently runs on a class-by-class basis. If it cannot rule out that a class has
21+
// subclasses, it will not remove the outer reference. A smarter analysis here could detect if no members of
22+
// a sealed (or effectively sealed) hierarchy use the outer reference, the optimization could be performed.
23+
class Parent
24+
class Child extends Parent
25+
assertHasOuter(classOf[Parent])
26+
27+
// Note: outer references (if they haven't been elided) are used in pattern matching as follows.
28+
// This isn't relevant to term-owned classes, as you can't refer to them with a prefix that includes
29+
// the outer class.
30+
val outer1 = new Outer
31+
val outer2 = new Outer
32+
(new outer1.Member: Any) match {
33+
case _: outer2.Member => sys.error("wrong match!")
34+
case _: outer1.Member => // okay
35+
}
36+
37+
// ... continuing on that theme, note that `Member` isn't considered as a local class, it is owned by a the class
38+
// `LocalOuter`, which itself happens to be term-owned. So we expect that it has an outer reference, and that this
39+
// is respected in type tests.
40+
class LocalOuter {
41+
class Member
42+
final class FinalMember
43+
}
44+
assertNoFields(classOf[LocalOuter])
45+
assertHasOuter(classOf[LocalOuter#Member])
46+
val localOuter1 = new LocalOuter
47+
val localOuter2 = new LocalOuter
48+
(new localOuter1.Member: Any) match {
49+
case _: localOuter2.Member => sys.error("wrong match!")
50+
case _: localOuter1.Member => // okay
51+
}
52+
// Final member classes still lose the outer reference.
53+
assertNoFields(classOf[LocalOuter#FinalMember])
54+
}
55+
}
56+
57+
object Test {
58+
def main(args: Array[String]): Unit = {
59+
new Outer().test
60+
}
61+
}

0 commit comments

Comments
 (0)