Skip to content

Commit 5cc5ae8

Browse files
committed
Fix #8033: Eliminate unused outer accessors
1 parent 4cb1cda commit 5cc5ae8

File tree

5 files changed

+97
-3
lines changed

5 files changed

+97
-3
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ class Compiler {
101101
new LazyVals, // Expand lazy vals
102102
new Memoize, // Add private fields to getters and setters
103103
new NonLocalReturns, // Expand non-local returns
104-
new CapturedVars) :: // Represent vars captured by closures as heap objects
104+
new CapturedVars, // Represent vars captured by closures as heap objects
105+
new ElimOuterAccessors) :: // Drop unused outer accessors
105106
List(new Constructors, // Collect initialization code in primary constructors
106107
// Note: constructors changes decls in transformTemplate, no InfoTransformers should be added after it
107108
new FunctionalInterfaces, // Rewrites closures to implement @specialized types of Functions.

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1079,9 +1079,15 @@ object SymDenotations {
10791079
final def lexicallyEnclosingClass(implicit ctx: Context): Symbol =
10801080
if (!exists || isClass) symbol else owner.lexicallyEnclosingClass
10811081

1082+
/** A class is extensible if it is not final, nor a module class,
1083+
* nor an anonymous class.
1084+
*/
1085+
final def isExtensibleClass(using Context): Boolean =
1086+
isClass && !isOneOf(FinalOrModuleClass) && !isAnonymousClass
1087+
10821088
/** A symbol is effectively final if it cannot be overridden in a subclass */
10831089
final def isEffectivelyFinal(implicit ctx: Context): Boolean =
1084-
isOneOf(EffectivelyFinalFlags) || !owner.isClass || owner.isOneOf(FinalOrModuleClass) || owner.isAnonymousClass
1090+
isOneOf(EffectivelyFinalFlags) || !owner.isExtensibleClass
10851091

10861092
/** A class is effectively sealed if has the `final` or `sealed` modifier, or it
10871093
* is defined in Scala 3 and is neither abstract nor open.

compiler/src/dotty/tools/dotc/transform/Constructors.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase =
3131

3232
override def phaseName: String = Constructors.name
3333
override def runsAfter: Set[String] = Set(HoistSuperArgs.name)
34-
override def runsAfterGroupsOf: Set[String] = Set(Memoize.name)
34+
override def runsAfterGroupsOf: Set[String] = Set(Memoize.name, ElimOuterAccessors.name)
3535
// Memoized needs to be finished because we depend on the ownerchain after Memoize
3636
// when checking whether an ident is an access in a constructor or outside it.
3737
// This test is done in the right-hand side of a value definition. If Memoize
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core._
5+
import MegaPhase.MiniPhase
6+
import dotty.tools.dotc.core.Contexts.Context
7+
import ast._
8+
import Trees._
9+
import Flags._
10+
import Symbols._
11+
import Decorators._
12+
import DenotTransformers._
13+
import collection.mutable
14+
15+
object ElimOuterAccessors:
16+
val name: String = "elimOuterAccessors"
17+
18+
/** Drops outer accessors of final classes that are unused */
19+
class ElimOuterAccessors extends MiniPhase with IdentityDenotTransformer:
20+
thisPhase =>
21+
import tpd._
22+
23+
override def phaseName: String = ElimOuterAccessors.name
24+
25+
override def changesMembers: Boolean = true // the phase drops outer accessors
26+
27+
private def mightBeDropped(sym: Symbol)(using Context) =
28+
sym.is(OuterAccessor) && !sym.owner.isExtensibleClass
29+
30+
/** The accessed outer accessors that might otherwise be dropped */
31+
private val retainedAccessors = mutable.Set[Symbol]()
32+
33+
private def markAccessed(tree: RefTree)(implicit ctx: Context): Tree =
34+
val sym = tree.symbol
35+
if mightBeDropped(sym) then retainedAccessors += sym
36+
tree
37+
38+
def transformStat(tree: Tree)(using Context): Tree =
39+
val sym = tree.symbol
40+
if mightBeDropped(sym) && !retainedAccessors.remove(sym) then
41+
sym.dropAfter(thisPhase)
42+
EmptyTree
43+
else
44+
tree
45+
46+
override def transformIdent(tree: Ident)(using Context): Tree =
47+
markAccessed(tree)
48+
49+
override def transformSelect(tree: Select)(using Context): Tree =
50+
markAccessed(tree)
51+
52+
override def transformTemplate(tree: Template)(using Context): Tree =
53+
cpy.Template(tree)(body = tree.body.map(transformStat).filter(_ != EmptyTree))

tests/run/i8033.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
trait Okay extends Serializable {
2+
def okay: Okay
3+
}
4+
5+
class Foo {
6+
def okay1: Okay = new Okay() {
7+
val okay: Okay = this
8+
}
9+
def okay2: Okay = new Okay {
10+
val okay: Okay = okay1
11+
}
12+
}
13+
14+
object Test {
15+
def main(args: Array[String]): Unit = {
16+
val foo = new Foo
17+
println(roundTrip(foo.okay1))
18+
println(roundTrip(foo.okay2))
19+
}
20+
21+
def roundTrip[A](a: A): A = {
22+
import java.io._
23+
24+
val aos = new ByteArrayOutputStream()
25+
val oos = new ObjectOutputStream(aos)
26+
oos.writeObject(a)
27+
oos.close()
28+
val ais = new ByteArrayInputStream(aos.toByteArray())
29+
val ois: ObjectInputStream = new ObjectInputStream(ais)
30+
val newA = ois.readObject()
31+
ois.close()
32+
newA.asInstanceOf[A]
33+
}
34+
}

0 commit comments

Comments
 (0)