[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>
This commit is contained in:
Nate Biggs
2025-11-06 20:49:28 -08:00
committed by Commit Queue
parent e94ccebe8a
commit 0507f059ee
3 changed files with 82 additions and 11 deletions
@@ -113,24 +113,30 @@ TypeConstantValue createType(CommonElements commonElements, DartType type) {
/// 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 @@ class BitNotOperation implements UnaryOperation {
@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 @@ class NegateOperation implements UnaryOperation {
@override
NumConstantValue? fold(ConstantValue constant) {
constant = _unwrap(constant);
NumConstantValue? fold(ConstantValue constant) {
if (constant is IntConstantValue) {
return createInt(-constant.intValue);
@@ -326,6 +336,8 @@ class NotOperation implements UnaryOperation {
@override
BoolConstantValue? fold(ConstantValue constant) {
constant = _unwrap(constant);
if (constant is BoolConstantValue) {
return createBool(!constant.boolValue);
}
@@ -339,6 +351,9 @@ abstract class BinaryBitOperation implements BinaryOperation {
@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 @@ abstract class ArithmeticNumOperation implements BinaryOperation {
@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 @@ class AddOperation implements BinaryOperation {
@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 @@ abstract class RelationalNumOperation implements BinaryOperation {
@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 @@ class EqualsOperation implements BinaryOperation {
@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 @@ class IdentityOperation implements BinaryOperation {
@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 @@ class IfNullOperation implements BinaryOperation {
@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 @@ class CodeUnitAtOperation implements BinaryOperation {
@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 @@ class RoundOperation implements UnaryOperation {
@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 @@ class ToIntOperation implements UnaryOperation {
@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 @@ class _IndexOperation implements BinaryOperation {
@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 @@ class JavaScriptMapConstant extends MapConstantValue {
}
}
}
ConstantValue _unwrap(ConstantValue value) {
return value is DeferredGlobalConstantValue ? value.referenced : value;
}
@@ -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() {}
+23
View File
@@ -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();
}