@@ -30,7 +30,7 @@ import dotty.tools.dotc.util.Spans.Span
30
30
import dotty .tools .dotc .report
31
31
32
32
import org .scalajs .ir
33
- import org .scalajs .ir .{ClassKind , Position , Trees => js , Types => jstpe }
33
+ import org .scalajs .ir .{ClassKind , Position , Names => jsNames , Trees => js , Types => jstpe }
34
34
import org .scalajs .ir .Names .{ClassName , MethodName , SimpleMethodName }
35
35
import org .scalajs .ir .OriginalName
36
36
import org .scalajs .ir .OriginalName .NoOriginalName
@@ -1176,9 +1176,7 @@ class JSCodeGen()(using genCtx: Context) {
1176
1176
1177
1177
/** A Match reaching the backend is supposed to be optimized as a switch */
1178
1178
case mtch : Match =>
1179
- // TODO Correctly handle `Match` nodes
1180
- // genMatch(mtch, isStat)
1181
- js.Throw (js.Null ())
1179
+ genMatch(mtch, isStat)
1182
1180
1183
1181
case tree : Closure =>
1184
1182
genClosure(tree)
@@ -2228,6 +2226,127 @@ class JSCodeGen()(using genCtx: Context) {
2228
2226
js.ArrayValue (arrayTypeRef, genElems)
2229
2227
}
2230
2228
2229
+ /** Gen JS code for a switch-`Match`, which is translated into an IR `js.Match`. */
2230
+ def genMatch (tree : Tree , isStat : Boolean ): js.Tree = {
2231
+ implicit val pos = tree.span
2232
+ val Match (selector, cases) = tree
2233
+
2234
+ def abortMatch (msg : String ): Nothing =
2235
+ throw new FatalError (s " $msg in switch-like pattern match at ${tree.span}: $tree" )
2236
+
2237
+ /* Although GenBCode adapts the scrutinee and the cases to `int`, only
2238
+ * true `int`s can reach the back-end, as asserted by the String-switch
2239
+ * transformation in `cleanup`. Therefore, we do not adapt, preserving
2240
+ * the `string`s and `null`s that come out of the pattern matching in
2241
+ * Scala 2.13.2+.
2242
+ */
2243
+ val genSelector = genExpr(selector)
2244
+
2245
+ // Sanity check: we can handle Ints and Strings (including `null`s), but nothing else
2246
+ genSelector.tpe match {
2247
+ case jstpe.IntType | jstpe.ClassType (jsNames.BoxedStringClass ) | jstpe.NullType | jstpe.NothingType =>
2248
+ // ok
2249
+ case _ =>
2250
+ abortMatch(s " Invalid selector type ${genSelector.tpe}" )
2251
+ }
2252
+
2253
+ val resultType =
2254
+ if (isStat) jstpe.NoType
2255
+ else toIRType(tree.tpe)
2256
+
2257
+ var clauses : List [(List [js.Tree ], js.Tree )] = Nil
2258
+ var optDefaultClause : Option [js.Tree ] = None
2259
+
2260
+ for (caze @ CaseDef (pat, guard, body) <- cases) {
2261
+ if (guard != EmptyTree )
2262
+ abortMatch(" Found a case guard" )
2263
+
2264
+ val genBody = genStatOrExpr(body, isStat)
2265
+
2266
+ pat match {
2267
+ case lit : Literal =>
2268
+ clauses = (List (genExpr(lit)), genBody) :: clauses
2269
+ case Ident (nme.WILDCARD ) =>
2270
+ optDefaultClause = Some (genBody)
2271
+ case Alternative (alts) =>
2272
+ val genAlts = alts.map {
2273
+ case lit : Literal => genExpr(lit)
2274
+ case _ => abortMatch(" Invalid case in alternative" )
2275
+ }
2276
+ clauses = (genAlts, genBody) :: clauses
2277
+ case _ =>
2278
+ abortMatch(" Invalid case pattern" )
2279
+ }
2280
+ }
2281
+
2282
+ clauses = clauses.reverse
2283
+ val defaultClause = optDefaultClause.getOrElse {
2284
+ throw new AssertionError (" No elseClause in pattern match" )
2285
+ }
2286
+
2287
+ /* Builds a `js.Match`, but simplifies it to a `js.If` if there is only
2288
+ * one case with one alternative, and to a `js.Block` if there is no case
2289
+ * at all. This happens in practice in the standard library. Having no
2290
+ * case is a typical product of `match`es that are full of
2291
+ * `case n if ... =>`, which are used instead of `if` chains for
2292
+ * convenience and/or readability.
2293
+ *
2294
+ * When no optimization applies, and any of the case values is not a
2295
+ * literal int, we emit a series of `if..else` instead of a `js.Match`.
2296
+ * This became necessary in 2.13.2 with strings and nulls.
2297
+ *
2298
+ * Note that dotc has not adopted String-switch-Matches yet, so these code
2299
+ * paths are dead code at the moment. However, they already existed in the
2300
+ * scalac, so were ported, to be immediately available and working when
2301
+ * dotc starts emitting switch-Matches on Strings.
2302
+ */
2303
+ def isInt (tree : js.Tree ): Boolean = tree.tpe == jstpe.IntType
2304
+
2305
+ clauses match {
2306
+ case Nil =>
2307
+ // Completely remove the Match. Preserve the side-effects of `genSelector`.
2308
+ js.Block (exprToStat(genSelector), defaultClause)
2309
+
2310
+ case (uniqueAlt :: Nil , caseRhs) :: Nil =>
2311
+ /* Simplify the `match` as an `if`, so that the optimizer has less
2312
+ * work to do, and we emit less code at the end of the day.
2313
+ * Use `Int_==` instead of `===` if possible, since it is a common case.
2314
+ */
2315
+ val op =
2316
+ if (isInt(genSelector) && isInt(uniqueAlt)) js.BinaryOp .Int_==
2317
+ else js.BinaryOp .===
2318
+ js.If (js.BinaryOp (op, genSelector, uniqueAlt), caseRhs, defaultClause)(resultType)
2319
+
2320
+ case _ =>
2321
+ if (isInt(genSelector) &&
2322
+ clauses.forall(_._1.forall(_.isInstanceOf [js.IntLiteral ]))) {
2323
+ // We have int literals only: use a js.Match
2324
+ val intClauses = clauses.asInstanceOf [List [(List [js.IntLiteral ], js.Tree )]]
2325
+ js.Match (genSelector, intClauses, defaultClause)(resultType)
2326
+ } else {
2327
+ // We have other stuff: generate an if..else chain
2328
+ val (tempSelectorDef, tempSelectorRef) = genSelector match {
2329
+ case varRef : js.VarRef =>
2330
+ (js.Skip (), varRef)
2331
+ case _ =>
2332
+ val varDef = js.VarDef (freshLocalIdent(), NoOriginalName ,
2333
+ genSelector.tpe, mutable = false , genSelector)
2334
+ (varDef, varDef.ref)
2335
+ }
2336
+ val ifElseChain = clauses.foldRight(defaultClause) { (caze, elsep) =>
2337
+ val conds = caze._1.map { caseValue =>
2338
+ js.BinaryOp (js.BinaryOp .=== , tempSelectorRef, caseValue)
2339
+ }
2340
+ val cond = conds.reduceRight[js.Tree ] { (left, right) =>
2341
+ js.If (left, js.BooleanLiteral (true ), right)(jstpe.BooleanType )
2342
+ }
2343
+ js.If (cond, caze._2, elsep)(resultType)
2344
+ }
2345
+ js.Block (tempSelectorDef, ifElseChain)
2346
+ }
2347
+ }
2348
+ }
2349
+
2231
2350
/** Gen JS code for a closure.
2232
2351
*
2233
2352
* Input: a `Closure` tree of the form
0 commit comments