Skip to content

Commit 655d0ee

Browse files
committed
Scala.js: Allow delegating JS Class constructor calls with default params
Previously this was not allowed, since implementing it was difficult. After the refactoring of the JS Class constrcutor call generation (a6cd84f), implementing this is not hard anymore, so we lift the implementation restriction. Forward port of scala-js/scala-js@2249ba6
1 parent bb57aa9 commit 655d0ee

File tree

2 files changed

+65
-41
lines changed

2 files changed

+65
-41
lines changed

compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,7 +1067,8 @@ class JSCodeGen()(using genCtx: Context) {
10671067
val jsClassCaptures = List.newBuilder[js.ParamDef]
10681068

10691069
def add(tree: ConstructorTree[_ <: JSCtor]): Unit = {
1070-
val (e, c) = genJSClassCtorDispatch(tree.ctor.sym, tree.ctor.params, tree.overloadNum)
1070+
val (e, c) = genJSClassCtorDispatch(tree.ctor.sym,
1071+
tree.ctor.paramsAndInfo, tree.overloadNum)
10711072
exports += e
10721073
jsClassCaptures ++= c
10731074
tree.subCtors.foreach(add(_))
@@ -1133,9 +1134,7 @@ class JSCodeGen()(using genCtx: Context) {
11331134
assert(jsSuperCall.isDefined,
11341135
s"Did not find Super call in primary JS construtor at ${dd.sourcePos}")
11351136

1136-
val params = dd.paramss.flatten.map(_.symbol)
1137-
1138-
new PrimaryJSCtor(sym, params, jsSuperCall.get :: jsStats.result())
1137+
new PrimaryJSCtor(sym, genParamsAndInfo(sym, dd.paramss), jsSuperCall.get :: jsStats.result())
11391138
}
11401139

11411140
private def genSecondaryJSClassCtor(dd: DefDef): SplitSecondaryJSCtor = {
@@ -1176,19 +1175,24 @@ class JSCodeGen()(using genCtx: Context) {
11761175
i"could not find the this() call in secondary JS constructor at ${dd.sourcePos}:\n${stats.map(_.show).mkString("\n")}")
11771176
val Some((targetCtor, ctorArgs)) = thisCall
11781177

1179-
val params = dd.paramss.flatten.map(_.symbol)
1178+
new SplitSecondaryJSCtor(sym, genParamsAndInfo(sym, dd.paramss),
1179+
beforeThisCall.result(), targetCtor, ctorArgs, afterThisCall.result())
1180+
}
11801181

1181-
new SplitSecondaryJSCtor(sym, params, beforeThisCall.result(), targetCtor,
1182-
ctorArgs, afterThisCall.result())
1182+
private def genParamsAndInfo(ctorSym: Symbol,
1183+
vparamss: List[ParamClause]): List[(Symbol, JSParamInfo)] = {
1184+
implicit val pos: SourcePosition = ctorSym.sourcePos
1185+
1186+
val paramSyms = if (vparamss.isEmpty) Nil else vparamss.head.map(_.symbol)
1187+
paramSyms.zip(ctorSym.jsParamInfos)
11831188
}
11841189

1185-
private def genJSClassCtorDispatch(ctorSym: Symbol, allParamSyms: List[Symbol],
1190+
private def genJSClassCtorDispatch(ctorSym: Symbol,
1191+
allParamsAndInfos: List[(Symbol, JSParamInfo)],
11861192
overloadNum: Int): (jsExportsGen.Exported, List[js.ParamDef]) = {
11871193

11881194
implicit val pos: SourcePosition = ctorSym.sourcePos
11891195

1190-
val allParamsAndInfos = allParamSyms.zip(ctorSym.jsParamInfos)
1191-
11921196
/* `allParams` are the parameters as seen from inside the constructor body,
11931197
* i.e., the ones generated by the trees in the constructor body.
11941198
*/
@@ -1266,40 +1270,58 @@ class JSCodeGen()(using genCtx: Context) {
12661270
*/
12671271

12681272
def preStats(tree: ConstructorTree[SplitSecondaryJSCtor],
1269-
nextParams: List[Symbol]): js.Tree = {
1270-
assert(tree.ctor.ctorArgs.size == nextParams.size, "param count mismatch")
1273+
nextParamsAndInfo: List[(Symbol, JSParamInfo)]): js.Tree = {
1274+
val inner = tree.subCtors.map(preStats(_, tree.ctor.paramsAndInfo))
12711275

1272-
val inner = tree.subCtors.map(preStats(_, tree.ctor.params))
1276+
assert(tree.ctor.ctorArgs.size == nextParamsAndInfo.size, "param count mismatch")
1277+
val paramsInfosAndArgs = nextParamsAndInfo.zip(tree.ctor.ctorArgs)
12731278

1274-
/* Reject undefined params (i.e. using a default value of another
1275-
* constructor) via implementation restriction.
1276-
*
1277-
* This is mostly for historical reasons. The ideal solution here would
1278-
* be to recognize calls to default param getters of JS class
1279-
* constructors and not even translate them to UndefinedParam in the
1280-
* first place.
1281-
*/
1282-
def isUndefinedParam(tree: js.Tree): Boolean = tree match {
1283-
case js.Transient(UndefinedParam) => true
1284-
case _ => false
1285-
}
1279+
val (captureParamsInfosAndArgs, normalParamsInfosAndArgs) =
1280+
paramsInfosAndArgs.partition(_._1._2.capture)
12861281

1287-
if (tree.ctor.ctorArgs.exists(isUndefinedParam)) {
1288-
report.error(
1289-
"Implementation restriction: " +
1290-
"in a JS class, a secondary constructor calling another constructor " +
1291-
"with default parameters must provide the values of all parameters.",
1292-
tree.ctor.sym.sourcePos)
1282+
val captureAssigns = for {
1283+
((param, _), arg) <- captureParamsInfosAndArgs
1284+
} yield {
1285+
js.Assign(genVarRef(param), arg)
12931286
}
12941287

1295-
val assignments = for {
1296-
(param, arg) <- nextParams.zip(tree.ctor.ctorArgs)
1297-
if !isUndefinedParam(arg)
1288+
val normalAssigns = for {
1289+
(((param, info), arg), i) <- normalParamsInfosAndArgs.zipWithIndex
12981290
} yield {
1299-
js.Assign(genVarRef(param), arg)
1291+
val newArg = arg match {
1292+
case js.Transient(UndefinedParam) =>
1293+
/* Go full circle: We have ignored the default param getter for
1294+
* this, we'll create it again.
1295+
*
1296+
* This seems not optimal: We could simply not ignore the calls to
1297+
* default param getters in the first place.
1298+
*
1299+
* However, this proves to be difficult: Because of translations in
1300+
* earlier phases, calls to default param getters may be assigned
1301+
* to temporary variables first (see the undefinedDefaultParams
1302+
* ScopedVar). If this happens, it becomes increasingly difficult
1303+
* to distinguish a default param getter call for a constructor
1304+
* call of *this* instance (in which case we would want to keep
1305+
* the default param getter call) from one for a *different*
1306+
* instance (in which case we would want to discard the default
1307+
* param getter call)
1308+
*
1309+
* Because of this, it ends up being easier to just re-create the
1310+
* default param getter call if necessary.
1311+
*/
1312+
implicit val pos: SourcePosition = tree.ctor.sym.sourcePos
1313+
jsExportsGen.genCallDefaultGetter(tree.ctor.sym, i, static = false,
1314+
captures = captureParamsInfosAndArgs.map(p => genVarRef(p._1._1)))(
1315+
prevArgsCount => normalParamsInfosAndArgs.take(prevArgsCount).map(p => genVarRef(p._1._1)))
1316+
1317+
case arg => arg
1318+
}
1319+
1320+
js.Assign(genVarRef(param), newArg)
13001321
}
13011322

1302-
ifOverload(tree, js.Block(inner ++ tree.ctor.beforeCall ++ assignments))
1323+
ifOverload(tree, js.Block(
1324+
inner ++ tree.ctor.beforeCall ++ captureAssigns ++ normalAssigns))
13031325
}
13041326

13051327
def postStats(tree: ConstructorTree[SplitSecondaryJSCtor]): js.Tree = {
@@ -1311,22 +1333,24 @@ class JSCodeGen()(using genCtx: Context) {
13111333
val secondaryCtorTrees = ctorTree.subCtors
13121334

13131335
js.Block(
1314-
secondaryCtorTrees.map(preStats(_, primaryCtor.params)) ++
1336+
secondaryCtorTrees.map(preStats(_, primaryCtor.paramsAndInfo)) ++
13151337
primaryCtor.body ++
13161338
secondaryCtorTrees.map(postStats(_))
13171339
)
13181340
}
13191341

13201342
private sealed trait JSCtor {
13211343
val sym: Symbol
1322-
val params: List[Symbol]
1344+
val paramsAndInfo: List[(Symbol, JSParamInfo)]
13231345
}
13241346

13251347
private class PrimaryJSCtor(val sym: Symbol,
1326-
val params: List[Symbol], val body: List[js.Tree]) extends JSCtor
1348+
val paramsAndInfo: List[(Symbol, JSParamInfo)],
1349+
val body: List[js.Tree]) extends JSCtor
13271350

13281351
private class SplitSecondaryJSCtor(val sym: Symbol,
1329-
val params: List[Symbol], val beforeCall: List[js.Tree],
1352+
val paramsAndInfo: List[(Symbol, JSParamInfo)],
1353+
val beforeCall: List[js.Tree],
13301354
val targetCtor: Symbol, val ctorArgs: List[js.Tree],
13311355
val afterCall: List[js.Tree]) extends JSCtor
13321356

compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) {
725725
}
726726
}
727727

728-
private def genCallDefaultGetter(sym: Symbol, paramIndex: Int,
728+
def genCallDefaultGetter(sym: Symbol, paramIndex: Int,
729729
static: Boolean, captures: List[js.Tree])(
730730
previousArgsValues: Int => List[js.Tree])(
731731
implicit pos: SourcePosition): js.Tree = {

0 commit comments

Comments
 (0)