Skip to content

Commit 78f3eeb

Browse files
committed
Fix scala-js/scala-js#4929: Fix logic for moving early assignements in 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
1 parent 710aad8 commit 78f3eeb

File tree

1 file changed

+49
-19
lines changed

1 file changed

+49
-19
lines changed

Diff for: compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala

+49-19
Original file line numberDiff line numberDiff line change
@@ -1146,42 +1146,72 @@ class JSCodeGen()(using genCtx: Context) {
11461146

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

1151+
var preSuperStats = List.newBuilder[js.Tree]
11521152
var jsSuperCall: Option[js.JSSuperConstructorCall] = None
1153-
val jsStats = List.newBuilder[js.Tree]
1153+
val postSuperStats = List.newBuilder[js.Tree]
11541154

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

1171-
case stat =>
1172-
val jsStat = genStat(stat)
1173-
assert(jsSuperCall.isDefined || !jsStat.isInstanceOf[js.VarDef],
1174-
"Trying to move a local VarDef after the super constructor call of a non-native JS class at " +
1175-
dd.sourcePos)
1176-
jsStats += jsStat
1179+
def rec(tree: Tree): Unit = {
1180+
tree match {
1181+
case Block(stats, expr) =>
1182+
stats.foreach(rec(_))
1183+
rec(expr)
1184+
1185+
case tree @ Apply(fun @ Select(Super(This(_), _), _), args)
1186+
if fun.symbol.isClassConstructor =>
1187+
assert(jsSuperCall.isEmpty, s"Found 2 JS Super calls at ${dd.sourcePos}")
1188+
implicit val pos: Position = tree.span
1189+
jsSuperCall = Some(js.JSSuperConstructorCall(genActualJSArgs(fun.symbol, args)))
1190+
1191+
case tree if jsSuperCall.isDefined =>
1192+
// Once we're past the super constructor call, everything goes after.
1193+
postSuperStats += genStat(tree)
1194+
1195+
case Assign(lhs, Ident(_)) if isThisField(lhs) =>
1196+
/* If that shape appears before the jsSuperCall, it is a param
1197+
* accessor initializer. We move it.
1198+
*/
1199+
postSuperStats += genStat(tree)
1200+
1201+
case stat =>
1202+
// Other statements are left before.
1203+
preSuperStats += genStat(stat)
1204+
}
11771205
}
1206+
1207+
rec(dd.rhs)
11781208
}
11791209

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

11831213
new PrimaryJSCtor(sym, genParamsAndInfo(sym, dd.paramss),
1184-
js.JSConstructorBody(Nil, jsSuperCall.get, jsStats.result())(dd.span))
1214+
js.JSConstructorBody(preSuperStats.result(), jsSuperCall.get, postSuperStats.result())(dd.span))
11851215
}
11861216

11871217
private def genSecondaryJSClassCtor(dd: DefDef): SplitSecondaryJSCtor = {

0 commit comments

Comments
 (0)