Issue 51567. Implement constant equality as primitive equality

Bug: https://github.com/dart-lang/sdk/issues/51567
Change-Id: I7821598a761573519205b0ea06b13f433639282b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/286241
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Konstantin Shcheglov 2023-03-01 20:58:57 +00:00 committed by Commit Queue
parent 16e935ebad
commit ad15774558
6 changed files with 374 additions and 49 deletions

View file

@ -544,6 +544,7 @@ class ConstantVisitor extends UnifyingAstVisitor<DartObjectImpl> {
_substitution = substitution {
_dartObjectComputer = DartObjectComputer(
typeSystem,
_library.featureSet,
_errorReporter,
);
}
@ -1566,11 +1567,12 @@ class ConstantVisitor extends UnifyingAstVisitor<DartObjectImpl> {
/// class and for collecting errors during evaluation.
class DartObjectComputer {
final TypeSystemImpl _typeSystem;
final FeatureSet _featureSet;
/// The error reporter that we are using to collect errors.
final ErrorReporter _errorReporter;
DartObjectComputer(this._typeSystem, this._errorReporter);
DartObjectComputer(this._typeSystem, this._featureSet, this._errorReporter);
DartObjectImpl? add(BinaryExpression node, DartObjectImpl? leftOperand,
DartObjectImpl? rightOperand) {
@ -1698,7 +1700,7 @@ class DartObjectComputer {
DartObjectImpl? rightOperand) {
if (leftOperand != null && rightOperand != null) {
try {
return leftOperand.equalEqual(_typeSystem, rightOperand);
return leftOperand.equalEqual(_typeSystem, _featureSet, rightOperand);
} on EvaluationException catch (exception) {
_errorReporter.reportErrorForNode(exception.errorCode, node);
}
@ -1770,7 +1772,8 @@ class DartObjectComputer {
DartObjectImpl? rightOperand) {
if (leftOperand != null && rightOperand != null) {
try {
return leftOperand.lazyEqualEqual(_typeSystem, rightOperand);
return leftOperand.lazyEqualEqual(
_typeSystem, _featureSet, rightOperand);
} on EvaluationException catch (exception) {
_errorReporter.reportErrorForNode(exception.errorCode, node);
}
@ -1878,7 +1881,7 @@ class DartObjectComputer {
DartObjectImpl? rightOperand) {
if (leftOperand != null && rightOperand != null) {
try {
return leftOperand.notEqual(_typeSystem, rightOperand);
return leftOperand.notEqual(_typeSystem, _featureSet, rightOperand);
} on EvaluationException catch (exception) {
_errorReporter.reportErrorForNode(exception.errorCode, node);
}

View file

@ -429,7 +429,10 @@ class DartObjectImpl implements DartObject {
/// Throws an [EvaluationException] if the operator is not appropriate for an
/// object of this kind.
DartObjectImpl equalEqual(
TypeSystemImpl typeSystem, DartObjectImpl rightOperand) {
TypeSystemImpl typeSystem,
FeatureSet featureSet,
DartObjectImpl rightOperand,
) {
if (isNull || rightOperand.isNull) {
return DartObjectImpl(
typeSystem,
@ -439,12 +442,22 @@ class DartObjectImpl implements DartObject {
: BoolState.FALSE_STATE,
);
}
if (isBoolNumStringOrNull) {
return DartObjectImpl(
typeSystem,
typeSystem.typeProvider.boolType,
state.equalEqual(typeSystem, rightOperand.state),
);
if (featureSet.isEnabled(Feature.patterns)) {
if (state is DoubleState || hasPrimitiveEquality(featureSet)) {
return DartObjectImpl(
typeSystem,
typeSystem.typeProvider.boolType,
state.equalEqual(typeSystem, rightOperand.state),
);
}
} else {
if (isBoolNumStringOrNull) {
return DartObjectImpl(
typeSystem,
typeSystem.typeProvider.boolType,
state.equalEqual(typeSystem, rightOperand.state),
);
}
}
throw EvaluationException(
CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING);
@ -598,7 +611,10 @@ class DartObjectImpl implements DartObject {
/// Throws an [EvaluationException] if the operator is not appropriate for an
/// object of this kind.
DartObjectImpl lazyEqualEqual(
TypeSystemImpl typeSystem, DartObjectImpl rightOperand) {
TypeSystemImpl typeSystem,
FeatureSet featureSet,
DartObjectImpl rightOperand,
) {
if (isNull || rightOperand.isNull) {
return DartObjectImpl(
typeSystem,
@ -608,12 +624,22 @@ class DartObjectImpl implements DartObject {
: BoolState.FALSE_STATE,
);
}
if (isBoolNumStringOrNull) {
return DartObjectImpl(
typeSystem,
typeSystem.typeProvider.boolType,
state.lazyEqualEqual(typeSystem, rightOperand.state),
);
if (featureSet.isEnabled(Feature.patterns)) {
if (state is DoubleState || hasPrimitiveEquality(featureSet)) {
return DartObjectImpl(
typeSystem,
typeSystem.typeProvider.boolType,
state.equalEqual(typeSystem, rightOperand.state),
);
}
} else {
if (isBoolNumStringOrNull) {
return DartObjectImpl(
typeSystem,
typeSystem.typeProvider.boolType,
state.equalEqual(typeSystem, rightOperand.state),
);
}
}
throw EvaluationException(
CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING);
@ -739,8 +765,12 @@ class DartObjectImpl implements DartObject {
/// Throws an [EvaluationException] if the operator is not appropriate for an
/// object of this kind.
DartObjectImpl notEqual(
TypeSystemImpl typeSystem, DartObjectImpl rightOperand) {
return equalEqual(typeSystem, rightOperand).logicalNot(typeSystem);
TypeSystemImpl typeSystem,
FeatureSet featureSet,
DartObjectImpl rightOperand,
) {
return equalEqual(typeSystem, featureSet, rightOperand)
.logicalNot(typeSystem);
}
/// Return the result of converting this object to a 'String'.

View file

@ -29,6 +29,166 @@ main() {
@reflectiveTest
class ConstantVisitorTest extends ConstantVisitorTestSupport
with ConstantVisitorTestCases {
test_equalEqual_double_object() async {
await assertNoErrorsInCode('''
const v = 1.2 == Object();
''');
final value = _evaluateConstant('v');
assertDartObjectText(value, r'''
bool false
''');
}
test_equalEqual_int_int_false() async {
await assertNoErrorsInCode('''
const v = 1 == 2;
''');
final value = _evaluateConstant('v');
assertDartObjectText(value, r'''
bool false
''');
}
test_equalEqual_int_int_true() async {
await assertNoErrorsInCode('''
const v = 1 == 1;
''');
final value = _evaluateConstant('v');
assertDartObjectText(value, r'''
bool true
''');
}
test_equalEqual_int_null() async {
await assertNoErrorsInCode('''
const int? a = 1;
const v = a == null;
''');
final value = _evaluateConstant('v');
assertDartObjectText(value, r'''
bool false
''');
}
test_equalEqual_int_object() async {
await assertNoErrorsInCode('''
const v = 1 == Object();
''');
final value = _evaluateConstant('v');
assertDartObjectText(value, r'''
bool false
''');
}
test_equalEqual_int_userClass() async {
await assertNoErrorsInCode('''
class A {
const A();
}
const v = 1 == A();
''');
final value = _evaluateConstant('v');
assertDartObjectText(value, r'''
bool false
''');
}
test_equalEqual_null_object() async {
await assertNoErrorsInCode('''
const Object? a = null;
const v = a == Object();
''');
final value = _evaluateConstant('v');
assertDartObjectText(value, r'''
bool false
''');
}
test_equalEqual_string_object() async {
await assertNoErrorsInCode('''
const v = 'foo' == Object();
''');
final value = _evaluateConstant('v');
assertDartObjectText(value, r'''
bool false
''');
}
test_equalEqual_userClass_hasEqEq() async {
await assertErrorsInCode('''
class A {
const A();
bool operator ==(other) => false;
}
const v = A() == 0;
''', [
error(CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING, 72, 8),
]);
// TODO(scheglov) check the invalid value
}
test_equalEqual_userClass_hasHashCode() async {
await assertErrorsInCode('''
class A {
const A();
int get hashCode => 0;
}
const v = A() == 0;
''', [
error(CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING, 61, 8),
]);
// TODO(scheglov) check the invalid value
}
test_equalEqual_userClass_hasPrimitiveEquality_false() async {
await assertNoErrorsInCode('''
class A {
final int f;
const A(this.f);
}
const v = A(0) == 0;
''');
final value = _evaluateConstant('v');
assertDartObjectText(value, r'''
bool false
''');
}
test_equalEqual_userClass_hasPrimitiveEquality_language219() async {
await assertErrorsInCode('''
// @dart = 2.19
class A {
const A();
}
const v = A() == 0;
''', [
error(CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING, 52, 8),
]);
_evaluateConstantOrNull('v', errorCodes: [
CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING,
]);
}
test_equalEqual_userClass_hasPrimitiveEquality_true() async {
await assertNoErrorsInCode('''
class A {
final int f;
const A(this.f);
}
const v = A(0) == A(0);
''');
final value = _evaluateConstant('v');
assertDartObjectText(value, r'''
bool true
''');
}
test_hasPrimitiveEquality_bool() async {
await assertNoErrorsInCode('''
const v = true;
@ -2314,6 +2474,19 @@ const a = const A<int?>();
);
}
test_assertInitializer_intInDoubleContext_true() async {
await assertNoErrorsInCode('''
class A {
const A(double x): assert((x + 3) / 2 == 1.5);
}
const v = const A(0);
''');
final value = _evaluateConstant('v');
assertDartObjectText(value, r'''
A
''');
}
test_fieldInitializer_functionReference_withTypeParameter() async {
await resolveTestCode('''
void g<U>(U a) {}
@ -2519,16 +2692,6 @@ const a = const A(1);
);
}
test_assertInitializer_intInDoubleContext_true() async {
await resolveTestCode('''
class A {
const A(double x): assert((x + 3) / 2 == 1.5);
}
const a = const A(0);
''');
_assertValidConstant('a');
}
test_assertInitializer_simple_false() async {
await resolveTestCode('''
class A {

View file

@ -2,6 +2,7 @@
// 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:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
@ -11,6 +12,7 @@ import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../generated/test_analysis_context.dart';
import '../../../util/feature_sets.dart';
main() {
defineReflectiveSuite(() {
@ -27,6 +29,7 @@ final Matcher throwsEvaluationException =
class DartObjectImplTest {
late final TypeProvider _typeProvider;
late final TypeSystemImpl _typeSystem;
FeatureSet _featureSet = FeatureSets.latestWithExperiments;
void setUp() {
var analysisContext = TestAnalysisContext();
@ -358,14 +361,29 @@ class DartObjectImplTest {
_assertEqualEqual(_boolValue(false), _doubleValue(2.0), _doubleValue(4.0));
}
void test_equalEqual_double_false_language219() {
_featureSet = FeatureSets.language_2_19;
_assertEqualEqual(_boolValue(false), _doubleValue(2.0), _doubleValue(4.0));
}
void test_equalEqual_double_true() {
_assertEqualEqual(_boolValue(true), _doubleValue(2.0), _doubleValue(2.0));
}
void test_equalEqual_double_true_language219() {
_featureSet = FeatureSets.language_2_19;
_assertEqualEqual(_boolValue(true), _doubleValue(2.0), _doubleValue(2.0));
}
void test_equalEqual_double_unknown() {
_assertEqualEqual(_boolValue(null), _doubleValue(1.0), _doubleValue(null));
}
void test_equalEqual_double_unknown_language219() {
_featureSet = FeatureSets.language_2_19;
_assertEqualEqual(_boolValue(null), _doubleValue(1.0), _doubleValue(null));
}
void test_equalEqual_int_false() {
_assertEqualEqual(_boolValue(false), _intValue(-5), _intValue(5));
}
@ -378,7 +396,8 @@ class DartObjectImplTest {
_assertEqualEqual(_boolValue(null), _intValue(null), _intValue(3));
}
void test_equalEqual_list_empty() {
void test_equalEqual_list_error_language219() {
_featureSet = FeatureSets.language_2_19;
_assertEqualEqual(
null,
_listValue(_typeProvider.intType, []),
@ -386,15 +405,16 @@ class DartObjectImplTest {
);
}
void test_equalEqual_list_false() {
void test_equalEqual_list_true() {
_assertEqualEqual(
null,
_boolValue(true),
_listValue(_typeProvider.intType, []),
_listValue(_typeProvider.intType, []),
);
}
void test_equalEqual_map_empty() {
void test_equalEqual_map_error_language219() {
_featureSet = FeatureSets.language_2_19;
_assertEqualEqual(
null,
_mapValue(_typeProvider.intType, _typeProvider.stringType, []),
@ -402,9 +422,9 @@ class DartObjectImplTest {
);
}
void test_equalEqual_map_false() {
void test_equalEqual_map_true() {
_assertEqualEqual(
null,
_boolValue(true),
_mapValue(_typeProvider.intType, _typeProvider.stringType, []),
_mapValue(_typeProvider.intType, _typeProvider.stringType, []),
);
@ -1582,11 +1602,21 @@ class DartObjectImplTest {
_assertNotEqual(_boolValue(null), _boolValue(null), _boolValue(false));
}
void test_notEqual_double_false() {
void test_notEqual_double_double_false() {
_assertNotEqual(_boolValue(false), _doubleValue(2.0), _doubleValue(2.0));
}
void test_notEqual_double_true() {
void test_notEqual_double_double_false_language219() {
_featureSet = FeatureSets.language_2_19;
_assertNotEqual(_boolValue(false), _doubleValue(2.0), _doubleValue(2.0));
}
void test_notEqual_double_double_true() {
_assertNotEqual(_boolValue(true), _doubleValue(2.0), _doubleValue(4.0));
}
void test_notEqual_double_double_true_language219() {
_featureSet = FeatureSets.language_2_19;
_assertNotEqual(_boolValue(true), _doubleValue(2.0), _doubleValue(4.0));
}
@ -1594,6 +1624,11 @@ class DartObjectImplTest {
_assertNotEqual(_boolValue(null), _doubleValue(1.0), _doubleValue(null));
}
void test_notEqual_double_unknown_language219() {
_featureSet = FeatureSets.language_2_19;
_assertNotEqual(_boolValue(null), _doubleValue(1.0), _doubleValue(null));
}
void test_notEqual_int_false() {
_assertNotEqual(_boolValue(false), _intValue(5), _intValue(5));
}
@ -1996,10 +2031,10 @@ class DartObjectImplTest {
DartObjectImpl? expected, DartObjectImpl left, DartObjectImpl right) {
if (expected == null) {
expect(() {
left.equalEqual(_typeSystem, right);
return left.equalEqual(_typeSystem, _featureSet, right);
}, throwsEvaluationException);
} else {
DartObjectImpl result = left.equalEqual(_typeSystem, right);
DartObjectImpl result = left.equalEqual(_typeSystem, _featureSet, right);
expect(result, isNotNull);
expect(result, expected);
}
@ -2189,10 +2224,10 @@ class DartObjectImplTest {
DartObjectImpl? expected, DartObjectImpl left, DartObjectImpl right) {
if (expected == null) {
expect(() {
left.notEqual(_typeSystem, right);
left.notEqual(_typeSystem, _featureSet, right);
}, throwsEvaluationException);
} else {
DartObjectImpl result = left.notEqual(_typeSystem, right);
DartObjectImpl result = left.notEqual(_typeSystem, _featureSet, right);
expect(result, isNotNull);
expect(result, expected);
}

View file

@ -22,6 +22,14 @@ const b = a == Object();
''');
}
test_equal_double_object_language219() async {
await assertNoErrorsInCode(r'''
// @dart = 2.19
const a = 0.1;
const b = a == Object();
''');
}
test_equal_int_object() async {
await assertNoErrorsInCode(r'''
const a = 0;
@ -54,8 +62,48 @@ const b = a == Object();
''');
}
test_equal_userClass_int() async {
test_equal_userClass_int_hasEqEq() async {
await assertErrorsInCode(r'''
class A {
const A();
bool operator ==(other) => false;
}
const a = A();
const b = a == 0;
''', [
error(CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING, 87, 6),
]);
}
test_equal_userClass_int_hasHashCode() async {
await assertErrorsInCode(r'''
class A {
const A();
int get hashCode => 0;
}
const a = A();
const b = a == 0;
''', [
error(CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING, 76, 6),
]);
}
test_equal_userClass_int_hasPrimitiveEquality() async {
await assertNoErrorsInCode(r'''
class A {
const A();
}
const a = A();
const b = a == 0;
''');
}
test_equal_userClass_int_language219() async {
await assertErrorsInCode(r'''
// @dart = 2.19
class A {
const A();
}
@ -63,7 +111,7 @@ class A {
const a = A();
const b = a == 0;
''', [
error(CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING, 51, 6),
error(CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING, 67, 6),
]);
}
@ -74,6 +122,14 @@ const b = a != Object();
''');
}
test_notEqual_double_object_language219() async {
await assertNoErrorsInCode(r'''
// @dart = 2.19
const a = 0.1;
const b = a != Object();
''');
}
test_notEqual_int_object() async {
await assertNoErrorsInCode(r'''
const a = 0;
@ -106,8 +162,48 @@ const b = a != Object();
''');
}
test_notEqual_userClass_int() async {
test_notEqual_userClass_int_hasEqEq() async {
await assertErrorsInCode(r'''
class A {
const A();
bool operator ==(other) => false;
}
const a = A();
const b = a != 0;
''', [
error(CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING, 87, 6),
]);
}
test_notEqual_userClass_int_hasHashCode() async {
await assertErrorsInCode(r'''
class A {
const A();
int get hashCode => 0;
}
const a = A();
const b = a != 0;
''', [
error(CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING, 76, 6),
]);
}
test_notEqual_userClass_int_hasPrimitiveEquality() async {
await assertNoErrorsInCode(r'''
class A {
const A();
}
const a = A();
const b = a != 0;
''');
}
test_notEqual_userClass_int_language219() async {
await assertErrorsInCode(r'''
// @dart = 2.19
class A {
const A();
}
@ -115,7 +211,7 @@ class A {
const a = A();
const b = a != 0;
''', [
error(CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING, 51, 6),
error(CompileTimeErrorCode.CONST_EVAL_TYPE_BOOL_NUM_STRING, 67, 6),
]);
}
}

View file

@ -177,8 +177,6 @@ method<T>(o) {
// [analyzer] SYNTACTIC_ERROR.INVALID_CONSTANT_CONST_PREFIX
// [cfe] The expression can't be prefixed by 'const' to form a constant pattern.
case const Object() == 2: // Error
// ^^^^^^^^^^^^^
// [analyzer] COMPILE_TIME_ERROR.CONST_EVAL_TYPE_BOOL_NUM_STRING
// ^
// [analyzer] SYNTACTIC_ERROR.INVALID_CONSTANT_CONST_PREFIX
// [cfe] The expression can't be prefixed by 'const' to form a constant pattern.
@ -243,4 +241,4 @@ method<T>(o) {
// [analyzer] COMPILE_TIME_ERROR.CONSTANT_PATTERN_WITH_NON_CONSTANT_EXPRESSION
// [cfe] Not a constant expression.
}
}
}