Skip to content

Commit

Permalink
Fix scala-js/scala-js#4929: Fix logic for moving early assignements i…
Browse files Browse the repository at this point in the history
…n JS ctors.

Previously, we moved all statements in the constructors after the
super constructor call. However, it turns out that there are
statements that must be kept before, notably local `val`s
generated for default arguments to the super constructor.

We now keep statements where they are by default. We only move
statements of the form `C.this.field = ident;`, which are the
only ones that require access to `this`.

Forward port of the upstream commit
scala-js/scala-js@2e4594f
  • Loading branch information
sjrd committed Apr 29, 2024
1 parent ea49110 commit edb2faf
Showing 1 changed file with 49 additions and 19 deletions.
68 changes: 49 additions & 19 deletions compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1146,42 +1146,72 @@ class JSCodeGen()(using genCtx: Context) {

private def genPrimaryJSClassCtor(dd: DefDef): PrimaryJSCtor = {
val sym = dd.symbol
val Block(stats, _) = dd.rhs: @unchecked
assert(sym.isPrimaryConstructor, s"called with non-primary ctor: $sym")

var preSuperStats = List.newBuilder[js.Tree]
var jsSuperCall: Option[js.JSSuperConstructorCall] = None
val jsStats = List.newBuilder[js.Tree]
val postSuperStats = List.newBuilder[js.Tree]

/* Move all statements after the super constructor call since JS
* cannot access `this` before the super constructor call.
/* Move param accessor initializers after the super constructor call since
* JS cannot access `this` before the super constructor call.
*
* dotc inserts statements before the super constructor call for param
* accessor initializers (including val's and var's declared in the
* params). We move those after the super constructor call, and are
* therefore executed later than for a Scala class.
* params). Those statements are assignments whose rhs'es are always simple
* Idents (the constructor params).
*
* There can also be local `val`s before the super constructor call for
* default arguments to the super constructor. These must remain before.
*
* Our strategy is therefore to move only the field assignments after the
* super constructor call. They are therefore executed later than for a
* Scala class (as specified for non-native JS classes semantics).
* However, side effects and evaluation order of all the other
* computations remains unchanged.
*/
withPerMethodBodyState(sym) {
stats.foreach {
case tree @ Apply(fun @ Select(Super(This(_), _), _), args)
if fun.symbol.isClassConstructor =>
assert(jsSuperCall.isEmpty, s"Found 2 JS Super calls at ${dd.sourcePos}")
implicit val pos: Position = tree.span
jsSuperCall = Some(js.JSSuperConstructorCall(genActualJSArgs(fun.symbol, args)))
def isThisField(tree: Tree): Boolean = tree match {
case Select(ths: This, _) => ths.symbol == currentClassSym.get
case tree: Ident => desugarIdent(tree).exists(isThisField(_))
case _ => false
}

case stat =>
val jsStat = genStat(stat)
assert(jsSuperCall.isDefined || !jsStat.isInstanceOf[js.VarDef],
"Trying to move a local VarDef after the super constructor call of a non-native JS class at " +
dd.sourcePos)
jsStats += jsStat
def rec(tree: Tree): Unit = {
tree match {
case Block(stats, expr) =>
stats.foreach(rec(_))
rec(expr)

case tree @ Apply(fun @ Select(Super(This(_), _), _), args)
if fun.symbol.isClassConstructor =>
assert(jsSuperCall.isEmpty, s"Found 2 JS Super calls at ${dd.sourcePos}")
implicit val pos: Position = tree.span
jsSuperCall = Some(js.JSSuperConstructorCall(genActualJSArgs(fun.symbol, args)))

case tree if jsSuperCall.isDefined =>
// Once we're past the super constructor call, everything goes after.
postSuperStats += genStat(tree)

case Assign(lhs, Ident(_)) if isThisField(lhs) =>
/* If that shape appears before the jsSuperCall, it is a param
* accessor initializer. We move it.
*/
postSuperStats += genStat(tree)

case stat =>
// Other statements are left before.
preSuperStats += genStat(stat)
}
}

rec(dd.rhs)
}

assert(jsSuperCall.isDefined,
s"Did not find Super call in primary JS construtor at ${dd.sourcePos}")

new PrimaryJSCtor(sym, genParamsAndInfo(sym, dd.paramss),
js.JSConstructorBody(Nil, jsSuperCall.get, jsStats.result())(dd.span))
js.JSConstructorBody(preSuperStats.result(), jsSuperCall.get, postSuperStats.result())(dd.span))
}

private def genSecondaryJSClassCtor(dd: DefDef): SplitSecondaryJSCtor = {
Expand Down

0 comments on commit edb2faf

Please sign in to comment.