From c6352b0e4979069fd814fbde31feccdd96ebd8a4 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Fri, 10 Feb 2023 21:50:27 +0000 Subject: [PATCH] Unit tests for NON_EXHAUSTIVE_SWITCH and UNREACHABLE_SWITCH_CASE. Change-Id: I0b291046eba881acd35aeba1f4725c84f0bc9bcc Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/282160 Reviewed-by: Brian Wilkerson Commit-Queue: Konstantin Shcheglov --- .../selection_range_computer_test.dart | 10 + .../src/dart/constant/constant_verifier.dart | 210 ++++++---- .../lib/src/generated/exhaustiveness.dart | 5 +- .../body_might_complete_normally_test.dart | 8 +- .../missing_enum_constant_in_switch_test.dart | 8 +- .../non_exhaustive_switch_test.dart | 394 ++++++++++++++++++ .../test/src/diagnostics/test_all.dart | 4 + .../unreachable_switch_case_test.dart | 69 +++ .../exhaustiveness/bool_switch_test.dart | 12 +- .../exhaustiveness/enum_switch_test.dart | 21 +- .../object_pattern_switch_test.dart | 12 +- .../sealed_class_switch_test.dart | 15 +- 12 files changed, 656 insertions(+), 112 deletions(-) create mode 100644 pkg/analyzer/test/src/diagnostics/non_exhaustive_switch_test.dart create mode 100644 pkg/analyzer/test/src/diagnostics/unreachable_switch_case_test.dart diff --git a/pkg/analysis_server/test/src/computer/selection_range_computer_test.dart b/pkg/analysis_server/test/src/computer/selection_range_computer_test.dart index 17074b5a048..3a87c94a490 100644 --- a/pkg/analysis_server/test/src/computer/selection_range_computer_test.dart +++ b/pkg/analysis_server/test/src/computer/selection_range_computer_test.dart @@ -209,6 +209,16 @@ final a = switch (Object()) { Square(length: v^ar l) => l * l, Circle(radius: var r) => math.pi * r * r }; + +class Square { + final int length; + Square(this.length); +} + +class Circle { + final int length; + Circle(this.radius); +} '''); final regions = await _computeSelectionRanges(content); diff --git a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart index a4e9fa7151e..45e8ceb44ff 100644 --- a/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart +++ b/pkg/analyzer/lib/src/dart/constant/constant_verifier.dart @@ -6,7 +6,6 @@ import 'dart:collection'; import 'package:_fe_analyzer_shared/src/exhaustiveness/exhaustive.dart'; import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart'; -import 'package:_fe_analyzer_shared/src/exhaustiveness/static_type.dart'; import 'package:analyzer/dart/analysis/declared_variables.dart'; import 'package:analyzer/dart/analysis/features.dart'; import 'package:analyzer/dart/ast/ast.dart'; @@ -369,20 +368,38 @@ class ConstantVerifier extends RecursiveAstVisitor { } } + @override + void visitSwitchExpression(SwitchExpression node) { + _withConstantPatternValues((constantPatternValues) { + super.visitSwitchExpression(node); + _validateSwitchExhaustiveness( + node: node, + switchKeyword: node.switchKeyword, + scrutinee: node.expression, + caseNodes: node.cases, + constantPatternValues: constantPatternValues, + ); + }); + } + @override void visitSwitchStatement(SwitchStatement node) { - Map? previousConstantPatternValues = - _constantPatternValues; - _constantPatternValues = {}; - super.visitSwitchStatement(node); - if (_currentLibrary.featureSet.isEnabled(Feature.patterns)) { - _validateSwitchStatement_patterns(node, _constantPatternValues!); - } else if (_currentLibrary.isNonNullableByDefault) { - _validateSwitchStatement_nullSafety(node); - } else { - _validateSwitchStatement_legacy(node); - } - _constantPatternValues = previousConstantPatternValues; + _withConstantPatternValues((constantPatternValues) { + super.visitSwitchStatement(node); + if (_currentLibrary.featureSet.isEnabled(Feature.patterns)) { + _validateSwitchExhaustiveness( + node: node, + switchKeyword: node.switchKeyword, + scrutinee: node.expression, + caseNodes: node.members, + constantPatternValues: constantPatternValues, + ); + } else if (_currentLibrary.isNonNullableByDefault) { + _validateSwitchStatement_nullSafety(node); + } else { + _validateSwitchStatement_legacy(node); + } + }); } @override @@ -721,6 +738,97 @@ class ConstantVerifier extends RecursiveAstVisitor { } } + void _validateSwitchExhaustiveness({ + required AstNode node, + required Token switchKeyword, + required Expression scrutinee, + required List caseNodes, + required Map constantPatternValues, + }) { + final scrutineeType = scrutinee.typeOrThrow; + final scrutineeTypeEx = _exhaustivenessCache.getStaticType(scrutineeType); + + final caseNodesWithSpace = []; + final caseSpaces = []; + var hasDefault = false; + + // Build spaces for cases. + for (final caseNode in caseNodes) { + GuardedPattern? guardedPattern; + if (caseNode is SwitchDefault) { + hasDefault = true; + } else if (caseNode is SwitchExpressionCase) { + guardedPattern = caseNode.guardedPattern; + } else if (caseNode is SwitchPatternCase) { + guardedPattern = caseNode.guardedPattern; + } else { + throw UnimplementedError('(${caseNode.runtimeType}) $caseNode'); + } + + if (guardedPattern != null) { + Space space; + if (guardedPattern.whenClause != null) { + // TODO(johnniwinther): Test this. + space = Space(_exhaustivenessCache.getUnknownStaticType()); + } else { + final pattern = guardedPattern.pattern; + space = convertPatternToSpace( + _exhaustivenessCache, pattern, constantPatternValues); + } + caseNodesWithSpace.add(caseNode); + caseSpaces.add(space); + } + } + + // Prepare for recording data for testing. + List? remainingSpaces; + final exhaustivenessDataForTesting = this.exhaustivenessDataForTesting; + if (exhaustivenessDataForTesting != null) { + remainingSpaces = []; + } + + // Compute and report errors. + final errors = reportErrors(scrutineeTypeEx, caseSpaces, remainingSpaces); + for (final error in errors) { + if (error is UnreachableCaseError) { + final caseNode = caseNodesWithSpace[error.index]; + final Token errorToken; + if (caseNode is SwitchExpressionCase) { + errorToken = caseNode.arrow; + } else if (caseNode is SwitchPatternCase) { + errorToken = caseNode.keyword; + } else { + throw UnimplementedError('(${caseNode.runtimeType}) $caseNode'); + } + _errorReporter.reportErrorForToken( + HintCode.UNREACHABLE_SWITCH_CASE, + errorToken, + ); + } else if (error is NonExhaustiveError && + _typeSystem.isAlwaysExhaustive(scrutineeType) && + !hasDefault) { + _errorReporter.reportErrorForToken( + CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, + switchKeyword, + [scrutineeType, '${error.remaining}'], + ); + } + } + + // Record data for testing. + if (exhaustivenessDataForTesting != null && remainingSpaces != null) { + assert(remainingSpaces.length == caseSpaces.length + 1); + for (var i = 0; i < caseSpaces.length; i++) { + final caseNode = caseNodesWithSpace[i]; + exhaustivenessDataForTesting.caseSpaces[caseNode] = caseSpaces[i]; + exhaustivenessDataForTesting.remainingSpaces[caseNode] = + remainingSpaces[i]; + } + exhaustivenessDataForTesting.switchScrutineeType[node] = scrutineeTypeEx; + exhaustivenessDataForTesting.remainingSpaces[node] = remainingSpaces.last; + } + } + void _validateSwitchStatement_legacy(SwitchStatement node) { // TODO(paulberry): to minimize error messages, it would be nice to // compare all types with the most popular type rather than the first @@ -809,74 +917,14 @@ class ConstantVerifier extends RecursiveAstVisitor { } } - void _validateSwitchStatement_patterns(SwitchStatement node, - Map constantPatternValues) { - DartType expressionType = node.expression.staticType!; - StaticType type = _exhaustivenessCache.getStaticType(expressionType); - List cases = []; - List caseMembers = []; - bool hasDefault = false; - for (var switchMember in node.members) { - if (switchMember is SwitchCase) { - Expression expression = switchMember.expression; - var expressionValue = _validate( - expression, - CompileTimeErrorCode.NON_CONSTANT_CASE_EXPRESSION, - ); - Space space = - convertConstantValueToSpace(_exhaustivenessCache, expressionValue); - cases.add(space); - caseMembers.add(switchMember); - } else if (switchMember is SwitchPatternCase) { - Space space; - if (switchMember.guardedPattern.whenClause != null) { - // TODO(johnniwinther): Test this. - space = Space(_exhaustivenessCache.getUnknownStaticType()); - } else { - DartPattern pattern = switchMember.guardedPattern.pattern; - space = convertPatternToSpace( - _exhaustivenessCache, pattern, constantPatternValues); - } - cases.add(space); - caseMembers.add(switchMember); - } else if (switchMember is SwitchDefault) { - hasDefault = true; - } - } - List? remainingSpaces; - if (exhaustivenessDataForTesting != null) { - remainingSpaces = []; - } - for (ExhaustivenessError error - in reportErrors(type, cases, remainingSpaces)) { - if (error is UnreachableCaseError) { - _errorReporter.reportErrorForToken( - HintCode.UNREACHABLE_SWITCH_CASE, - caseMembers[error.index].keyword, - [], - ); - } else if (error is NonExhaustiveError && - _typeSystem.isAlwaysExhaustive(expressionType) && - !hasDefault) { - _errorReporter.reportErrorForNode( - CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, - node.expression, - [expressionType, '${error.remaining}'], - ); - } - } - if (exhaustivenessDataForTesting != null) { - assert(remainingSpaces!.length == cases.length + 1); - for (int i = 0; i < cases.length; i++) { - SwitchMember caseMember = caseMembers[i]; - exhaustivenessDataForTesting!.caseSpaces[caseMember] = cases[i]; - exhaustivenessDataForTesting!.remainingSpaces[caseMember] = - remainingSpaces![i]; - } - exhaustivenessDataForTesting!.switchScrutineeType[node] = type; - exhaustivenessDataForTesting!.remainingSpaces[node] = - remainingSpaces!.last; - } + /// Runs [f] with new [_constantPatternValues]. + void _withConstantPatternValues( + void Function(Map constantPatternValues) f, + ) { + final previous = _constantPatternValues; + final values = _constantPatternValues = {}; + f(values); + _constantPatternValues = previous; } } diff --git a/pkg/analyzer/lib/src/generated/exhaustiveness.dart b/pkg/analyzer/lib/src/generated/exhaustiveness.dart index 1313b9fe151..4140958ec78 100644 --- a/pkg/analyzer/lib/src/generated/exhaustiveness.dart +++ b/pkg/analyzer/lib/src/generated/exhaustiveness.dart @@ -40,9 +40,8 @@ Space convertPatternToSpace( AnalyzerExhaustivenessCache cache, DartPattern pattern, Map constantPatternValues) { - if (pattern is DeclaredVariablePattern) { - DartType type = - (pattern as DeclaredVariablePatternImpl).declaredElement!.type; + if (pattern is DeclaredVariablePatternImpl) { + DartType type = pattern.declaredElement!.type; return Space(cache.getStaticType(type)); } else if (pattern is ObjectPattern) { Map fields = {}; diff --git a/pkg/analyzer/test/src/diagnostics/body_might_complete_normally_test.dart b/pkg/analyzer/test/src/diagnostics/body_might_complete_normally_test.dart index 3149fa54507..76820c66eed 100644 --- a/pkg/analyzer/test/src/diagnostics/body_might_complete_normally_test.dart +++ b/pkg/analyzer/test/src/diagnostics/body_might_complete_normally_test.dart @@ -66,7 +66,7 @@ enum E { error(CompileTimeErrorCode.BODY_MIGHT_COMPLETE_NORMALLY, 28, 5), error(StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH, 40, 13), ] else - error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 48, 4) + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 40, 6) ]); } @@ -197,7 +197,7 @@ int f(Foo foo) { error(CompileTimeErrorCode.BODY_MIGHT_COMPLETE_NORMALLY, 23, 1), error(StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH, 38, 12), ] else - error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 46, 3), + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 38, 6), ]); } @@ -220,7 +220,7 @@ int f(E e) { error(CompileTimeErrorCode.BODY_MIGHT_COMPLETE_NORMALLY, 47, 1), error(StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH, 58, 10), ] else - error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 66, 1), + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 58, 6), ]); } @@ -275,7 +275,7 @@ int f(Foo? foo) { error(CompileTimeErrorCode.BODY_MIGHT_COMPLETE_NORMALLY, 23, 1), error(StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH, 39, 12), ] else - error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 47, 3), + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 39, 6), ]); } diff --git a/pkg/analyzer/test/src/diagnostics/missing_enum_constant_in_switch_test.dart b/pkg/analyzer/test/src/diagnostics/missing_enum_constant_in_switch_test.dart index 4c77d84985a..02c2d09f955 100644 --- a/pkg/analyzer/test/src/diagnostics/missing_enum_constant_in_switch_test.dart +++ b/pkg/analyzer/test/src/diagnostics/missing_enum_constant_in_switch_test.dart @@ -68,7 +68,7 @@ void f(E e) { if (!_arePatternsEnabled) error(StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH, 44, 10) else - error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 52, 1), + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 44, 6), ]); } @@ -87,7 +87,7 @@ void f(E e) { if (!_arePatternsEnabled) error(StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH, 44, 10) else - error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 52, 1), + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 44, 6), ]); } @@ -106,7 +106,7 @@ void f(E e) { if (!_arePatternsEnabled) error(StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH, 44, 10) else - error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 52, 1), + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 44, 6), ]); } @@ -166,7 +166,7 @@ void f(E? e) { if (!_arePatternsEnabled) error(StaticWarningCode.MISSING_ENUM_CONSTANT_IN_SWITCH, 38, 10) else - error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 46, 1), + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 38, 6), ]); } diff --git a/pkg/analyzer/test/src/diagnostics/non_exhaustive_switch_test.dart b/pkg/analyzer/test/src/diagnostics/non_exhaustive_switch_test.dart new file mode 100644 index 00000000000..57018406815 --- /dev/null +++ b/pkg/analyzer/test/src/diagnostics/non_exhaustive_switch_test.dart @@ -0,0 +1,394 @@ +// 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(NonExhaustiveSwitchExpressionTest); + defineReflectiveTests(NonExhaustiveSwitchStatementTest); + }); +} + +@reflectiveTest +class NonExhaustiveSwitchExpressionTest extends PubPackageResolutionTest { + test_bool_true() async { + await assertErrorsInCode(r''' +Object f(bool x) { + return switch (x) { + true => 0, + }; +} +''', [ + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 28, 6), + ]); + } + + test_bool_true_false() async { + await assertNoErrorsInCode(r''' +Object f(bool x) { + return switch (x) { + true => 1, + false => 0, + }; +} +'''); + } + + test_enum_2at2_hasWhen() async { + await assertErrorsInCode(r''' +enum E { + a, b +} + +Object f(E x) { + return switch (x) { + E.a when 1 == 0 => 0, + E.b => 1, + }; +} +''', [ + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 44, 6, + correctionContains: 'E.a'), + ]); + } +} + +@reflectiveTest +class NonExhaustiveSwitchStatementTest extends PubPackageResolutionTest { + test_alwaysExhaustive_bool_true() async { + await assertErrorsInCode(r''' +void f(bool x) { + switch (x) { + case true: + break; + } +} +''', [ + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 19, 6), + ]); + } + + test_alwaysExhaustive_bool_true_false() async { + await assertNoErrorsInCode(r''' +void f(bool x) { + switch (x) { + case true: + case false: + break; + } +} +'''); + } + + /// TODO(scheglov) Fix it. + @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/51275') + test_alwaysExhaustive_bool_wildcard() async { + await assertNoErrorsInCode(r''' +void f(bool x) { + switch (x) { + case _: + break; + } +} +'''); + } + + test_alwaysExhaustive_boolNullable_true_false() async { + await assertErrorsInCode(r''' +void f(bool? x) { + switch (x) { + case true: + case false: + break; + } +} +''', [ + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 20, 6), + ]); + } + + /// TODO(scheglov) Fix it. + @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/51275') + test_alwaysExhaustive_boolNullable_true_false_null() async { + await assertNoErrorsInCode(r''' +void f(bool? x) { + switch (x) { + case true: + case false: + case Null: + break; + } +} +'''); + } + + test_alwaysExhaustive_enum_2at1() async { + await assertErrorsInCode(r''' +enum E { + a, b +} + +void f(E x) { + switch (x) { + case E.a: + break; + } +} +''', [ + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 35, 6), + ]); + } + + test_alwaysExhaustive_enum_2at2_cases() async { + await assertNoErrorsInCode(r''' +enum E { + a, b +} + +void f(E x) { + switch (x) { + case E.a: + case E.b: + break; + } +} +'''); + } + + test_alwaysExhaustive_enum_2at2_hasWhen() async { + await assertErrorsInCode(r''' +enum E { + a, b +} + +void f(E x) { + switch (x) { + case E.a when 1 == 0: + case E.b: + break; + } +} +''', [ + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 35, 6, + correctionContains: 'E.a'), + ]); + } + + /// TODO(scheglov) Fix it. + @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/51275') + test_alwaysExhaustive_enum_2at2_logicalOr() async { + await assertNoErrorsInCode( + r''' +enum E { + a, b +} + +void f(E x) { + switch (x) { + case E.a || E.b: + break; + } +} +''', + ); + } + + test_alwaysExhaustive_Null_hasError() async { + await assertErrorsInCode(r''' +void f(Null x) { + switch (x) {} +} +''', [ + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 19, 6), + ]); + } + + test_alwaysExhaustive_Null_noError() async { + await assertNoErrorsInCode(r''' +void f(Null x) { + switch (x) { + case null: + break; + } +} +'''); + } + + /// TODO(scheglov) Fix it. + @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/51275') + test_alwaysExhaustive_recordType_bool_bool_4at4() async { + await assertNoErrorsInCode(r''' +void f((bool, bool) x) { + switch (x) { + case (false, false): + case (false, true): + case (true, false): + case (true, true): + break; + } +} +'''); + } + + test_alwaysExhaustive_sealedClass_2at1() async { + await assertErrorsInCode(r''' +sealed class A {} +class B extends A {} +class C extends A {} + +void f(A x) { + switch (x) { + case B(): + break; + } +} +''', [ + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 77, 6), + ]); + } + + test_alwaysExhaustive_sealedClass_2at2() async { + await assertNoErrorsInCode(r''' +sealed class A {} +class B extends A {} +class C extends A {} + +void f(A x) { + switch (x) { + case B(): + break; + case C(): + break; + } +} +'''); + } + + /// TODO(scheglov) Fix it. + @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/51275') + test_alwaysExhaustive_sealedClass_2at2_wildcard() async { + await assertNoErrorsInCode(r''' +sealed class A {} +class B extends A {} +class C extends A {} + +void f(A x) { + switch (x) { + case B(): + break; + case _: + break; + } +} +'''); + } + + test_alwaysExhaustive_sealedMixin_2at1() async { + await assertErrorsInCode(r''' +sealed mixin M {} +class A with M {} +class B with M {} + +void f(M x) { + switch (x) { + case A(): + break; + } +} +''', [ + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 71, 6), + ]); + } + + /// TODO(scheglov) Fix it. + @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/51275') + test_alwaysExhaustive_sealedMixin_2at2() async { + await assertNoErrorsInCode(r''' +sealed mixin M {} +class A with M {} +class B with M {} + +void f(M x) { + switch (x) { + case A(): + case B(): + break; + } +} +'''); + } + + test_alwaysExhaustive_typeVariable_bound_bool_true() async { + await assertErrorsInCode(r''' +void f(T x) { + switch (x) { + case true: + break; + } +} +''', [ + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 32, 6), + ]); + } + + test_alwaysExhaustive_typeVariable_bound_bool_true_false() async { + await assertErrorsInCode(r''' +void f(T x) { + switch (x) { + case true: + case false: + break; + } +} +''', [ + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 32, 6), + ]); + } + + test_alwaysExhaustive_typeVariable_promoted_bool_true() async { + await assertErrorsInCode(r''' +void f(T x) { + if (x is bool) { + switch (x) { + case true: + break; + } + } +} +''', [ + error(CompileTimeErrorCode.NON_EXHAUSTIVE_SWITCH, 40, 6), + ]); + } + + /// TODO(scheglov) Fix it. + @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/51275') + test_alwaysExhaustive_typeVariable_promoted_bool_true_false() async { + await assertNoErrorsInCode(r''' +void f(T x) { + if (x is bool) { + switch (x) { + case true: + case false: + break; + } + } +} +'''); + } + + test_notAlwaysExhaustive_int() async { + await assertNoErrorsInCode(r''' +void f(int x) { + switch (x) { + case 0: + break; + } +} +'''); + } +} diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart index 1ad9b7ce6a5..113ceebc042 100644 --- a/pkg/analyzer/test/src/diagnostics/test_all.dart +++ b/pkg/analyzer/test/src/diagnostics/test_all.dart @@ -571,6 +571,7 @@ import 'non_constant_relational_pattern_expression_test.dart' as non_constant_relational_pattern_expression; import 'non_constant_set_element_test.dart' as non_constant_set_element; import 'non_constant_type_argument_test.dart' as non_constant_type_argument; +import 'non_exhaustive_switch_test.dart' as non_exhaustive_switch; import 'non_final_field_in_enum_test.dart' as non_final_field_in_enum; import 'non_generative_constructor_test.dart' as non_generative_constructor; import 'non_generative_implicit_constructor_test.dart' @@ -832,6 +833,7 @@ import 'unqualified_reference_to_non_local_static_member_test.dart' as unqualified_reference_to_non_local_static_member; import 'unqualified_reference_to_static_member_of_extended_type_test.dart' as unqualified_reference_to_static_member_of_extended_type; +import 'unreachable_switch_case_test.dart' as unreachable_switch_case; import 'unused_catch_clause_test.dart' as unused_catch_clause; import 'unused_catch_stack_test.dart' as unused_catch_stack; import 'unused_element_test.dart' as unused_element; @@ -1236,6 +1238,7 @@ main() { non_constant_map_value_from_deferred_library.main(); non_constant_set_element.main(); non_constant_type_argument.main(); + non_exhaustive_switch.main(); non_final_field_in_enum.main(); non_generative_constructor.main(); non_generative_implicit_constructor.main(); @@ -1414,6 +1417,7 @@ main() { unnecessary_type_check.main(); unqualified_reference_to_non_local_static_member.main(); unqualified_reference_to_static_member_of_extended_type.main(); + unreachable_switch_case.main(); unused_catch_clause.main(); unused_catch_stack.main(); unused_element.main(); diff --git a/pkg/analyzer/test/src/diagnostics/unreachable_switch_case_test.dart b/pkg/analyzer/test/src/diagnostics/unreachable_switch_case_test.dart new file mode 100644 index 00000000000..0d6bc52085d --- /dev/null +++ b/pkg/analyzer/test/src/diagnostics/unreachable_switch_case_test.dart @@ -0,0 +1,69 @@ +// 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(UnreachableSwitchCaseTest_SwitchExpression); + defineReflectiveTests(UnreachableSwitchCaseTest_SwitchStatement); + }); +} + +@reflectiveTest +class UnreachableSwitchCaseTest_SwitchExpression + extends PubPackageResolutionTest { + test_bool_false_true_false() async { + await assertErrorsInCode(r''' +Object f(bool x) { + return switch (x) { + false => 0, + true => 1, + false => 2, + }; +} +''', [ + error(HintCode.UNREACHABLE_SWITCH_CASE, 82, 2), + ]); + } + + /// TODO(scheglov) Fix it. + @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/51275') + test_bool_wildcard_true_false() async { + await assertErrorsInCode(r''' +Object f(bool x) { + return switch (x) { + _ => 0, + true => 1, + false => 2, + }; +} +''', [ + error(HintCode.UNREACHABLE_SWITCH_CASE, 62, 2), + error(HintCode.UNREACHABLE_SWITCH_CASE, 78, 2), + ]); + } +} + +@reflectiveTest +class UnreachableSwitchCaseTest_SwitchStatement + extends PubPackageResolutionTest { + test_bool() async { + await assertErrorsInCode(r''' +void f(bool x) { + switch (x) { + case false: + case true: + case false: + break; + } +} +''', [ + error(HintCode.UNREACHABLE_SWITCH_CASE, 67, 4), + ]); + } +} diff --git a/tests/language/patterns/exhaustiveness/bool_switch_test.dart b/tests/language/patterns/exhaustiveness/bool_switch_test.dart index ef3d14e9626..fbf6df0343a 100644 --- a/tests/language/patterns/exhaustiveness/bool_switch_test.dart +++ b/tests/language/patterns/exhaustiveness/bool_switch_test.dart @@ -45,8 +45,9 @@ const f2 = false; void nonExhaustiveSwitch1(bool b) { switch (b) /* Error */ { +//^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // ^ - // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // [cfe] The type 'bool' is not exhaustively matched by the switch cases. case true: print('true'); @@ -56,8 +57,9 @@ void nonExhaustiveSwitch1(bool b) { void nonExhaustiveSwitch2(bool b) { switch (b) /* Error */ { +//^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // ^ - // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // [cfe] The type 'bool' is not exhaustively matched by the switch cases. case false: print('false'); @@ -92,8 +94,9 @@ void exhaustiveNullableSwitch(bool? b) { void nonExhaustiveNullableSwitch1(bool? b) { switch (b) /* Error */ { +//^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // ^ - // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // [cfe] The type 'bool?' is not exhaustively matched by the switch cases. case true: print('true'); @@ -106,8 +109,9 @@ void nonExhaustiveNullableSwitch1(bool? b) { void nonExhaustiveNullableSwitch2(bool? b) { switch (b) /* Error */ { +//^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // ^ - // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // [cfe] The type 'bool?' is not exhaustively matched by the switch cases. case true: print('true'); diff --git a/tests/language/patterns/exhaustiveness/enum_switch_test.dart b/tests/language/patterns/exhaustiveness/enum_switch_test.dart index ea6e78bb1cc..c19977d248f 100644 --- a/tests/language/patterns/exhaustiveness/enum_switch_test.dart +++ b/tests/language/patterns/exhaustiveness/enum_switch_test.dart @@ -58,8 +58,9 @@ const c2 = Enum.c; void nonExhaustiveSwitch1(Enum e) { switch (e) /* Error */ { -// ^ +//^^^^^^ // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH +// ^ // [cfe] The type 'Enum' is not exhaustively matched by the switch cases. case Enum.a: print('a'); @@ -72,8 +73,9 @@ void nonExhaustiveSwitch1(Enum e) { void nonExhaustiveSwitch2(Enum e) { switch (e) /* Error */ { -// ^ +//^^^^^^ // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH +// ^ // [cfe] The type 'Enum' is not exhaustively matched by the switch cases. case Enum.a: print('a'); @@ -86,8 +88,9 @@ void nonExhaustiveSwitch2(Enum e) { void nonExhaustiveSwitch3(Enum e) { switch (e) /* Error */ { -// ^ +//^^^^^^ // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH +// ^ // [cfe] The type 'Enum' is not exhaustively matched by the switch cases. case Enum.b: print('b'); @@ -100,8 +103,9 @@ void nonExhaustiveSwitch3(Enum e) { void nonExhaustiveSwitch4(Enum e) { switch (e) /* Error */ { -// ^ +//^^^^^^ // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH +// ^ // [cfe] The type 'Enum' is not exhaustively matched by the switch cases. case Enum.b: print('b'); @@ -139,8 +143,9 @@ void exhaustiveNullableSwitch(Enum? e) { void nonExhaustiveNullableSwitch1(Enum? e) { switch (e) /* Error */ { -// ^ +//^^^^^^ // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH +// ^ // [cfe] The type 'Enum?' is not exhaustively matched by the switch cases. case Enum.a: print('a'); @@ -156,8 +161,9 @@ void nonExhaustiveNullableSwitch1(Enum? e) { void nonExhaustiveNullableSwitch2(Enum? e) { switch (e) /* Error */ { -// ^ +//^^^^^^ // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH +// ^ // [cfe] The type 'Enum?' is not exhaustively matched by the switch cases. case Enum.a: print('a'); @@ -192,8 +198,9 @@ void unreachableCase1(Enum e) { void unreachableCase2(Enum e) { switch (e) /* Non-exhaustive */ { -// ^ +//^^^^^^ // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH +// ^ // [cfe] The type 'Enum' is not exhaustively matched by the switch cases. case Enum.a: print('a1'); diff --git a/tests/language/patterns/exhaustiveness/object_pattern_switch_test.dart b/tests/language/patterns/exhaustiveness/object_pattern_switch_test.dart index c8a8d97ea1e..a0b98ebc34d 100644 --- a/tests/language/patterns/exhaustiveness/object_pattern_switch_test.dart +++ b/tests/language/patterns/exhaustiveness/object_pattern_switch_test.dart @@ -36,8 +36,9 @@ void exhaustiveSwitch(A r) { void nonExhaustiveSwitch1(A r) { switch (r) /* Error */ { +//^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // ^ - // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // [cfe] The type 'A' is not exhaustively matched by the switch cases. case A(a: Enum.a, b: false): print('A(a, false)'); @@ -53,8 +54,9 @@ void nonExhaustiveSwitch1(A r) { void nonExhaustiveSwitch2(A r) { switch (r) /* Error */ { +//^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // ^ - // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // [cfe] The type 'A' is not exhaustively matched by the switch cases. case A(a: Enum.b, b: false): print('A(b, false)'); @@ -101,8 +103,9 @@ void exhaustiveNullableSwitch(A? r) { void nonExhaustiveNullableSwitch1(A? r) { switch (r) /* Error */ { +//^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // ^ - // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // [cfe] The type 'A?' is not exhaustively matched by the switch cases. case A(a: Enum.a, b: false): print('A(a, false)'); @@ -121,8 +124,9 @@ void nonExhaustiveNullableSwitch1(A? r) { void nonExhaustiveNullableSwitch2(A? r) { switch (r) /* Error */ { +//^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // ^ - // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // [cfe] The type 'A?' is not exhaustively matched by the switch cases. case A(a: Enum.a, b: false): print('A(a, false)'); diff --git a/tests/language/patterns/exhaustiveness/sealed_class_switch_test.dart b/tests/language/patterns/exhaustiveness/sealed_class_switch_test.dart index ca99751d0a7..c422a9e7122 100644 --- a/tests/language/patterns/exhaustiveness/sealed_class_switch_test.dart +++ b/tests/language/patterns/exhaustiveness/sealed_class_switch_test.dart @@ -38,8 +38,9 @@ void exhaustiveSwitch2(A a) { void nonExhaustiveSwitch1(A a) { switch (a) /* Error */ { +//^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // ^ - // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // [cfe] The type 'A' is not exhaustively matched by the switch cases. case B b: print('B'); @@ -52,8 +53,9 @@ void nonExhaustiveSwitch1(A a) { void nonExhaustiveSwitch2(A a) { switch (a) /* Error */ { +//^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // ^ - // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // [cfe] The type 'A' is not exhaustively matched by the switch cases. case C c: print('C'); @@ -66,8 +68,9 @@ void nonExhaustiveSwitch2(A a) { void nonExhaustiveSwitch3(A a) { switch (a) /* Error */ { +//^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // ^ - // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // [cfe] The type 'A' is not exhaustively matched by the switch cases. case B b: print('B'); @@ -108,8 +111,9 @@ void exhaustiveNullableSwitch(A? a) { void nonExhaustiveNullableSwitch1(A? a) { switch (a) /* Error */ { +//^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // ^ - // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // [cfe] The type 'A?' is not exhaustively matched by the switch cases. case A a: print('A'); @@ -119,8 +123,9 @@ void nonExhaustiveNullableSwitch1(A? a) { void nonExhaustiveNullableSwitch2(A? a) { switch (a) /* Error */ { +//^^^^^^ +// [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // ^ - // [analyzer] COMPILE_TIME_ERROR.NON_EXHAUSTIVE_SWITCH // [cfe] The type 'A?' is not exhaustively matched by the switch cases. case B b: print('B');