fix #34450, implement boolean bitwise operators in dartdevc

These will be added in https://dart-review.googlesource.com/c/sdk/+/74664.

Change-Id: I9712d8f9df72e686dd49e0b5198aa37e17815eb5
Reviewed-on: https://dart-review.googlesource.com/71228
Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
Reviewed-by: Vijay Menon <vsm@google.com>
Commit-Queue: Jenny Messerly <jmesserly@google.com>
diff --git a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
index 9545296..ef0d000 100644
--- a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
+++ b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
@@ -4635,10 +4635,12 @@
 
     if (jsTypeRep.binaryOperationIsPrimitive(leftType, rightType) ||
         leftType == types.stringType && op.type == TokenType.PLUS) {
-      // special cases where we inline the operation
-      // these values are assumed to be non-null (determined by the checker)
-      // TODO(jmesserly): it would be nice to just inline the method from core,
-      // instead of special cases here.
+      // Inline operations on primitive types where possible.
+      // TODO(jmesserly): inline these from dart:core instead of hardcoding
+      // the implementation details here.
+
+      /// Emits an inlined binary operation using the JS [code], adding null
+      /// checks if needed to ensure we throw the appropriate error.
       JS.Expression binary(String code) {
         return js.call(code, [notNull(left), notNull(right)])
           ..sourceInformation = _getLocation(node.operator.offset);
@@ -4648,6 +4650,16 @@
         return _coerceBitOperationResultToUnsigned(node, binary(code));
       }
 
+      /// Similar to [binary] but applies a boolean conversion to the right
+      /// operand, to match the boolean bitwise operators in dart:core.
+      ///
+      /// Short circuiting operators should not be used in [code], because the
+      /// null checks for both operands must happen unconditionally.
+      JS.Expression bitwiseBool(String code) {
+        return js.call(code, [notNull(left), _visitTest(right)])
+          ..sourceInformation = _getLocation(node.operator.offset);
+      }
+
       switch (op.type) {
         case TokenType.TILDE_SLASH:
           // `a ~/ b` is equivalent to `(a / b).truncate()`
@@ -4662,13 +4674,19 @@
           return operatorCall();
 
         case TokenType.AMPERSAND:
-          return bitwise('# & #');
+          return jsTypeRep.isBoolean(leftType)
+              ? bitwiseBool('!!(# & #)')
+              : bitwise('# & #');
 
         case TokenType.BAR:
-          return bitwise('# | #');
+          return jsTypeRep.isBoolean(leftType)
+              ? bitwiseBool('!!(# | #)')
+              : bitwise('# | #');
 
         case TokenType.CARET:
-          return bitwise('# ^ #');
+          return jsTypeRep.isBoolean(leftType)
+              ? bitwiseBool('# !== #')
+              : bitwise('# ^ #');
 
         case TokenType.GT_GT:
           int shiftCount = _asIntInRange(right, 0, 31);
diff --git a/pkg/dev_compiler/lib/src/compiler/js_typerep.dart b/pkg/dev_compiler/lib/src/compiler/js_typerep.dart
index dda2d29..a7bd241 100644
--- a/pkg/dev_compiler/lib/src/compiler/js_typerep.dart
+++ b/pkg/dev_compiler/lib/src/compiler/js_typerep.dart
@@ -89,6 +89,8 @@
 
   bool isNumber(DartType type) => typeFor(type) is JSNumber;
 
+  bool isBoolean(DartType type) => typeFor(type) is JSBoolean;
+
   /// Is this type known to be represented as Object or Null in JS.
   bool isObjectOrNull(DartType t) {
     var rep = typeFor(t);
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index b23e36f..ff4086c 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -4043,10 +4043,12 @@
 
       if (_typeRep.binaryOperationIsPrimitive(leftType, rightType) ||
           leftType == types.stringType && op == '+') {
-        // special cases where we inline the operation
-        // these values are assumed to be non-null (determined by the checker)
-        // TODO(jmesserly): it would be nice to just inline the method from core,
-        // instead of special cases here.
+        // Inline operations on primitive types where possible.
+        // TODO(jmesserly): inline these from dart:core instead of hardcoding
+        // the implementation details here.
+
+        /// Emits an inlined binary operation using the JS [code], adding null
+        /// checks if needed to ensure we throw the appropriate error.
         JS.Expression binary(String code) {
           return js.call(code, [notNull(left), notNull(right)]);
         }
@@ -4055,6 +4057,15 @@
           return _coerceBitOperationResultToUnsigned(node, binary(code));
         }
 
+        /// Similar to [binary] but applies a boolean conversion to the right
+        /// operand, to match the boolean bitwise operators in dart:core.
+        ///
+        /// Short circuiting operators should not be used in [code], because the
+        /// null checks for both operands must happen unconditionally.
+        JS.Expression bitwiseBool(String code) {
+          return js.call(code, [notNull(left), _visitTest(right)]);
+        }
+
         switch (op) {
           case '~/':
             // `a ~/ b` is equivalent to `(a / b).truncate()`
@@ -4070,13 +4081,19 @@
             return _emitOperatorCall(left, target, op, [right]);
 
           case '&':
-            return bitwise('# & #');
+            return _typeRep.isBoolean(leftType)
+                ? bitwiseBool('!!(# & #)')
+                : bitwise('# & #');
 
           case '|':
-            return bitwise('# | #');
+            return _typeRep.isBoolean(leftType)
+                ? bitwiseBool('!!(# | #)')
+                : bitwise('# | #');
 
           case '^':
-            return bitwise('# ^ #');
+            return _typeRep.isBoolean(leftType)
+                ? bitwiseBool('# !== #')
+                : bitwise('# ^ #');
 
           case '>>':
             int shiftCount = _asIntInRange(right, 0, 31);