Fix DEAD_CODE issue when `break label` is found inside a for-each.
Fixes #31714.
Bug: https://github.com/dart-lang/sdk/issues/31714
Change-Id: I36580a16604765497ee117625cb0b035603a69f3
Reviewed-on: https://dart-review.googlesource.com/69281
Commit-Queue: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 193cf09..6016cae 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -2222,17 +2222,17 @@
if (lhsResult != null) {
bool value = lhsResult.value.toBoolValue();
if (value == true && isBarBar) {
- // report error on else block: true || !e!
+ // Report error on "else" block: true || !e!
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.rightOperand);
- // only visit the LHS:
+ // Only visit the LHS:
lhsCondition?.accept(this);
return null;
} else if (value == false && isAmpAmp) {
- // report error on if block: false && !e!
+ // Report error on "if" block: false && !e!
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.rightOperand);
- // only visit the LHS:
+ // Only visit the LHS:
lhsCondition?.accept(this);
return null;
}
@@ -2288,13 +2288,13 @@
_getConstantBooleanValue(conditionExpression);
if (result != null) {
if (result.value.toBoolValue() == true) {
- // report error on else block: true ? 1 : !2!
+ // Report error on "else" block: true ? 1 : !2!
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.elseExpression);
node.thenExpression?.accept(this);
return null;
} else {
- // report error on if block: false ? !1! : 2
+ // Report error on "if" block: false ? !1! : 2
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.thenExpression);
node.elseExpression?.accept(this);
@@ -2315,7 +2315,7 @@
Object visitExportDirective(ExportDirective node) {
ExportElement exportElement = node.element;
if (exportElement != null) {
- // The element is null when the URI is invalid
+ // The element is null when the URI is invalid.
LibraryElement library = exportElement.exportedLibrary;
if (library != null && !library.isSynthetic) {
for (Combinator combinator in node.combinators) {
@@ -2335,7 +2335,7 @@
_getConstantBooleanValue(conditionExpression);
if (result != null) {
if (result.value.toBoolValue() == true) {
- // report error on else block: if(true) {} else {!}
+ // Report error on else block: if(true) {} else {!}
Statement elseStatement = node.elseStatement;
if (elseStatement != null) {
_errorReporter.reportErrorForNode(
@@ -2344,7 +2344,7 @@
return null;
}
} else {
- // report error on if block: if (false) {!} else {}
+ // Report error on if block: if (false) {!} else {}
_errorReporter.reportErrorForNode(
HintCode.DEAD_CODE, node.thenStatement);
node.elseStatement?.accept(this);
@@ -2419,8 +2419,8 @@
for (int i = 0; i < numOfCatchClauses; i++) {
CatchClause catchClause = catchClauses[i];
if (catchClause.onKeyword != null) {
- // on-catch clause found, verify that the exception type is not a
- // subtype of a previous on-catch exception type
+ // An on-catch clause was found; verify that the exception type is not a
+ // subtype of a previous on-catch exception type.
DartType currentType = catchClause.exceptionType?.type;
if (currentType != null) {
if (currentType.isObject) {
@@ -2430,7 +2430,7 @@
// following catch clauses (and don't visit them).
catchClause?.accept(this);
if (i + 1 != numOfCatchClauses) {
- // this catch clause is not the last in the try statement
+ // This catch clause is not the last in the try statement.
CatchClause nextCatchClause = catchClauses[i + 1];
CatchClause lastCatchClause = catchClauses[numOfCatchClauses - 1];
int offset = nextCatchClause.offset;
@@ -2464,7 +2464,7 @@
// (and don't visit them).
catchClause?.accept(this);
if (i + 1 != numOfCatchClauses) {
- // this catch clause is not the last in the try statement
+ // This catch clause is not the last in the try statement.
CatchClause nextCatchClause = catchClauses[i + 1];
CatchClause lastCatchClause = catchClauses[numOfCatchClauses - 1];
int offset = nextCatchClause.offset;
@@ -2487,7 +2487,7 @@
_getConstantBooleanValue(conditionExpression);
if (result != null) {
if (result.value.toBoolValue() == false) {
- // report error on if block: while (false) {!}
+ // Report error on while block: while (false) {!}
_errorReporter.reportErrorForNode(HintCode.DEAD_CODE, node.body);
return null;
}
@@ -3367,7 +3367,13 @@
bool outerBreakValue = _enclosingBlockContainsBreak;
_enclosingBlockContainsBreak = false;
try {
- return _nodeExits(node.iterable);
+ bool iterableExits = _nodeExits(node.iterable);
+ // Discard whether the for-each body exits; since the for-each iterable
+ // may be empty, execution may never enter the body, so it doesn't matter
+ // if it exits or not. We still must visit the body, to accurately
+ // manage `_enclosingBlockBreaksLabel`.
+ _nodeExits(node.body);
+ return iterableExits;
} finally {
_enclosingBlockContainsBreak = outerBreakValue;
}
diff --git a/pkg/analyzer/test/generated/non_hint_code_kernel_test.dart b/pkg/analyzer/test/generated/non_hint_code_kernel_test.dart
index cb43b69..61ad9cc 100644
--- a/pkg/analyzer/test/generated/non_hint_code_kernel_test.dart
+++ b/pkg/analyzer/test/generated/non_hint_code_kernel_test.dart
@@ -25,6 +25,18 @@
@override
@failingTest
+ test_deadCode_afterForEachWithBreakLabel() async {
+ await super.test_deadCode_afterForEachWithBreakLabel();
+ }
+
+ @override
+ @failingTest
+ test_deadCode_afterForWithBreakLabel() async {
+ await super.test_deadCode_afterForWithBreakLabel();
+ }
+
+ @override
+ @failingTest
test_unusedImport_annotationOnDirective() async {
await super.test_unusedImport_annotationOnDirective();
}
diff --git a/pkg/analyzer/test/generated/non_hint_code_test.dart b/pkg/analyzer/test/generated/non_hint_code_test.dart
index 6d2c384..8ac787c 100644
--- a/pkg/analyzer/test/generated/non_hint_code_test.dart
+++ b/pkg/analyzer/test/generated/non_hint_code_test.dart
@@ -236,6 +236,42 @@
verify([source]);
}
+ test_deadCode_afterForEachWithBreakLabel() async {
+ Source source = addSource('''
+f() {
+ named: {
+ for (var x in [1]) {
+ if (x == null)
+ break named;
+ }
+ return;
+ }
+ print('not dead');
+}
+''');
+ await computeAnalysisResult(source);
+ assertNoErrors(source);
+ verify([source]);
+ }
+
+ test_deadCode_afterForWithBreakLabel() async {
+ Source source = addSource('''
+f() {
+ named: {
+ for (int i = 0; i < 7; i++) {
+ if (i == null)
+ break named;
+ }
+ return;
+ }
+ print('not dead');
+}
+''');
+ await computeAnalysisResult(source);
+ assertNoErrors(source);
+ verify([source]);
+ }
+
test_deprecatedAnnotationUse_namedParameter_inDefiningFunction() async {
Source source = addSource(r'''
f({@deprecated int x}) => x;