Skip to content

Commit a38ae20

Browse files
Merge pull request #13383 from dotty-staging/scalajs-1.7.0
Upgrade to Scala.js 1.7.0.
2 parents daeed81 + 1ef2fff commit a38ae20

File tree

5 files changed

+167
-103
lines changed

5 files changed

+167
-103
lines changed

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

Lines changed: 96 additions & 80 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,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

Comments
 (0)