[analyzer] Fix dead code reporting for null-aware accesses.
A null-aware access can result in dead code if the target has static
type `Null`. The analyzer wasn't properly accounting for this,
resulting in some confusing ranges reported for the DEAD_CODE warning.
This change causes the following expressions to report dead code for
the code ranges indiced by `^`:
Null myNullVar = null;
myNullVar?[index];
// ^^^^^^ DEAD_CODE
myNullVar?[index] = value;
// ^^^^^^^^^^^^^^ DEAD_CODE
myNullVar?.method();
// ^^^^^^^^ DEAD_CODE
myNullVar?.property;
// ^^^^^^^^ DEAD_CODE
myNullVar?.property = value;
// ^^^^^^^^^^^^^^^^ DEAD_CODE
Note that the bug was confined solely to the logic that reports the
DEAD_CODE warning; there is no change to the reachability inferred by
flow analysis (and hence, this is a non-breaking change).
Fixes https://github.com/dart-lang/sdk/issues/60364.
Bug: https://github.com/dart-lang/sdk/issues/60364
Change-Id: I068826282fba6b9057e9c27d1d9310c65714e203
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/416723
Auto-Submit: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/unreachable_via_never.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/unreachable_via_never.dart
index 7800df9..c2c740e 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/unreachable_via_never.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/unreachable_via_never.dart
@@ -50,7 +50,7 @@
}
void nullAwareAccess(Null Function() f, Object? Function() g) {
- f()?.extensionMethod(/*unreachable*/ 1);
+ f()?./*analyzer.unreachable*/extensionMethod(/*unreachable*/ 1);
g()?.extensionMethod(2);
}
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index be27c65..c4876dd 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -1239,6 +1239,7 @@
do {
_unfinishedNullShorts.removeLast();
flowAnalysis.flow!.nullAwareAccess_end();
+ nullSafetyDeadCodeVerifier.flowEnd(node);
} while (identical(_unfinishedNullShorts.last, node));
if (node is! CascadeExpression) {
// Make the static type of `node` (or whatever it was rewritten to)
@@ -1404,6 +1405,7 @@
if (node.isNullAware) {
_startNullAwareAccess(node, node.target);
+ nullSafetyDeadCodeVerifier.visitNode(node.index);
}
var result = _propertyElementResolver.resolveIndexExpression(
@@ -1457,6 +1459,7 @@
}
if (node.isNullAware) {
_startNullAwareAccess(node, node.target);
+ nullSafetyDeadCodeVerifier.visitNode(node.propertyName);
}
inferenceLogWriter?.exitLValue(node);
@@ -2838,6 +2841,7 @@
if (node.isNullAware) {
_startNullAwareAccess(node, node.target);
+ nullSafetyDeadCodeVerifier.visitNode(node.index);
}
var result = _propertyElementResolver.resolveIndexExpression(
@@ -3055,6 +3059,7 @@
if (node.isNullAware) {
_startNullAwareAccess(node, target);
+ nullSafetyDeadCodeVerifier.visitNode(node.methodName);
}
node.typeArguments?.accept(this);
@@ -3945,6 +3950,7 @@
PropertyAccessImpl node, TypeImpl contextType) {
if (node.isNullAware) {
_startNullAwareAccess(node, node.target);
+ nullSafetyDeadCodeVerifier.visitNode(node.propertyName);
}
var result = _propertyElementResolver.resolvePropertyAccess(
@@ -4009,6 +4015,7 @@
if (function is PropertyAccessImpl && function.isNullAware) {
_startNullAwareAccess(function, function.target);
+ nullSafetyDeadCodeVerifier.visitNode(node.argumentList);
}
_functionExpressionInvocationResolver.resolve(node, whyNotPromotedArguments,
diff --git a/pkg/analyzer/test/src/diagnostics/dead_code_test.dart b/pkg/analyzer/test/src/diagnostics/dead_code_test.dart
index 872cf69..7e2f4a9 100644
--- a/pkg/analyzer/test/src/diagnostics/dead_code_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/dead_code_test.dart
@@ -202,6 +202,66 @@
]);
}
+ test_nullAwareIndexedRead() async {
+ await assertErrorsInCode(r'''
+void f(Null n, int i) {
+ n?[i];
+ print('reached');
+}
+''', [
+ // Dead range: `i]`
+ error(WarningCode.DEAD_CODE, 29, 2)
+ ]);
+ }
+
+ test_nullAwareIndexedWrite() async {
+ await assertErrorsInCode(r'''
+void f(Null n, int i, int j) {
+ n?[i] = j;
+ print('reached');
+}
+''', [
+ // Dead range: `i] = j`
+ error(WarningCode.DEAD_CODE, 36, 6)
+ ]);
+ }
+
+ test_nullAwareMethodInvocation() async {
+ await assertErrorsInCode(r'''
+void f(Null n, int i) {
+ n?.foo(i);
+ print('reached');
+}
+''', [
+ // Dead range: `foo(i)`
+ error(WarningCode.DEAD_CODE, 29, 6)
+ ]);
+ }
+
+ test_nullAwarePropertyRead() async {
+ await assertErrorsInCode(r'''
+void f(Null n) {
+ n?.p;
+ print('reached');
+}
+''', [
+ // Dead range: `p`
+ error(WarningCode.DEAD_CODE, 22, 1)
+ ]);
+ }
+
+ test_nullAwarePropertyWrite() async {
+ await assertErrorsInCode(r'''
+void f(Null n, int i) {
+ n?.p = i;
+ print('reached');
+}
+''', [
+ // Dead range: `p = i`
+ error(WarningCode.DEAD_CODE, 29, 5)
+ ]);
+ }
+
test_objectPattern_neverTypedGetter() async {
await assertErrorsInCode(r'''
class A {