@@ -30,7 +30,7 @@ import dotty.tools.dotc.util.Spans.Span
3030import dotty .tools .dotc .report
3131
3232import 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 }
3434import org .scalajs .ir .Names .{ClassName , MethodName , SimpleMethodName }
3535import org .scalajs .ir .OriginalName
3636import org .scalajs .ir .OriginalName .NoOriginalName
@@ -1176,9 +1176,7 @@ class JSCodeGen()(using genCtx: Context) {
11761176
11771177 /** A Match reaching the backend is supposed to be optimized as a switch */
11781178 case mtch : Match =>
1179- // TODO Correctly handle `Match` nodes
1180- // genMatch(mtch, isStat)
1181- js.Throw (js.Null ())
1179+ genMatch(mtch, isStat)
11821180
11831181 case tree : Closure =>
11841182 genClosure(tree)
@@ -2228,6 +2226,127 @@ class JSCodeGen()(using genCtx: Context) {
22282226 js.ArrayValue (arrayTypeRef, genElems)
22292227 }
22302228
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+
22312350 /** Gen JS code for a closure.
22322351 *
22332352 * Input: a `Closure` tree of the form
0 commit comments