Skip to content

Also hoist lifted arguments in super calls #14919

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

Merged
merged 5 commits into from
Apr 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 43 additions & 22 deletions compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,12 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase

/** If argument is complex, hoist it out into its own method and refer to the
* method instead.
* @param arg The argument that might be hoisted
* @param cdef The definition of the constructor from which the call is made
* @param arg The argument that might be hoisted
* @param cdef The definition of the constructor from which the call is made
* @param lifted Argument definitions that were lifted out in a call prefix
* @return The argument after possible hoisting
*/
private def hoistSuperArg(arg: Tree, cdef: DefDef): Tree = {
private def hoistSuperArg(arg: Tree, cdef: DefDef, lifted: List[Symbol]): Tree = {
val constr = cdef.symbol
lazy val origParams = // The parameters that can be accessed in the supercall
if (constr == cls.primaryConstructor)
Expand Down Expand Up @@ -92,36 +93,39 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase
val argTypeWrtConstr = argType.widenTermRefExpr.subst(origParams, allParamRefs(constr.info))
// argType with references to paramRefs of the primary constructor instead of
// local parameter accessors
val abstractedArgType =
if lifted.isEmpty then argTypeWrtConstr
else MethodType.fromSymbols(lifted, argTypeWrtConstr)
newSymbol(
owner = methOwner,
name = SuperArgName.fresh(cls.name.toTermName),
flags = Synthetic | Private | Method | staticFlag,
info = replaceResult(constr.info, argTypeWrtConstr),
info = replaceResult(constr.info, abstractedArgType),
coord = constr.coord
).enteredAfter(thisPhase)
}

/** Type of a reference implies that it needs to be hoisted */
def refNeedsHoist(tp: Type): Boolean = tp match {
case tp: ThisType => !tp.cls.isStaticOwner && tp.cls != cls
case tp: ThisType => !tp.cls.isStaticOwner && !cls.isContainedIn(tp.cls)
case tp: TermRef => refNeedsHoist(tp.prefix)
case _ => false
}

/** Super call argument is complex, needs to be hoisted */
def needsHoist(tree: Tree) = tree match {
def needsHoist(tree: Tree) = tree match
case _: DefDef => true
case _: Template => true
case _: New => !tree.tpe.typeSymbol.isStatic
case _: RefTree | _: This => refNeedsHoist(tree.tpe)
case _ => false
}

/** Only rewire types that are owned by the current Hoister and is an param or accessor */
def needsRewire(tp: Type) = tp match {
case ntp: NamedType =>
val owner = ntp.symbol.maybeOwner
(owner == cls || owner == constr) && ntp.symbol.isParamOrAccessor
|| lifted.contains(ntp.symbol)
case _ => false
}

Expand All @@ -134,7 +138,7 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase
if pref.isType then pref.tpe.typeSymbol else pref.symbol)
val tmap = new TreeTypeMap(
typeMap = new TypeMap {
lazy val origToParam = origParams.zip(paramSyms).toMap
lazy val origToParam = (origParams ::: lifted).zip(paramSyms).toMap
def apply(tp: Type) = tp match {
case tp: NamedType if needsRewire(tp) =>
origToParam.get(tp.symbol) match {
Expand Down Expand Up @@ -164,35 +168,52 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase
Nil
}
val (typeParams, termParams) = origParams.span(_.isType)
val res = ref(superMeth)
var res = ref(superMeth)
.appliedToTypes(typeParams.map(_.typeRef))
.appliedToArgss(termParamRefs(constr.info, termParams))
if lifted.nonEmpty then
res = res.appliedToArgs(lifted.map(ref))
report.log(i"hoist $arg, cls = $cls = $res")
res
case _ => arg
}
}

/** Hoist complex arguments in super call out of the class. */
def hoistSuperArgsFromCall(superCall: Tree, cdef: DefDef): Tree = superCall match {
def hoistSuperArgsFromCall(superCall: Tree, cdef: DefDef, lifted: mutable.ListBuffer[Symbol]): Tree = superCall match
case Block(defs, expr) =>
cpy.Block(superCall)(
stats = defs.mapconserve {
case vdef: ValDef =>
try cpy.ValDef(vdef)(rhs = hoistSuperArg(vdef.rhs, cdef, lifted.toList))
finally lifted += vdef.symbol
case ddef: DefDef =>
try cpy.DefDef(ddef)(rhs = hoistSuperArg(ddef.rhs, cdef, lifted.toList))
finally lifted += ddef.symbol
case stat =>
stat
},
expr = hoistSuperArgsFromCall(expr, cdef, lifted))
case Apply(fn, args) =>
cpy.Apply(superCall)(hoistSuperArgsFromCall(fn, cdef), args.mapconserve(hoistSuperArg(_, cdef)))
cpy.Apply(superCall)(
hoistSuperArgsFromCall(fn, cdef, lifted),
args.mapconserve(hoistSuperArg(_, cdef, lifted.toList)))
case _ =>
superCall
}

/** Hoist complex arguments in this-constructor call of secondary constructor out of the class. */
def hoistSuperArgsFromConstr(stat: Tree): Tree = stat match {
case stat: DefDef if stat.symbol.isClassConstructor =>
cpy.DefDef(stat)(rhs =
stat.rhs match {
case Block(superCall :: stats, expr) =>
val superCall1 = hoistSuperArgsFromCall(superCall, stat)
if (superCall1 eq superCall) stat.rhs
else cpy.Block(stat.rhs)(superCall1 :: stats, expr)
case constr: DefDef if constr.symbol.isClassConstructor =>
val lifted = new mutable.ListBuffer[Symbol]
cpy.DefDef(constr)(rhs =
constr.rhs match
case Block(stats @ (superCall :: stats1), expr: Literal) =>
cpy.Block(constr.rhs)(
stats.derivedCons(hoistSuperArgsFromCall(superCall, constr, lifted), stats1),
expr)
case _ =>
hoistSuperArgsFromCall(stat.rhs, stat)
})
hoistSuperArgsFromCall(constr.rhs, constr, lifted)
)
case _ =>
stat
}
Expand All @@ -202,7 +223,7 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase
tdef.rhs match {
case impl @ Template(cdef, superCall :: others, _, _) =>
val hoist = new Hoister(tdef.symbol)
val hoistedSuperCall = hoist.hoistSuperArgsFromCall(superCall, cdef)
val hoistedSuperCall = hoist.hoistSuperArgsFromCall(superCall, cdef, new mutable.ListBuffer)
val hoistedBody = impl.body.mapconserve(hoist.hoistSuperArgsFromConstr)
if (hoist.superArgDefs.isEmpty) tdef
else {
Expand Down
27 changes: 27 additions & 0 deletions tests/run/i14164.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

object Test:
class Base(a: String = "x", param: String)

class Child extends Base(
param =
for x <- Seq("a") yield x
"param"
)

new Child

def main(args: Array[String]) = ()

end Test

class Test2:
class Inner(withDefault: String = "inner")(
dependentDefault: String = withDefault) extends Object {
def this(x: Int) = this(x.toString)()
}

class Test3:
class Inner(withDefault: () => String = () => "inner")(
dependentDefault: String = withDefault()) extends Object {
def this(x: Int) = this(() => x.toString)()
}