[dart2js] Fix error when comparing constants referenced across deferred import boundaries.

Constant folding was considering strings (or other primitives) unequal if one of them was a DeferredGlobalConstantValue and the other was not. This was producing incorrect results in the cases where the underlying constants were in fact equal.

For the purposes of constant folding equality checks, we should just ignore the deferred boundary and compare the constants themselves.

Fixes: https://github.com/dart-lang/sdk/issues/61833
Change-Id: Ibbd950ae910ee2377353f5fae8d9bea5c64d9d84
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/460020
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Mayank Patke <fishythefish@google.com>
diff --git a/pkg/compiler/lib/src/constants/constant_system.dart b/pkg/compiler/lib/src/constants/constant_system.dart
index a11747f..d3a17f7 100644
--- a/pkg/compiler/lib/src/constants/constant_system.dart
+++ b/pkg/compiler/lib/src/constants/constant_system.dart
@@ -113,24 +113,30 @@
 /// typeof(X) === "number" && Math.floor(X) === X
 ///
 /// We consistently match that runtime semantics at compile time as well.
-bool isInt(ConstantValue constant) =>
-    constant is IntConstantValue ||
-    constant.isMinusZero ||
-    constant.isPositiveInfinity ||
-    constant.isNegativeInfinity;
+bool isInt(ConstantValue constant) {
+  constant = _unwrap(constant);
+
+  return constant is IntConstantValue ||
+      constant.isMinusZero ||
+      constant.isPositiveInfinity ||
+      constant.isNegativeInfinity;
+}
 
 /// Returns true if the [constant] is a double at runtime.
-bool isDouble(ConstantValue constant) =>
-    constant is DoubleConstantValue && !constant.isMinusZero;
+bool isDouble(ConstantValue constant) {
+  constant = _unwrap(constant);
+  return constant is DoubleConstantValue && !constant.isMinusZero;
+}
 
 /// Returns true if the [constant] is a string at runtime.
-bool isString(ConstantValue constant) => constant is StringConstantValue;
+bool isString(ConstantValue constant) =>
+    _unwrap(constant) is StringConstantValue;
 
 /// Returns true if the [constant] is a boolean at runtime.
-bool isBool(ConstantValue constant) => constant is BoolConstantValue;
+bool isBool(ConstantValue constant) => _unwrap(constant) is BoolConstantValue;
 
 /// Returns true if the [constant] is null at runtime.
-bool isNull(ConstantValue constant) => constant is NullConstantValue;
+bool isNull(ConstantValue constant) => _unwrap(constant) is NullConstantValue;
 
 bool isSubtype(DartTypes types, DartType s, DartType t) {
   // At runtime, an integer is both an integer and a double: the
@@ -277,6 +283,8 @@
 
   @override
   IntConstantValue? fold(ConstantValue constant) {
+    constant = _unwrap(constant);
+
     if (isInt(constant)) {
       // In JavaScript we don't check for -0 and treat it as if it was zero.
       if (constant.isMinusZero) {
@@ -299,6 +307,8 @@
 
   @override
   NumConstantValue? fold(ConstantValue constant) {
+    constant = _unwrap(constant);
+
     NumConstantValue? fold(ConstantValue constant) {
       if (constant is IntConstantValue) {
         return createInt(-constant.intValue);
@@ -326,6 +336,8 @@
 
   @override
   BoolConstantValue? fold(ConstantValue constant) {
+    constant = _unwrap(constant);
+
     if (constant is BoolConstantValue) {
       return createBool(!constant.boolValue);
     }
@@ -339,6 +351,9 @@
 
   @override
   IntConstantValue? fold(ConstantValue left, ConstantValue right) {
+    left = _unwrap(left);
+    right = _unwrap(right);
+
     IntConstantValue? fold(ConstantValue left, ConstantValue right) {
       if (left is IntConstantValue && right is IntConstantValue) {
         BigInt? resultValue = foldInts(left.intValue, right.intValue);
@@ -495,6 +510,9 @@
 
   @override
   NumConstantValue? fold(ConstantValue left, ConstantValue right) {
+    left = _unwrap(left);
+    right = _unwrap(right);
+
     NumConstantValue? fold(ConstantValue left, ConstantValue right) {
       if (left is NumConstantValue && right is NumConstantValue) {
         Object? foldedValue;
@@ -633,6 +651,9 @@
 
   @override
   ConstantValue? fold(ConstantValue left, ConstantValue right) {
+    left = _unwrap(left);
+    right = _unwrap(right);
+
     ConstantValue? fold(ConstantValue left, ConstantValue right) {
       if (left is IntConstantValue && right is IntConstantValue) {
         BigInt result = left.intValue + right.intValue;
@@ -661,6 +682,8 @@
 
   @override
   BoolConstantValue? fold(ConstantValue left, ConstantValue right) {
+    left = _unwrap(left);
+    right = _unwrap(right);
     if (left is NumConstantValue && right is NumConstantValue) {
       bool foldedValue;
       if (left is IntConstantValue && right is IntConstantValue) {
@@ -737,6 +760,8 @@
 
   @override
   BoolConstantValue? fold(ConstantValue left, ConstantValue right) {
+    left = _unwrap(left);
+    right = _unwrap(right);
     // Numbers need to be treated specially because: NaN != NaN, -0.0 == 0.0,
     // and 1 == 1.0.
     if (left is IntConstantValue && right is IntConstantValue) {
@@ -770,6 +795,8 @@
 
   @override
   BoolConstantValue fold(ConstantValue left, ConstantValue right) {
+    left = _unwrap(left);
+    right = _unwrap(right);
     // NaNs are not identical to anything. This is a web platform departure from
     // standard Dart. If we make `identical(double.nan, double.nan)` be `true`,
     // this constant folding will be incorrect. TODOs below for cross-reference.
@@ -799,7 +826,9 @@
 
   @override
   ConstantValue fold(ConstantValue left, ConstantValue right) {
-    if (left is NullConstantValue) return right;
+    if (_unwrap(left) is NullConstantValue) {
+      return right;
+    }
     return left;
   }
 }
@@ -812,6 +841,8 @@
 
   @override
   NumConstantValue? fold(ConstantValue left, ConstantValue right) {
+    left = _unwrap(left);
+    right = _unwrap(right);
     if (left is StringConstantValue && right is IntConstantValue) {
       String string = left.stringValue;
       int index = right.intValue.toInt();
@@ -831,6 +862,7 @@
 
   @override
   NumConstantValue? fold(ConstantValue constant) {
+    constant = _unwrap(constant);
     // Be careful to round() only values that do not throw on either the host or
     // target platform.
     NumConstantValue? tryToRound(double value) {
@@ -873,6 +905,7 @@
 
   @override
   NumConstantValue? fold(ConstantValue constant) {
+    constant = _unwrap(constant);
     if (constant is IntConstantValue) {
       double value = constant.doubleValue;
       // The code below is written to work for any `double`, even though
@@ -899,6 +932,9 @@
 
   @override
   ConstantValue? fold(ConstantValue left, ConstantValue right) {
+    left = _unwrap(left);
+    right = _unwrap(right);
+
     if (left is ListConstantValue) {
       if (right is IntConstantValue) {
         List<ConstantValue> entries = left.entries;
@@ -1006,3 +1042,7 @@
     }
   }
 }
+
+ConstantValue _unwrap(ConstantValue value) {
+  return value is DeferredGlobalConstantValue ? value.referenced : value;
+}
diff --git a/tests/web/regress/issue/61833_helper.dart b/tests/web/regress/issue/61833_helper.dart
new file mode 100644
index 0000000..bbf6cb9
--- /dev/null
+++ b/tests/web/regress/issue/61833_helper.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2025, 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.
+
+final String str = 'hello';
+final bool? nullBool = null;
+
+void foo() {}
diff --git a/tests/web/regress/issue/61833_test.dart b/tests/web/regress/issue/61833_test.dart
new file mode 100644
index 0000000..d09ce71
--- /dev/null
+++ b/tests/web/regress/issue/61833_test.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2025, 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.
+
+import 'package:async_helper/async_helper.dart';
+import 'package:expect/expect.dart';
+
+import '61833_helper.dart' deferred as d;
+
+final String localStr = 'hello';
+
+Future<void> main() async {
+  asyncStart();
+  await d.loadLibrary();
+
+  // Don't use 'Expect' APIs since constants have to be direct
+  // inputs to the operators being tested.
+  Expect.isTrue(d.str == localStr);
+  Expect.isTrue(identical(d.str, localStr));
+  Expect.isTrue(d.nullBool ?? true);
+  d.foo();
+  asyncEnd();
+}