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 <brianwilkerson@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
committed by
Commit Queue
parent
973e75ab35
commit
c6352b0e49
@@ -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);
|
||||
|
||||
@@ -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<void> {
|
||||
}
|
||||
}
|
||||
|
||||
@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<ConstantPattern, DartObjectImpl>? 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> {
|
||||
}
|
||||
}
|
||||
|
||||
void _validateSwitchExhaustiveness({
|
||||
required AstNode node,
|
||||
required Token switchKeyword,
|
||||
required Expression scrutinee,
|
||||
required List<AstNode> caseNodes,
|
||||
required Map<ConstantPattern, DartObjectImpl> constantPatternValues,
|
||||
}) {
|
||||
final scrutineeType = scrutinee.typeOrThrow;
|
||||
final scrutineeTypeEx = _exhaustivenessCache.getStaticType(scrutineeType);
|
||||
|
||||
final caseNodesWithSpace = <AstNode>[];
|
||||
final caseSpaces = <Space>[];
|
||||
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<Space>? 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> {
|
||||
}
|
||||
}
|
||||
|
||||
void _validateSwitchStatement_patterns(SwitchStatement node,
|
||||
Map<ConstantPattern, DartObjectImpl> constantPatternValues) {
|
||||
DartType expressionType = node.expression.staticType!;
|
||||
StaticType type = _exhaustivenessCache.getStaticType(expressionType);
|
||||
List<Space> cases = [];
|
||||
List<SwitchMember> 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<Space>? 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<ConstantPattern, DartObjectImpl> constantPatternValues) f,
|
||||
) {
|
||||
final previous = _constantPatternValues;
|
||||
final values = _constantPatternValues = {};
|
||||
f(values);
|
||||
_constantPatternValues = previous;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,9 +40,8 @@ Space convertPatternToSpace(
|
||||
AnalyzerExhaustivenessCache cache,
|
||||
DartPattern pattern,
|
||||
Map<ConstantPattern, DartObjectImpl> 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<String, Space> fields = {};
|
||||
|
||||
@@ -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),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 extends bool>(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 extends bool>(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>(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>(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;
|
||||
}
|
||||
}
|
||||
''');
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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)');
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user