@@ -646,7 +646,40 @@ private[optimizer] abstract class OptimizerCore(
646
646
JSUnaryOp (op, transformExpr(lhs))
647
647
648
648
case JSBinaryOp (op, lhs, rhs) =>
649
- JSBinaryOp (op, transformExpr(lhs), transformExpr(rhs))
649
+ val newTree = JSBinaryOp (op, transformExpr(lhs), transformExpr(rhs))
650
+
651
+ // Introduce casts for some idioms that are guaranteed to return certain types
652
+
653
+ // Is `arg` guaranteed to evaluate to a JS `number` (and hence, not a `bigint`)?
654
+ def isJSNumber (arg : Tree ): Boolean = arg.tpe match {
655
+ case IntType | DoubleType | ByteType | ShortType | FloatType => true
656
+ case _ => false
657
+ }
658
+
659
+ newTree match {
660
+ /* Unless it throws, `x | y` returns either a signed 32-bit integer
661
+ * (an `Int`) or a bigint.
662
+ *
663
+ * The only case in which it returns a bigint is when both arguments
664
+ * are (convertible to) bigint's. Custom objects can be converted to
665
+ * bigint's if their `valueOf()` method returns a bigint.
666
+ *
667
+ * Primitive numbers cannot be implicitly converted to bigint's.
668
+ * `x | y` throws if one side is a number and the other is (converted
669
+ * to) a bigint. Therefore, if at least one of the arguments is known
670
+ * to be a primitive number, we know that `x | y` will return a
671
+ * signed 32-bit integer (or throw).
672
+ */
673
+ case JSBinaryOp (JSBinaryOp .| , x, y) if isJSNumber(x) || isJSNumber(y) =>
674
+ makeCast(newTree, IntType )
675
+
676
+ // >>> always returns a positive number in the unsigned 32-bit range (it rejects bigints)
677
+ case JSBinaryOp (JSBinaryOp .>>> , _, _) =>
678
+ makeCast(newTree, DoubleType )
679
+
680
+ case _ =>
681
+ newTree
682
+ }
650
683
651
684
case JSArrayConstr (items) =>
652
685
JSArrayConstr (transformExprsOrSpreads(items))
0 commit comments