diff --git a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart index 422bc52059a..d44744c53b3 100644 --- a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart +++ b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart @@ -1653,9 +1653,10 @@ mixin TypeAnalyzer< Type argumentType = isEquality ? operations.promoteToNonNull(operandType) : operandType; if (!operations.isAssignableTo(argumentType, operator.parameterType)) { - argumentTypeNotAssignableError = errors.argumentTypeNotAssignable( - argument: operand, - argumentType: argumentType, + argumentTypeNotAssignableError = + errors.relationalPatternOperandTypeNotAssignable( + pattern: node, + operandType: argumentType, parameterType: operator.parameterType, ); } @@ -2511,14 +2512,6 @@ abstract class TypeAnalyzerErrors< Type extends Object, Pattern extends Node, Error> implements TypeAnalyzerErrorsBase { - /// Called if [argument] has type [argumentType], which is not assignable - /// to [parameterType]. - Error argumentTypeNotAssignable({ - required Expression argument, - required Type argumentType, - required Type parameterType, - }); - /// Called if pattern support is disabled and a case constant's static type /// doesn't properly match the scrutinee's static type. Error caseExpressionTypeMismatch( @@ -2627,6 +2620,14 @@ abstract class TypeAnalyzerErrors< Error refutablePatternInIrrefutableContext( {required Node pattern, required Node context}); + /// Called if the operand of the [pattern] has the type [operandType], which + /// is not assignable to [parameterType] of the invoked relational operator. + Error relationalPatternOperandTypeNotAssignable({ + required Pattern pattern, + required Type operandType, + required Type parameterType, + }); + /// Called if the [returnType] of the invoked relational operator is not /// assignable to `bool`. Error relationalPatternOperatorReturnTypeNotAssignableToBool({ diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart index e598a131505..efc0933cbb2 100644 --- a/pkg/_fe_analyzer_shared/test/mini_ast.dart +++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart @@ -2991,19 +2991,6 @@ class _MiniAstErrors /// highlight the point of failure. StackTrace? _assertInErrorRecoveryStack; - @override - void argumentTypeNotAssignable({ - required Expression argument, - required Type argumentType, - required Type parameterType, - }) { - _recordError('argumentTypeNotAssignable', { - 'argument': argument, - 'argumentType': argumentType, - 'parameterType': parameterType, - }); - } - @override void assertInErrorRecovery() { if (_accumulatedErrors.isEmpty) { @@ -3181,6 +3168,19 @@ class _MiniAstErrors {'pattern': pattern, 'context': context}); } + @override + void relationalPatternOperandTypeNotAssignable({ + required Pattern pattern, + required Type operandType, + required Type parameterType, + }) { + _recordError('relationalPatternOperandTypeNotAssignable', { + 'pattern': pattern, + 'operandType': operandType, + 'parameterType': parameterType, + }); + } + @override void relationalPatternOperatorReturnTypeNotAssignableToBool({ required Pattern pattern, diff --git a/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart b/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart index d4071372b68..3e2452aa7b7 100644 --- a/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart +++ b/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart @@ -3633,13 +3633,13 @@ main() { h.run([ ifCase( expr('int').checkContext('?'), - relationalPattern('>', expr('String')..errorId = 'OPERAND'), + relationalPattern('>', expr('String'))..errorId = 'PATTERN', [], ).checkIr('ifCase(expr(int), >(expr(String), ' 'matchedType: int), variables(), true, block(), noop)') ], expectedErrors: { - 'argumentTypeNotAssignable(argument: OPERAND, ' - 'argumentType: String, parameterType: num)' + 'relationalPatternOperandTypeNotAssignable(pattern: PATTERN, ' + 'operandType: String, parameterType: num)' }); }); test('return type is not assignable to bool', () { diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml index 18c59bb28df..4020273e22f 100644 --- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml +++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml @@ -1017,6 +1017,8 @@ CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION: status: needsEvaluation CompileTimeErrorCode.REFUTABLE_PATTERN_IN_IRREFUTABLE_CONTEXT: status: noFix +CompileTimeErrorCode.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE: + status: noFix CompileTimeErrorCode.RELATIONAL_PATTERN_OPERATOR_RETURN_TYPE_NOT_ASSIGNABLE_TO_BOOL: status: noFix CompileTimeErrorCode.REST_ELEMENT_NOT_LAST_IN_MAP_PATTERN: diff --git a/pkg/analyzer/lib/src/dart/resolver/shared_type_analyzer.dart b/pkg/analyzer/lib/src/dart/resolver/shared_type_analyzer.dart index 7c1cbaa25e5..d3a897e0086 100644 --- a/pkg/analyzer/lib/src/dart/resolver/shared_type_analyzer.dart +++ b/pkg/analyzer/lib/src/dart/resolver/shared_type_analyzer.dart @@ -28,19 +28,6 @@ class SharedTypeAnalyzerErrors SharedTypeAnalyzerErrors(this._errorReporter); - @override - void argumentTypeNotAssignable({ - required Expression argument, - required DartType argumentType, - required DartType parameterType, - }) { - _errorReporter.reportErrorForNode( - CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, - argument, - [argumentType, parameterType], - ); - } - @override void assertInErrorRecovery() {} @@ -233,6 +220,19 @@ class SharedTypeAnalyzerErrors ); } + @override + void relationalPatternOperandTypeNotAssignable({ + required covariant RelationalPatternImpl pattern, + required DartType operandType, + required DartType parameterType, + }) { + _errorReporter.reportErrorForNode( + CompileTimeErrorCode.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE, + pattern.operand, + [operandType, parameterType, pattern.operator.lexeme], + ); + } + @override void relationalPatternOperatorReturnTypeNotAssignableToBool({ required covariant RelationalPatternImpl pattern, diff --git a/pkg/analyzer/lib/src/error/codes.g.dart b/pkg/analyzer/lib/src/error/codes.g.dart index d5fe3321709..6dc5b645db2 100644 --- a/pkg/analyzer/lib/src/error/codes.g.dart +++ b/pkg/analyzer/lib/src/error/codes.g.dart @@ -4265,6 +4265,17 @@ class CompileTimeErrorCode extends AnalyzerErrorCode { "instead.", ); + /// Parameters: + /// 0: the operand type + /// 1: the parameter type of the invoked operator + /// 2: the name of the invoked operator + static const CompileTimeErrorCode + RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE = CompileTimeErrorCode( + 'RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE', + "The constant expression type '{0}' is not assignable to the parameter " + "type '{1}' of the '{2}' operator.", + ); + static const CompileTimeErrorCode RELATIONAL_PATTERN_OPERATOR_RETURN_TYPE_NOT_ASSIGNABLE_TO_BOOL = CompileTimeErrorCode( diff --git a/pkg/analyzer/lib/src/error/error_code_values.g.dart b/pkg/analyzer/lib/src/error/error_code_values.g.dart index 8ce7f60c8e7..908a9ceb37d 100644 --- a/pkg/analyzer/lib/src/error/error_code_values.g.dart +++ b/pkg/analyzer/lib/src/error/error_code_values.g.dart @@ -432,6 +432,7 @@ const List errorCodeValues = [ CompileTimeErrorCode.REDIRECT_TO_TYPE_ALIAS_EXPANDS_TO_TYPE_PARAMETER, CompileTimeErrorCode.REFERENCED_BEFORE_DECLARATION, CompileTimeErrorCode.REFUTABLE_PATTERN_IN_IRREFUTABLE_CONTEXT, + CompileTimeErrorCode.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE, CompileTimeErrorCode .RELATIONAL_PATTERN_OPERATOR_RETURN_TYPE_NOT_ASSIGNABLE_TO_BOOL, CompileTimeErrorCode.REST_ELEMENT_NOT_LAST_IN_MAP_PATTERN, diff --git a/pkg/analyzer/messages.yaml b/pkg/analyzer/messages.yaml index 2c2da0ec071..6bf0cf44c9d 100644 --- a/pkg/analyzer/messages.yaml +++ b/pkg/analyzer/messages.yaml @@ -12464,6 +12464,13 @@ CompileTimeErrorCode: REFUTABLE_PATTERN_IN_IRREFUTABLE_CONTEXT: problemMessage: Refutable patterns can't be used in an irrefutable context. correctionMessage: Try using an if-case, a 'switch' statement, or a 'switch' expression instead. + RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE: + problemMessage: "The constant expression type '{0}' is not assignable to the parameter type '{1}' of the '{2}' operator." + comment: |- + Parameters: + 0: the operand type + 1: the parameter type of the invoked operator + 2: the name of the invoked operator RELATIONAL_PATTERN_OPERATOR_RETURN_TYPE_NOT_ASSIGNABLE_TO_BOOL: problemMessage: The return type of operators used in relational patterns must be assignable to 'bool'. correctionMessage: Try updating the operator declaration to return 'bool'. diff --git a/pkg/analyzer/test/src/diagnostics/argument_type_not_assignable_test.dart b/pkg/analyzer/test/src/diagnostics/argument_type_not_assignable_test.dart index d55fa5f41f9..79951f30e87 100644 --- a/pkg/analyzer/test/src/diagnostics/argument_type_not_assignable_test.dart +++ b/pkg/analyzer/test/src/diagnostics/argument_type_not_assignable_test.dart @@ -217,135 +217,6 @@ void g() { error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 44, 12), ]); } - - test_relationalPattern_bangEq_matchedValueNullable() async { - await assertNoErrorsInCode(r''' -class A {} - -void f(A? x) { - switch (x) { - case != null: - break; - } -} -'''); - } - - test_relationalPattern_bangEq_operandNull() async { - await assertNoErrorsInCode(r''' -class A {} - -void f(A x) { - switch (x) { - case != null: - break; - } -} -'''); - } - - test_relationalPattern_bangEq_operandNullable() async { - await assertNoErrorsInCode(r''' -class A {} - -const int? y = 0; - -void f(A x) { - switch (x) { - case != y: - break; - } -} -'''); - } - - test_relationalPattern_eqEq() async { - await assertNoErrorsInCode(r''' -class A {} - -void f(A x) { - switch (x) { - case == 0: - break; - } -} -'''); - } - - test_relationalPattern_eqEq_covariantParameterType() async { - await assertErrorsInCode(r''' -class A { - bool operator ==(covariant A other) => true; -} - -void f(A x) { - switch (x) { - case == 0: - break; - } -} -''', [ - error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 101, 1), - ]); - } - - test_relationalPattern_eqEq_matchedValueNullable() async { - await assertNoErrorsInCode(r''' -class A {} - -void f(A? x) { - switch (x) { - case == null: - break; - } -} -'''); - } - - test_relationalPattern_eqEq_operandNull() async { - await assertNoErrorsInCode(r''' -class A {} - -void f(A x) { - switch (x) { - case == null: - break; - } -} -'''); - } - - test_relationalPattern_eqEq_operandNullable() async { - await assertNoErrorsInCode(r''' -class A {} - -const int? y = 0; - -void f(A x) { - switch (x) { - case == y: - break; - } -} -'''); - } - - test_relationalPattern_greaterThan() async { - await assertErrorsInCode(r''' -class A { - bool operator >(A other) => true; -} - -void f(A x) { - switch (x) { - case > 0: - break; - } -} -''', [ - error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 89, 1), - ]); - } } mixin ArgumentTypeNotAssignableTestCases on PubPackageResolutionTest { diff --git a/pkg/analyzer/test/src/diagnostics/relational_pattern_operand_type_not_assignable_test.dart b/pkg/analyzer/test/src/diagnostics/relational_pattern_operand_type_not_assignable_test.dart new file mode 100644 index 00000000000..5c5409758c1 --- /dev/null +++ b/pkg/analyzer/test/src/diagnostics/relational_pattern_operand_type_not_assignable_test.dart @@ -0,0 +1,149 @@ +// Copyright (c) 2023, 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:analyzer/src/error/codes.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../dart/resolution/context_collection_resolution.dart'; + +main() { + defineReflectiveSuite(() { + defineReflectiveTests(RelationalPatternArgumentTypeNotAssignableTest); + }); +} + +@reflectiveTest +class RelationalPatternArgumentTypeNotAssignableTest + extends PubPackageResolutionTest { + test_bangEq_matchedValueNullable() async { + await assertNoErrorsInCode(r''' +class A {} + +void f(A? x) { + switch (x) { + case != null: + break; + } +} +'''); + } + + test_bangEq_operandNull() async { + await assertNoErrorsInCode(r''' +class A {} + +void f(A x) { + switch (x) { + case != null: + break; + } +} +'''); + } + + test_bangEq_operandNullable() async { + await assertNoErrorsInCode(r''' +class A {} + +const int? y = 0; + +void f(A x) { + switch (x) { + case != y: + break; + } +} +'''); + } + + test_eqEq() async { + await assertNoErrorsInCode(r''' +class A {} + +void f(A x) { + switch (x) { + case == 0: + break; + } +} +'''); + } + + test_eqEq_covariantParameterType() async { + await assertErrorsInCode(r''' +class A { + bool operator ==(covariant A other) => true; +} + +void f(A x) { + switch (x) { + case == 0: + break; + } +} +''', [ + error(CompileTimeErrorCode.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE, + 101, 1), + ]); + } + + test_eqEq_matchedValueNullable() async { + await assertNoErrorsInCode(r''' +class A {} + +void f(A? x) { + switch (x) { + case == null: + break; + } +} +'''); + } + + test_eqEq_operandNull() async { + await assertNoErrorsInCode(r''' +class A {} + +void f(A x) { + switch (x) { + case == null: + break; + } +} +'''); + } + + test_eqEq_operandNullable() async { + await assertNoErrorsInCode(r''' +class A {} + +const int? y = 0; + +void f(A x) { + switch (x) { + case == y: + break; + } +} +'''); + } + + test_greaterThan() async { + await assertErrorsInCode(r''' +class A { + bool operator >(A other) => true; +} + +void f(A x) { + switch (x) { + case > 0: + break; + } +} +''', [ + error(CompileTimeErrorCode.RELATIONAL_PATTERN_OPERAND_TYPE_NOT_ASSIGNABLE, + 89, 1), + ]); + } +} diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart index a4da1c35a75..482577e5041 100644 --- a/pkg/analyzer/test/src/diagnostics/test_all.dart +++ b/pkg/analyzer/test/src/diagnostics/test_all.dart @@ -691,6 +691,8 @@ import 'referenced_before_declaration_test.dart' as referenced_before_declaration; import 'refutable_pattern_in_irrefutable_context_test.dart' as refutable_pattern_in_irrefutable_context; +import 'relational_pattern_operand_type_not_assignable_test.dart' + as relational_pattern_operand_type_not_assignable; import 'relational_pattern_operator_return_type_not_assignable_to_bool_test.dart' as relational_pattern_operator_return_type_not_assignable_to_bool; import 'removed_lint_use_test.dart' as removed_lint_in_ignore; @@ -1324,6 +1326,7 @@ main() { redirect_to_type_alias_expands_to_type_parameter.main(); referenced_before_declaration.main(); refutable_pattern_in_irrefutable_context.main(); + relational_pattern_operand_type_not_assignable.main(); relational_pattern_operator_return_type_not_assignable_to_bool.main(); removed_lint_in_ignore.main(); replaced_lint_in_ignore.main(); diff --git a/pkg/front_end/lib/src/fasta/type_inference/shared_type_analyzer.dart b/pkg/front_end/lib/src/fasta/type_inference/shared_type_analyzer.dart index c6a523ff473..e31a9be26db 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/shared_type_analyzer.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/shared_type_analyzer.dart @@ -36,19 +36,6 @@ class SharedTypeAnalyzerErrors required this.coreTypes, required this.isNonNullableByDefault}); - @override - InvalidExpression argumentTypeNotAssignable({ - required Expression argument, - required DartType argumentType, - required DartType parameterType, - }) { - return helper.buildProblem( - templateArgumentTypeNotAssignable.withArguments( - argumentType, parameterType, isNonNullableByDefault), - argument.fileOffset, - noLength); - } - @override void assertInErrorRecovery() { // TODO(paulberry): figure out how to do this. @@ -164,12 +151,6 @@ class SharedTypeAnalyzerErrors messageNonBoolCondition, node.fileOffset, noLength); } - @override - void patternDoesNotAllowLate({required TreeNode pattern}) { - // TODO(johnniwinther): Is late even supported by the grammar or parser? - throw new UnimplementedError('TODO(paulberry)'); - } - @override InvalidExpression nonExhaustiveSwitch( {required TreeNode node, required DartType scrutineeType}) { @@ -188,6 +169,12 @@ class SharedTypeAnalyzerErrors noLength); } + @override + void patternDoesNotAllowLate({required TreeNode pattern}) { + // TODO(johnniwinther): Is late even supported by the grammar or parser? + throw new UnimplementedError('TODO(paulberry)'); + } + @override InvalidExpression patternForInExpressionIsNotIterable({ required TreeNode node, @@ -217,6 +204,19 @@ class SharedTypeAnalyzerErrors pattern.fileOffset, noLength); } + @override + InvalidExpression relationalPatternOperandTypeNotAssignable({ + required covariant RelationalPattern pattern, + required DartType operandType, + required DartType parameterType, + }) { + return helper.buildProblem( + templateArgumentTypeNotAssignable.withArguments( + operandType, parameterType, isNonNullableByDefault), + pattern.expression.fileOffset, + noLength); + } + @override InvalidExpression relationalPatternOperatorReturnTypeNotAssignableToBool({ required Pattern pattern,