diff --git a/pkg/analyzer/lib/src/dart/constant/evaluation.dart b/pkg/analyzer/lib/src/dart/constant/evaluation.dart index da7f44de137..f50b0bc2b88 100644 --- a/pkg/analyzer/lib/src/dart/constant/evaluation.dart +++ b/pkg/analyzer/lib/src/dart/constant/evaluation.dart @@ -544,6 +544,7 @@ class ConstantVisitor extends UnifyingAstVisitor { _substitution = substitution { _dartObjectComputer = DartObjectComputer( typeSystem, + _library.featureSet, _errorReporter, ); } @@ -1566,11 +1567,12 @@ class ConstantVisitor extends UnifyingAstVisitor { /// 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); } diff --git a/pkg/analyzer/lib/src/dart/constant/value.dart b/pkg/analyzer/lib/src/dart/constant/value.dart index f300161de9d..c7eeba1b3f9 100644 --- a/pkg/analyzer/lib/src/dart/constant/value.dart +++ b/pkg/analyzer/lib/src/dart/constant/value.dart @@ -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'. diff --git a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart index 0e545665928..b82194541de 100644 --- a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart +++ b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart @@ -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(); ); } + 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 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 { diff --git a/pkg/analyzer/test/src/dart/constant/value_test.dart b/pkg/analyzer/test/src/dart/constant/value_test.dart index e9e6e336fb8..b4956f25032 100644 --- a/pkg/analyzer/test/src/dart/constant/value_test.dart +++ b/pkg/analyzer/test/src/dart/constant/value_test.dart @@ -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); } diff --git a/pkg/analyzer/test/src/diagnostics/const_eval_type_bool_num_string_test.dart b/pkg/analyzer/test/src/diagnostics/const_eval_type_bool_num_string_test.dart index 4e03c038d30..9b612a31c4e 100644 --- a/pkg/analyzer/test/src/diagnostics/const_eval_type_bool_num_string_test.dart +++ b/pkg/analyzer/test/src/diagnostics/const_eval_type_bool_num_string_test.dart @@ -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), ]); } } diff --git a/tests/language/patterns/invalid_const_pattern_binary_test.dart b/tests/language/patterns/invalid_const_pattern_binary_test.dart index 8d86d635691..2160f348133 100644 --- a/tests/language/patterns/invalid_const_pattern_binary_test.dart +++ b/tests/language/patterns/invalid_const_pattern_binary_test.dart @@ -177,8 +177,6 @@ method(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(o) { // [analyzer] COMPILE_TIME_ERROR.CONSTANT_PATTERN_WITH_NON_CONSTANT_EXPRESSION // [cfe] Not a constant expression. } -} \ No newline at end of file +}