@@ -1067,7 +1067,8 @@ class JSCodeGen()(using genCtx: Context) {
1067
1067
val jsClassCaptures = List .newBuilder[js.ParamDef ]
1068
1068
1069
1069
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)
1071
1072
exports += e
1072
1073
jsClassCaptures ++= c
1073
1074
tree.subCtors.foreach(add(_))
@@ -1133,16 +1134,19 @@ class JSCodeGen()(using genCtx: Context) {
1133
1134
assert(jsSuperCall.isDefined,
1134
1135
s " Did not find Super call in primary JS construtor at ${dd.sourcePos}" )
1135
1136
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())
1139
1138
}
1140
1139
1141
1140
private def genSecondaryJSClassCtor (dd : DefDef ): SplitSecondaryJSCtor = {
1142
1141
val sym = dd.symbol
1143
- val Block (stats, _) = dd.rhs
1144
1142
assert(! sym.isPrimaryConstructor, s " called with primary ctor $sym" )
1145
1143
1144
+ def flattenBlocks (t : Tree ): List [Tree ] = t match {
1145
+ case Block (stats, expr) => (stats :+ expr).flatMap(flattenBlocks)
1146
+ case _ => t :: Nil
1147
+ }
1148
+ val stats = flattenBlocks(dd.rhs)
1149
+
1146
1150
val beforeThisCall = List .newBuilder[js.Tree ]
1147
1151
var thisCall : Option [(Symbol , List [js.Tree ])] = None
1148
1152
val afterThisCall = List .newBuilder[js.Tree ]
@@ -1167,24 +1171,33 @@ class JSCodeGen()(using genCtx: Context) {
1167
1171
}
1168
1172
}
1169
1173
1174
+ assert(thisCall.isDefined,
1175
+ i " could not find the this() call in secondary JS constructor at ${dd.sourcePos}: \n ${stats.map(_.show).mkString(" \n " )}" )
1170
1176
val Some ((targetCtor, ctorArgs)) = thisCall
1171
1177
1172
- val params = dd.paramss.flatten.map(_.symbol)
1178
+ new SplitSecondaryJSCtor (sym, genParamsAndInfo(sym, dd.paramss),
1179
+ beforeThisCall.result(), targetCtor, ctorArgs, afterThisCall.result())
1180
+ }
1181
+
1182
+ private def genParamsAndInfo (ctorSym : Symbol ,
1183
+ vparamss : List [ParamClause ]): List [(Symbol , JSParamInfo )] = {
1184
+ implicit val pos : SourcePosition = ctorSym.sourcePos
1173
1185
1174
- new SplitSecondaryJSCtor (sym, params, beforeThisCall.result(), targetCtor,
1175
- ctorArgs, afterThisCall.result() )
1186
+ val paramSyms = if (vparamss.isEmpty) Nil else vparamss.head.map(_.symbol)
1187
+ paramSyms.zip(ctorSym.jsParamInfos )
1176
1188
}
1177
1189
1178
- private def genJSClassCtorDispatch (sym : Symbol , allParams : List [Symbol ],
1190
+ private def genJSClassCtorDispatch (ctorSym : Symbol ,
1191
+ allParamsAndInfos : List [(Symbol , JSParamInfo )],
1179
1192
overloadNum : Int ): (jsExportsGen.Exported , List [js.ParamDef ]) = {
1180
1193
1181
- implicit val pos : SourcePosition = sym .sourcePos
1194
+ implicit val pos : SourcePosition = ctorSym .sourcePos
1182
1195
1183
1196
/* `allParams` are the parameters as seen from inside the constructor body,
1184
1197
* i.e., the ones generated by the trees in the constructor body.
1185
1198
*/
1186
1199
val (captureParamsAndInfos, normalParamsAndInfos) =
1187
- allParams.zip(sym.jsParamInfos) .partition(_._2.capture)
1200
+ allParamsAndInfos .partition(_._2.capture)
1188
1201
1189
1202
/* For class captures, we need to generate different names than the ones
1190
1203
* used by the constructor body. This is necessary so that we can forward
@@ -1203,13 +1216,14 @@ class JSCodeGen()(using genCtx: Context) {
1203
1216
1204
1217
val normalInfos = normalParamsAndInfos.map(_._2).toIndexedSeq
1205
1218
1206
- val jsExport = new jsExportsGen.Exported (sym , normalInfos) {
1219
+ val jsExport = new jsExportsGen.Exported (ctorSym , normalInfos) {
1207
1220
def genBody (formalArgsRegistry : jsExportsGen.FormalArgsRegistry ): js.Tree = {
1208
1221
val paramAssigns = for {
1209
1222
((param, info), i) <- normalParamsAndInfos.zipWithIndex
1210
1223
} yield {
1211
- val rhs = jsExportsGen.genScalaArg(this , i, formalArgsRegistry, info, static = true )(
1212
- prevArgsCount => allParams.take(prevArgsCount).map(genVarRef(_)))
1224
+ val rhs = jsExportsGen.genScalaArg(this , i, formalArgsRegistry, info, static = true ,
1225
+ captures = captureParamsAndInfos.map(pi => genVarRef(pi._1)))(
1226
+ prevArgsCount => normalParamsAndInfos.take(prevArgsCount).map(pi => genVarRef(pi._1)))
1213
1227
1214
1228
js.Assign (genVarRef(param), rhs)
1215
1229
}
@@ -1256,40 +1270,58 @@ class JSCodeGen()(using genCtx: Context) {
1256
1270
*/
1257
1271
1258
1272
def preStats (tree : ConstructorTree [SplitSecondaryJSCtor ],
1259
- nextParams : List [Symbol ]): js.Tree = {
1260
- 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) )
1261
1275
1262
- 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)
1263
1278
1264
- /* Reject undefined params (i.e. using a default value of another
1265
- * constructor) via implementation restriction.
1266
- *
1267
- * This is mostly for historical reasons. The ideal solution here would
1268
- * be to recognize calls to default param getters of JS class
1269
- * constructors and not even translate them to UndefinedParam in the
1270
- * first place.
1271
- */
1272
- def isUndefinedParam (tree : js.Tree ): Boolean = tree match {
1273
- case js.Transient (UndefinedParam ) => true
1274
- case _ => false
1275
- }
1279
+ val (captureParamsInfosAndArgs, normalParamsInfosAndArgs) =
1280
+ paramsInfosAndArgs.partition(_._1._2.capture)
1276
1281
1277
- if (tree.ctor.ctorArgs.exists(isUndefinedParam)) {
1278
- report.error(
1279
- " Implementation restriction: " +
1280
- " in a JS class, a secondary constructor calling another constructor " +
1281
- " with default parameters must provide the values of all parameters." ,
1282
- tree.ctor.sym.sourcePos)
1282
+ val captureAssigns = for {
1283
+ ((param, _), arg) <- captureParamsInfosAndArgs
1284
+ } yield {
1285
+ js.Assign (genVarRef(param), arg)
1283
1286
}
1284
1287
1285
- val assignments = for {
1286
- (param, arg) <- nextParams.zip(tree.ctor.ctorArgs)
1287
- if ! isUndefinedParam(arg)
1288
+ val normalAssigns = for {
1289
+ (((param, info), arg), i) <- normalParamsInfosAndArgs.zipWithIndex
1288
1290
} yield {
1289
- 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)
1290
1321
}
1291
1322
1292
- ifOverload(tree, js.Block (inner ++ tree.ctor.beforeCall ++ assignments))
1323
+ ifOverload(tree, js.Block (
1324
+ inner ++ tree.ctor.beforeCall ++ captureAssigns ++ normalAssigns))
1293
1325
}
1294
1326
1295
1327
def postStats (tree : ConstructorTree [SplitSecondaryJSCtor ]): js.Tree = {
@@ -1301,22 +1333,24 @@ class JSCodeGen()(using genCtx: Context) {
1301
1333
val secondaryCtorTrees = ctorTree.subCtors
1302
1334
1303
1335
js.Block (
1304
- secondaryCtorTrees.map(preStats(_, primaryCtor.params )) ++
1336
+ secondaryCtorTrees.map(preStats(_, primaryCtor.paramsAndInfo )) ++
1305
1337
primaryCtor.body ++
1306
1338
secondaryCtorTrees.map(postStats(_))
1307
1339
)
1308
1340
}
1309
1341
1310
1342
private sealed trait JSCtor {
1311
1343
val sym : Symbol
1312
- val params : List [Symbol ]
1344
+ val paramsAndInfo : List [( Symbol , JSParamInfo ) ]
1313
1345
}
1314
1346
1315
1347
private class PrimaryJSCtor (val sym : Symbol ,
1316
- 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
1317
1350
1318
1351
private class SplitSecondaryJSCtor (val sym : Symbol ,
1319
- val params : List [Symbol ], val beforeCall : List [js.Tree ],
1352
+ val paramsAndInfo : List [(Symbol , JSParamInfo )],
1353
+ val beforeCall : List [js.Tree ],
1320
1354
val targetCtor : Symbol , val ctorArgs : List [js.Tree ],
1321
1355
val afterCall : List [js.Tree ]) extends JSCtor
1322
1356
@@ -3158,7 +3192,7 @@ class JSCodeGen()(using genCtx: Context) {
3158
3192
case resType => resType
3159
3193
}
3160
3194
3161
- var clauses : List [(List [js.Tree ], js.Tree )] = Nil
3195
+ var clauses : List [(List [js.MatchableLiteral ], js.Tree )] = Nil
3162
3196
var optDefaultClause : Option [js.Tree ] = None
3163
3197
3164
3198
for (caze @ CaseDef (pat, guard, body) <- cases) {
@@ -3167,19 +3201,29 @@ class JSCodeGen()(using genCtx: Context) {
3167
3201
3168
3202
val genBody = genStatOrExpr(body, isStat)
3169
3203
3204
+ def invalidCase (): Nothing =
3205
+ abortMatch(" Invalid case" )
3206
+
3207
+ def genMatchableLiteral (tree : Literal ): js.MatchableLiteral = {
3208
+ genExpr(tree) match {
3209
+ case matchableLiteral : js.MatchableLiteral => matchableLiteral
3210
+ case otherExpr => invalidCase()
3211
+ }
3212
+ }
3213
+
3170
3214
pat match {
3171
3215
case lit : Literal =>
3172
- clauses = (List (genExpr (lit)), genBody) :: clauses
3216
+ clauses = (List (genMatchableLiteral (lit)), genBody) :: clauses
3173
3217
case Ident (nme.WILDCARD ) =>
3174
3218
optDefaultClause = Some (genBody)
3175
3219
case Alternative (alts) =>
3176
3220
val genAlts = alts.map {
3177
- case lit : Literal => genExpr (lit)
3178
- case _ => abortMatch( " Invalid case in alternative " )
3221
+ case lit : Literal => genMatchableLiteral (lit)
3222
+ case _ => invalidCase( )
3179
3223
}
3180
3224
clauses = (genAlts, genBody) :: clauses
3181
3225
case _ =>
3182
- abortMatch( " Invalid case pattern " )
3226
+ invalidCase( )
3183
3227
}
3184
3228
}
3185
3229
@@ -3194,10 +3238,6 @@ class JSCodeGen()(using genCtx: Context) {
3194
3238
* case is a typical product of `match`es that are full of
3195
3239
* `case n if ... =>`, which are used instead of `if` chains for
3196
3240
* convenience and/or readability.
3197
- *
3198
- * When no optimization applies, and any of the case values is not a
3199
- * literal int, we emit a series of `if..else` instead of a `js.Match`.
3200
- * This became necessary in 2.13.2 with strings and nulls.
3201
3241
*/
3202
3242
def isInt (tree : js.Tree ): Boolean = tree.tpe == jstpe.IntType
3203
3243
@@ -3217,32 +3257,8 @@ class JSCodeGen()(using genCtx: Context) {
3217
3257
js.If (js.BinaryOp (op, genSelector, uniqueAlt), caseRhs, defaultClause)(resultType)
3218
3258
3219
3259
case _ =>
3220
- if (isInt(genSelector) &&
3221
- clauses.forall(_._1.forall(_.isInstanceOf [js.IntLiteral ]))) {
3222
- // We have int literals only: use a js.Match
3223
- val intClauses = clauses.asInstanceOf [List [(List [js.IntLiteral ], js.Tree )]]
3224
- js.Match (genSelector, intClauses, defaultClause)(resultType)
3225
- } else {
3226
- // We have other stuff: generate an if..else chain
3227
- val (tempSelectorDef, tempSelectorRef) = genSelector match {
3228
- case varRef : js.VarRef =>
3229
- (js.Skip (), varRef)
3230
- case _ =>
3231
- val varDef = js.VarDef (freshLocalIdent(), NoOriginalName ,
3232
- genSelector.tpe, mutable = false , genSelector)
3233
- (varDef, varDef.ref)
3234
- }
3235
- val ifElseChain = clauses.foldRight(defaultClause) { (caze, elsep) =>
3236
- val conds = caze._1.map { caseValue =>
3237
- js.BinaryOp (js.BinaryOp .=== , tempSelectorRef, caseValue)
3238
- }
3239
- val cond = conds.reduceRight[js.Tree ] { (left, right) =>
3240
- js.If (left, js.BooleanLiteral (true ), right)(jstpe.BooleanType )
3241
- }
3242
- js.If (cond, caze._2, elsep)(resultType)
3243
- }
3244
- js.Block (tempSelectorDef, ifElseChain)
3245
- }
3260
+ // We have more than one case: use a js.Match
3261
+ js.Match (genSelector, clauses, defaultClause)(resultType)
3246
3262
}
3247
3263
}
3248
3264
@@ -3511,7 +3527,7 @@ class JSCodeGen()(using genCtx: Context) {
3511
3527
}
3512
3528
3513
3529
/** Gen a statically linked call to an instance method. */
3514
- private def genApplyMethodMaybeStatically (receiver : js.Tree , method : Symbol ,
3530
+ def genApplyMethodMaybeStatically (receiver : js.Tree , method : Symbol ,
3515
3531
arguments : List [js.Tree ])(implicit pos : Position ): js.Tree = {
3516
3532
if (method.isPrivate || method.isClassConstructor)
3517
3533
genApplyMethodStatically(receiver, method, arguments)
0 commit comments