@@ -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,16 +1134,19 @@ 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 = {
11421141 val sym = dd.symbol
1143- val Block (stats, _) = dd.rhs
11441142 assert(! sym.isPrimaryConstructor, s " called with primary ctor $sym" )
11451143
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+
11461150 val beforeThisCall = List .newBuilder[js.Tree ]
11471151 var thisCall : Option [(Symbol , List [js.Tree ])] = None
11481152 val afterThisCall = List .newBuilder[js.Tree ]
@@ -1167,24 +1171,33 @@ class JSCodeGen()(using genCtx: Context) {
11671171 }
11681172 }
11691173
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 " )}" )
11701176 val Some ((targetCtor, ctorArgs)) = thisCall
11711177
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
11731185
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 )
11761188 }
11771189
1178- private def genJSClassCtorDispatch (sym : Symbol , allParams : List [Symbol ],
1190+ private def genJSClassCtorDispatch (ctorSym : Symbol ,
1191+ allParamsAndInfos : List [(Symbol , JSParamInfo )],
11791192 overloadNum : Int ): (jsExportsGen.Exported , List [js.ParamDef ]) = {
11801193
1181- implicit val pos : SourcePosition = sym .sourcePos
1194+ implicit val pos : SourcePosition = ctorSym .sourcePos
11821195
11831196 /* `allParams` are the parameters as seen from inside the constructor body,
11841197 * i.e., the ones generated by the trees in the constructor body.
11851198 */
11861199 val (captureParamsAndInfos, normalParamsAndInfos) =
1187- allParams.zip(sym.jsParamInfos) .partition(_._2.capture)
1200+ allParamsAndInfos .partition(_._2.capture)
11881201
11891202 /* For class captures, we need to generate different names than the ones
11901203 * used by the constructor body. This is necessary so that we can forward
@@ -1203,13 +1216,14 @@ class JSCodeGen()(using genCtx: Context) {
12031216
12041217 val normalInfos = normalParamsAndInfos.map(_._2).toIndexedSeq
12051218
1206- val jsExport = new jsExportsGen.Exported (sym , normalInfos) {
1219+ val jsExport = new jsExportsGen.Exported (ctorSym , normalInfos) {
12071220 def genBody (formalArgsRegistry : jsExportsGen.FormalArgsRegistry ): js.Tree = {
12081221 val paramAssigns = for {
12091222 ((param, info), i) <- normalParamsAndInfos.zipWithIndex
12101223 } 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)))
12131227
12141228 js.Assign (genVarRef(param), rhs)
12151229 }
@@ -1256,40 +1270,58 @@ class JSCodeGen()(using genCtx: Context) {
12561270 */
12571271
12581272 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) )
12611275
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)
12631278
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)
12761281
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)
12831286 }
12841287
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
12881290 } 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)
12901321 }
12911322
1292- ifOverload(tree, js.Block (inner ++ tree.ctor.beforeCall ++ assignments))
1323+ ifOverload(tree, js.Block (
1324+ inner ++ tree.ctor.beforeCall ++ captureAssigns ++ normalAssigns))
12931325 }
12941326
12951327 def postStats (tree : ConstructorTree [SplitSecondaryJSCtor ]): js.Tree = {
@@ -1301,22 +1333,24 @@ class JSCodeGen()(using genCtx: Context) {
13011333 val secondaryCtorTrees = ctorTree.subCtors
13021334
13031335 js.Block (
1304- secondaryCtorTrees.map(preStats(_, primaryCtor.params )) ++
1336+ secondaryCtorTrees.map(preStats(_, primaryCtor.paramsAndInfo )) ++
13051337 primaryCtor.body ++
13061338 secondaryCtorTrees.map(postStats(_))
13071339 )
13081340 }
13091341
13101342 private sealed trait JSCtor {
13111343 val sym : Symbol
1312- val params : List [Symbol ]
1344+ val paramsAndInfo : List [( Symbol , JSParamInfo ) ]
13131345 }
13141346
13151347 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
13171350
13181351 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 ],
13201354 val targetCtor : Symbol , val ctorArgs : List [js.Tree ],
13211355 val afterCall : List [js.Tree ]) extends JSCtor
13221356
@@ -3158,7 +3192,7 @@ class JSCodeGen()(using genCtx: Context) {
31583192 case resType => resType
31593193 }
31603194
3161- var clauses : List [(List [js.Tree ], js.Tree )] = Nil
3195+ var clauses : List [(List [js.MatchableLiteral ], js.Tree )] = Nil
31623196 var optDefaultClause : Option [js.Tree ] = None
31633197
31643198 for (caze @ CaseDef (pat, guard, body) <- cases) {
@@ -3167,19 +3201,29 @@ class JSCodeGen()(using genCtx: Context) {
31673201
31683202 val genBody = genStatOrExpr(body, isStat)
31693203
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+
31703214 pat match {
31713215 case lit : Literal =>
3172- clauses = (List (genExpr (lit)), genBody) :: clauses
3216+ clauses = (List (genMatchableLiteral (lit)), genBody) :: clauses
31733217 case Ident (nme.WILDCARD ) =>
31743218 optDefaultClause = Some (genBody)
31753219 case Alternative (alts) =>
31763220 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( )
31793223 }
31803224 clauses = (genAlts, genBody) :: clauses
31813225 case _ =>
3182- abortMatch( " Invalid case pattern " )
3226+ invalidCase( )
31833227 }
31843228 }
31853229
@@ -3194,10 +3238,6 @@ class JSCodeGen()(using genCtx: Context) {
31943238 * case is a typical product of `match`es that are full of
31953239 * `case n if ... =>`, which are used instead of `if` chains for
31963240 * 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.
32013241 */
32023242 def isInt (tree : js.Tree ): Boolean = tree.tpe == jstpe.IntType
32033243
@@ -3217,32 +3257,8 @@ class JSCodeGen()(using genCtx: Context) {
32173257 js.If (js.BinaryOp (op, genSelector, uniqueAlt), caseRhs, defaultClause)(resultType)
32183258
32193259 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)
32463262 }
32473263 }
32483264
@@ -3511,7 +3527,7 @@ class JSCodeGen()(using genCtx: Context) {
35113527 }
35123528
35133529 /** 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 ,
35153531 arguments : List [js.Tree ])(implicit pos : Position ): js.Tree = {
35163532 if (method.isPrivate || method.isClassConstructor)
35173533 genApplyMethodStatically(receiver, method, arguments)
0 commit comments