dart2js: generate simple '==' in more cases

Change-Id: I9f8d674f8346b6c3ecf5232c369b3953053d261f
Reviewed-on: https://dart-review.googlesource.com/74964
Commit-Queue: Stephen Adams <sra@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/compiler/lib/src/ssa/codegen_helpers.dart b/pkg/compiler/lib/src/ssa/codegen_helpers.dart
index ca08263..6608085 100644
--- a/pkg/compiler/lib/src/ssa/codegen_helpers.dart
+++ b/pkg/compiler/lib/src/ssa/codegen_helpers.dart
@@ -82,18 +82,36 @@
     return node;
   }
 
+  /// Returns the single JavaScript comparison (`==` or `===`) if that
+  /// implements `identical(left, right)`, or returns `null` if a more complex
+  /// expression is needed.
   String simpleOp(HInstruction left, HInstruction right) {
-    // Returns the single identity comparison (== or ===) or null if a more
-    // complex expression is required.
     AbstractValue leftType = left.instructionType;
     AbstractValue rightType = right.instructionType;
     if (_abstractValueDomain.canBeNull(leftType) &&
         _abstractValueDomain.canBeNull(rightType)) {
-      if (left.isConstantNull() ||
-          right.isConstantNull() ||
-          (left.isPrimitive(_abstractValueDomain) && leftType == rightType)) {
+      // Can't use `===` on Dart `null` since it is implemented by JavaScript
+      // `null` and `undefined`.
+      if (left.isConstantNull() || right.isConstantNull()) {
         return '==';
       }
+      if (_abstractValueDomain.isNumberOrNull(leftType) &&
+          _abstractValueDomain.isNumberOrNull(rightType)) {
+        return '==';
+      }
+      if (_abstractValueDomain.isStringOrNull(leftType) &&
+          _abstractValueDomain.isStringOrNull(rightType)) {
+        return '==';
+      }
+      if (_abstractValueDomain.isBooleanOrNull(leftType) &&
+          _abstractValueDomain.isBooleanOrNull(rightType)) {
+        return '==';
+      }
+
+      // TODO(34439): There are more cases that can compile to `==` without
+      // triggering a conversion in the JavaScript evaluation. `==` will work
+      // for most Dart objects, but we have to ensure neither side can be a
+      // JavaScript Number, String, Symbol or Boolean.
       return null;
     }
     return '===';
diff --git a/tests/compiler/dart2js/codegen/equals_test.dart b/tests/compiler/dart2js/codegen/equals_test.dart
new file mode 100644
index 0000000..9395108
--- /dev/null
+++ b/tests/compiler/dart2js/codegen/equals_test.dart
@@ -0,0 +1,84 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library equals_test;
+
+import 'dart:async';
+import 'package:async_helper/async_helper.dart';
+import '../helpers/compiler_helper.dart';
+
+const String TEST1 = r"""
+foo(int a) {
+  return a == null;
+  // present: 'a == null'
+  // absent: 'eq'
+}
+""";
+
+const String TEST2 = r"""
+foo(int a) {
+  return a == 123;
+  // present: 'a === 123'
+  // absent: 'eq'
+}
+""";
+
+const String TEST3 = r"""
+foo(int a, int b) {
+  return a == b;
+  // present: 'a == b'
+  // absent: 'eq'
+  // absent: '==='
+}
+""";
+
+const String TEST4 = r"""
+foo(String a, String b) {
+  return a == b;
+  // present: 'a == b'
+  // absent: 'eq'
+  // absent: '==='
+}
+""";
+
+// Comparable includes String and int, so can't be compared with `a == b` since
+// that will convert an operand to make `2 =="2"` true.
+const String TEST5 = r"""
+foo(Comparable a, Comparable b) {
+  return a == b;
+  // present: 'a === b'
+  // present: 'a == null'
+  // present: 'b == null'
+  // absent: 'eq'
+  // absent: 'a == b'
+}
+""";
+
+const String TEST6 = r"""
+foo(dynamic a, dynamic b) {
+  return a == b;
+  // present: 'eq'
+  // absent: '=='
+}
+""";
+
+main() {
+  runTests() async {
+    Future check(String test) {
+      return compile(test, entry: 'foo', check: checkerForAbsentPresent(test));
+    }
+
+    await check(TEST1);
+    await check(TEST2);
+    await check(TEST3);
+    await check(TEST4);
+    await check(TEST5);
+    await check(TEST6);
+  }
+
+  asyncTest(() async {
+    print('--test from kernel------------------------------------------------');
+    await runTests();
+  });
+}