-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #7926: Redo parameter aliasing optimization #8428
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b8b32be
8afe82c
6b78d3f
708c702
45a4b00
a924104
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,88 +1,82 @@ | ||
package dotty.tools.dotc | ||
package dotty.tools | ||
package dotc | ||
package transform | ||
|
||
import core._ | ||
import ast.Trees._ | ||
import Contexts._, Types._, Symbols._, Flags._, TypeUtils._, DenotTransformers._, StdNames._ | ||
import Decorators._ | ||
import MegaPhase._ | ||
import NameKinds.ParamAccessorName | ||
import config.Printers.typr | ||
|
||
/** For all parameter accessors | ||
/** For all private parameter accessors | ||
* | ||
* val x: T = ... | ||
* private val x: T = ... | ||
* | ||
* if | ||
* (1) x is forwarded in the supercall to a parameter that's also named `x` | ||
* (2) the superclass parameter accessor for `x` is accessible from the current class | ||
* change the accessor to | ||
* If there is a chain of parameter accessors starting with `x` such that | ||
* (1) The last parameter accessor in the chain is a field that's accessible | ||
* from the current class, and | ||
* (2) each preceding parameter is forwarded in the supercall of its class | ||
* to a parameter that's also named `x` | ||
* then change the accessor to | ||
* | ||
* def x: T = super.x.asInstanceOf[T] | ||
* private def x$accessor: T = super.x'.asInstanceOf[T] | ||
* | ||
* where x' is a reference to the final parameter in the chain. | ||
* Property (1) is established by the @see forwardParamAccessors method in PostTyper. | ||
* | ||
* The reason for renaming `x` to `x$accessor` is that private methods in the JVM | ||
* cannot override public ones. | ||
* | ||
* Do the same also if there are intermediate inaccessible parameter accessor forwarders. | ||
* The aim of this transformation is to avoid redundant parameter accessor fields. | ||
*/ | ||
class ParamForwarding(thisPhase: DenotTransformer) { | ||
class ParamForwarding extends MiniPhase with IdentityDenotTransformer: | ||
import ast.tpd._ | ||
|
||
def forwardParamAccessors(impl: Template)(implicit ctx: Context): Template = { | ||
def fwd(stats: List[Tree])(implicit ctx: Context): List[Tree] = { | ||
val (superArgs, superParamNames) = impl.parents match { | ||
case superCall @ Apply(fn, args) :: _ => | ||
fn.tpe.widen match { | ||
case MethodType(paramNames) => (args, paramNames) | ||
case _ => (Nil, Nil) | ||
} | ||
case _ => (Nil, Nil) | ||
} | ||
def inheritedAccessor(sym: Symbol): Symbol = { | ||
/** | ||
* Dmitry: having it have the same name is needed to maintain correctness in presence of subclassing | ||
* if you would use parent param-name `a` to implement param-field `b` | ||
* overriding field `b` will actually override field `a`, that is wrong! | ||
* | ||
* class A(val s: Int); | ||
* class B(val b: Int) extends A(b) | ||
* class C extends A(2) { | ||
* def s = 3 | ||
* assert(this.b == 2) | ||
* } | ||
*/ | ||
val candidate = sym.owner.asClass.superClass | ||
.info.decl(sym.name).suchThat(_.is(ParamAccessor, butNot = Mutable)).symbol | ||
if (candidate.isAccessibleFrom(currentClass.thisType, superAccess = true)) candidate | ||
else if (candidate.exists) inheritedAccessor(candidate) | ||
else NoSymbol | ||
} | ||
def forwardParamAccessor(stat: Tree): Tree = { | ||
stat match { | ||
case stat: ValDef => | ||
val sym = stat.symbol.asTerm | ||
if (sym.is(ParamAccessor, butNot = Mutable) && !sym.info.isInstanceOf[ExprType]) { | ||
// ElimByName gets confused with methods returning an ExprType, | ||
// so avoid param forwarding if parameter is by name. See i1766.scala | ||
val idx = superArgs.indexWhere(_.symbol == sym) | ||
if (idx >= 0 && superParamNames(idx) == stat.name) { // supercall to like-named parameter | ||
val alias = inheritedAccessor(sym) | ||
if (alias.exists) { | ||
def forwarder(implicit ctx: Context) = { | ||
sym.copySymDenotation(initFlags = sym.flags | Method | StableRealizable, info = sym.info.ensureMethodic) | ||
.installAfter(thisPhase) | ||
val superAcc = | ||
Super(This(currentClass), tpnme.EMPTY, inConstrCall = false).select(alias) | ||
typr.println(i"adding param forwarder $superAcc") | ||
DefDef(sym, superAcc.ensureConforms(sym.info.widen)).withSpan(stat.span) | ||
} | ||
return forwarder(ctx.withPhase(thisPhase.next)) | ||
} | ||
} | ||
} | ||
case _ => | ||
} | ||
stat | ||
} | ||
stats map forwardParamAccessor | ||
} | ||
private def thisPhase: ParamForwarding = this | ||
|
||
val phaseName: String = "paramForwarding" | ||
|
||
def transformIfParamAlias(mdef: ValOrDefDef)(using ctx: Context): Tree = | ||
|
||
def inheritedAccessor(sym: Symbol)(using Context): Symbol = | ||
val candidate = sym.owner.asClass.superClass | ||
.info.decl(sym.name).suchThat(_.is(ParamAccessor, butNot = Mutable)) | ||
.symbol | ||
if !candidate.is(Private) // candidate might be private and accessible if it is in an outer class | ||
&& candidate.isAccessibleFrom(currentClass.thisType, superAccess = true) | ||
then | ||
candidate | ||
else if candidate.is(SuperParamAlias) then | ||
inheritedAccessor(candidate) | ||
else | ||
NoSymbol | ||
|
||
val sym = mdef.symbol.asTerm | ||
if sym.is(SuperParamAlias) then | ||
assert(sym.is(ParamAccessor, butNot = Mutable)) | ||
val alias = inheritedAccessor(sym)(using ctx.withPhase(thisPhase)) | ||
if alias.exists then | ||
sym.copySymDenotation( | ||
name = ParamAccessorName(sym.name), | ||
initFlags = sym.flags | Method | StableRealizable, | ||
info = sym.info.ensureMethodic | ||
).installAfter(thisPhase) | ||
val superAcc = | ||
Super(This(currentClass), tpnme.EMPTY, inConstrCall = false) | ||
.withSpan(mdef.span) | ||
.select(alias) | ||
.ensureApplied | ||
.ensureConforms(sym.info.finalResultType) | ||
ctx.log(i"adding param forwarder $superAcc") | ||
DefDef(sym, superAcc) | ||
else mdef | ||
else mdef | ||
end transformIfParamAlias | ||
|
||
override def transformValDef(mdef: ValDef)(using Context): Tree = | ||
transformIfParamAlias(mdef) | ||
|
||
cpy.Template(impl)(body = fwd(impl.body)(ctx.withPhase(thisPhase))) | ||
} | ||
} | ||
override def transformDefDef(mdef: DefDef)(using Context): Tree = | ||
transformIfParamAlias(mdef) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ import Types._, Contexts._, Names._, Flags._, DenotTransformers._, Phases._ | |
import SymDenotations._, StdNames._, Annotations._, Trees._, Scopes._ | ||
import Decorators._ | ||
import Symbols._, SymUtils._ | ||
import config.Printers.typr | ||
import reporting.diagnostic.messages._ | ||
|
||
object PostTyper { | ||
|
@@ -24,7 +25,7 @@ object PostTyper { | |
* (2) Convert parameter fields that have the same name as a corresponding | ||
* public parameter field in a superclass to a forwarder to the superclass | ||
* field (corresponding = super class field is initialized with subclass field) | ||
* (@see ForwardParamAccessors) | ||
* @see forwardParamAccessors. | ||
* | ||
* (3) Add synthetic members (@see SyntheticMembers) | ||
* | ||
|
@@ -72,7 +73,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase | |
new PostTyperTransformer | ||
|
||
val superAcc: SuperAccessors = new SuperAccessors(thisPhase) | ||
val paramFwd: ParamForwarding = new ParamForwarding(thisPhase) | ||
val synthMbr: SyntheticMembers = new SyntheticMembers(thisPhase) | ||
|
||
private def newPart(tree: Tree): Option[New] = methPart(tree) match { | ||
|
@@ -98,6 +98,30 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase | |
|
||
def isCheckable(t: New): Boolean = !inJavaAnnot && !noCheckNews.contains(t) | ||
|
||
/** Mark parameter accessors that are aliases of like-named parameters | ||
* in their superclass with SuperParamAlias. | ||
* This info is used in phase ParamForwarding | ||
*/ | ||
private def forwardParamAccessors(impl: Template)(using Context): Unit = impl.parents match | ||
case superCall @ Apply(fn, superArgs) :: _ if superArgs.nonEmpty => | ||
fn.tpe.widen match | ||
case MethodType(superParamNames) => | ||
for case stat: ValDef <- impl.body do | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will loop over the whole body looking for param accessors for every super-call. I think it'd be more efficient to collect the param accessors first, then check if any super-argument matches one of them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it's only the supercall of the first template parent |
||
val sym = stat.symbol | ||
if sym.isAllOf(PrivateParamAccessor, butNot = Mutable) | ||
&& !sym.info.isInstanceOf[ExprType] // val-parameters cannot be call-by name, so no need to try to forward to them | ||
then | ||
val idx = superArgs.indexWhere(_.symbol == sym) | ||
if idx >= 0 && superParamNames(idx) == stat.name then | ||
// Supercall to like-named parameter. | ||
// Having it have the same name is needed to maintain correctness in presence of subclassing | ||
// if you would use parent param-name `a` to implement param-field `b` | ||
// overriding field `b` will actually override field `a`, that is wrong! | ||
typr.println(i"super alias: ${sym.showLocated}") | ||
sym.setFlagFrom(thisPhase, SuperParamAlias) | ||
case _ => | ||
case _ => | ||
|
||
private def transformAnnot(annot: Tree)(implicit ctx: Context): Tree = { | ||
val saved = inJavaAnnot | ||
inJavaAnnot = annot.symbol.is(JavaDefined) | ||
|
@@ -227,11 +251,11 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase | |
val pos = call.sourcePos | ||
val callTrace = Inliner.inlineCallTrace(call.symbol, pos)(ctx.withSource(pos.source)) | ||
cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)(inlineContext(call))) | ||
case tree: Template => | ||
withNoCheckNews(tree.parents.flatMap(newPart)) { | ||
val templ1 = paramFwd.forwardParamAccessors(tree) | ||
case templ: Template => | ||
withNoCheckNews(templ.parents.flatMap(newPart)) { | ||
forwardParamAccessors(templ) | ||
synthMbr.addSyntheticMembers( | ||
superAcc.wrapTemplate(templ1)( | ||
superAcc.wrapTemplate(templ)( | ||
super.transform(_).asInstanceOf[Template])) | ||
} | ||
case tree: ValDef => | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should exclude super-calls to traits
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't happen. First super call is always to a class.