-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8253 from dotty-staging/fix-#8033
Fix #8033: Eliminate unnecessary outer accessors
- Loading branch information
Showing
10 changed files
with
245 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
compiler/src/dotty/tools/dotc/transform/CountOuterAccesses.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package dotty.tools.dotc | ||
package transform | ||
|
||
import core._ | ||
import MegaPhase.MiniPhase | ||
import dotty.tools.dotc.core.Contexts.Context | ||
import ast._ | ||
import Trees._ | ||
import Flags._ | ||
import Symbols._ | ||
import Decorators._ | ||
import ExplicitOuter.isOuterParamAccessor | ||
|
||
import collection.mutable | ||
|
||
object CountOuterAccesses: | ||
val name: String = "countOuterAccesses" | ||
|
||
/** Characterizes outer accessors and outer fields that can be dropped | ||
* if there are no references to them from within the toplevel class | ||
* where they are defined. | ||
*/ | ||
def mightBeDropped(sym: Symbol)(using Context) = | ||
def isLocal(cls: Symbol) = | ||
cls.isAnonymousClass | ||
|| cls.owner.isTerm | ||
|| cls.accessBoundary(defn.RootClass).isContainedIn(cls.topLevelClass) | ||
(sym.is(OuterAccessor) || sym.isOuterParamAccessor) && isLocal(sym.owner) | ||
|
||
/** Counts number of accesses to outer accessors and outer fields of | ||
* classes that are visible only within one source file. The info | ||
* is collected in `outerAccessCount` and used in the subsequent | ||
* DropOuterAccessors phase | ||
*/ | ||
class CountOuterAccesses extends MiniPhase: | ||
thisPhase => | ||
import tpd._ | ||
|
||
override def phaseName: String = CountOuterAccesses.name | ||
|
||
override def runsAfter: Set[String] = Set(LambdaLift.name) | ||
// LambdaLift can create outer paths. These need to be known in this phase. | ||
|
||
/** The number of times an outer accessor that might be dropped is accessed */ | ||
val outerAccessCount = new mutable.HashMap[Symbol, Int] { | ||
override def default(s: Symbol): Int = 0 | ||
} | ||
|
||
private def markAccessed(tree: RefTree)(implicit ctx: Context): Tree = | ||
val sym = tree.symbol | ||
if CountOuterAccesses.mightBeDropped(sym) then outerAccessCount(sym) += 1 | ||
tree | ||
|
||
override def transformIdent(tree: Ident)(using Context): Tree = | ||
markAccessed(tree) | ||
|
||
override def transformSelect(tree: Select)(using Context): Tree = | ||
markAccessed(tree) |
86 changes: 86 additions & 0 deletions
86
compiler/src/dotty/tools/dotc/transform/DropOuterAccessors.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package dotty.tools.dotc | ||
package transform | ||
|
||
import core._ | ||
import MegaPhase.MiniPhase | ||
import dotty.tools.dotc.core.Contexts.Context | ||
import ast._ | ||
import Trees._ | ||
import Flags._ | ||
import Symbols._ | ||
import Contexts._ | ||
import Decorators._ | ||
import DenotTransformers._ | ||
import ExplicitOuter.isOuterParamAccessor | ||
import CountOuterAccesses.mightBeDropped | ||
import collection.mutable | ||
import annotation.threadUnsafe | ||
|
||
object DropOuterAccessors: | ||
val name: String = "dropOuterAccessors" | ||
|
||
/** Drops unused outer accessors of inner classes that are visible only in one | ||
* toplevel class. For other classes, we can't tell whether an outer accessor | ||
* is used or not. It could for instance be used in a type test in some other source. | ||
*/ | ||
class DropOuterAccessors extends MiniPhase with IdentityDenotTransformer: | ||
thisPhase => | ||
import tpd._ | ||
|
||
override def phaseName: String = DropOuterAccessors.name | ||
|
||
override def runsAfterGroupsOf: Set[String] = Set(CountOuterAccesses.name) | ||
|
||
override def changesMembers: Boolean = true // the phase drops outer accessors | ||
|
||
override def transformTemplate(impl: Template)(using ctx: Context): Tree = | ||
val outerAccessCount = ctx.base.countOuterAccessesPhase | ||
.asInstanceOf[CountOuterAccesses] | ||
.outerAccessCount | ||
|
||
def dropOuterAccessor(stat: Tree): Boolean = stat match | ||
case stat: DefDef | ||
if stat.symbol.is(OuterAccessor) | ||
&& mightBeDropped(stat.symbol) | ||
&& outerAccessCount(stat.symbol) == 0 => | ||
assert(stat.rhs.isInstanceOf[RefTree], stat) | ||
assert(outerAccessCount(stat.rhs.symbol) > 0) | ||
outerAccessCount(stat.rhs.symbol) -= 1 | ||
stat.symbol.dropAfter(thisPhase) | ||
true | ||
case _ => | ||
false | ||
|
||
val droppedParamAccessors = mutable.Set[Symbol]() | ||
|
||
def dropOuterParamAccessor(stat: Tree): Boolean = stat match | ||
case stat: ValDef | ||
if stat.symbol.isOuterParamAccessor | ||
&& mightBeDropped(stat.symbol) | ||
&& outerAccessCount(stat.symbol) == 1 => | ||
droppedParamAccessors += stat.symbol | ||
stat.symbol.dropAfter(thisPhase) | ||
true | ||
case _ => | ||
false | ||
|
||
def dropOuterInit(stat: Tree): Boolean = stat match | ||
case Assign(lhs, rhs) => droppedParamAccessors.remove(lhs.symbol) | ||
case _ => false | ||
|
||
val body1 = impl.body | ||
.filterNot(dropOuterAccessor) | ||
.filterNot(dropOuterParamAccessor) | ||
val constr1 = | ||
if droppedParamAccessors.isEmpty then impl.constr | ||
else cpy.DefDef(impl.constr)( | ||
rhs = impl.constr.rhs match { | ||
case rhs @ Block(inits, expr) => | ||
cpy.Block(rhs)(inits.filterNot(dropOuterInit), expr) | ||
}) | ||
assert(droppedParamAccessors.isEmpty, | ||
i"""Failed to eliminate: $droppedParamAccessors | ||
when dropping outer accessors for ${ctx.owner} with | ||
$impl""") | ||
cpy.Template(impl)(constr = constr1, body = body1) | ||
end transformTemplate |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
trait Okay extends Serializable { | ||
def okay: Okay | ||
} | ||
|
||
class Foo { | ||
def okay1: Okay = new Okay() { | ||
val okay: Okay = this | ||
override def toString = "okay1" | ||
} | ||
def okay2: Okay = new Okay { | ||
val okay: Okay = okay1 | ||
override def toString = "okay2" | ||
} | ||
} | ||
|
||
object Test { | ||
def main(args: Array[String]): Unit = { | ||
val foo = new Foo | ||
assert(roundTrip(foo.okay1).toString == "okay1") | ||
assert(roundTrip(foo.okay2).toString == "okay2") | ||
} | ||
|
||
def roundTrip[A](a: A): A = { | ||
import java.io._ | ||
|
||
val aos = new ByteArrayOutputStream() | ||
val oos = new ObjectOutputStream(aos) | ||
oos.writeObject(a) | ||
oos.close() | ||
val ais = new ByteArrayInputStream(aos.toByteArray()) | ||
val ois: ObjectInputStream = new ObjectInputStream(ais) | ||
val newA = ois.readObject() | ||
ois.close() | ||
newA.asInstanceOf[A] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
class A: | ||
val a = 2 | ||
|
||
class B: | ||
val b = 3 | ||
|
||
trait T: | ||
def t = a + b | ||
|
||
val bb = B() | ||
|
||
class C extends bb.T: | ||
def result = a + t | ||
|
||
@main def Test = | ||
val a = A() | ||
val c = a.C() | ||
assert(c.result == 7) |