analyzer: mark fewer types in variable declarations and is-expressions as "used"
analyzer already considers `foo is _A` to _not_ be a use of class _A.
This change makes this policy more consistent by considering code like
`foo is List<_A>` to also not be a "use".
The same is true for non-field variable declarations like `_A? foo;`,
which now includes `List<_A>? foo;`.
Change-Id: I500668d5659ff2d8e992fd448ed6b0f0561eb540
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/225380
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analyzer/lib/src/error/unused_local_elements_verifier.dart b/pkg/analyzer/lib/src/error/unused_local_elements_verifier.dart
index 9984e82..c7a1de2 100644
--- a/pkg/analyzer/lib/src/error/unused_local_elements_verifier.dart
+++ b/pkg/analyzer/lib/src/error/unused_local_elements_verifier.dart
@@ -25,6 +25,12 @@
ClassElement? _enclosingClass;
ExecutableElement? _enclosingExec;
+ /// Non-null when the visitor is inside an [IsExpression]'s type.
+ IsExpression? _enclosingIsExpression;
+
+ /// Non-null when the visitor is inside a [VariableDeclarationList]'s type.
+ VariableDeclarationList? _enclosingVariableDeclaration;
+
GatherUsedLocalElementsVisitor(this._enclosingLibrary);
@override
@@ -114,6 +120,18 @@
}
@override
+ void visitIsExpression(IsExpression node) {
+ var enclosingIsExpressionOld = _enclosingIsExpression;
+ node.expression.accept(this);
+ try {
+ _enclosingIsExpression = node;
+ node.type.accept(this);
+ } finally {
+ _enclosingIsExpression = enclosingIsExpressionOld;
+ }
+ }
+
+ @override
void visitMethodDeclaration(MethodDeclaration node) {
var enclosingExecOld = _enclosingExec;
try {
@@ -178,21 +196,22 @@
usedElements.addElement(element);
}
} else {
- _useIdentifierElement(node, node.readElement);
- _useIdentifierElement(node, node.writeElement);
- _useIdentifierElement(node, node.staticElement);
var parent = node.parent!;
- // If [node] is a method tear-off, assume all parameters are used.
+ _useIdentifierElement(node, node.readElement, parent: parent);
+ _useIdentifierElement(node, node.writeElement, parent: parent);
+ _useIdentifierElement(node, node.staticElement, parent: parent);
+ var grandparent = parent.parent;
+ // If [node] is a tear-off, assume all parameters are used.
var functionReferenceIsCall =
(element is ExecutableElement && parent is MethodInvocation) ||
// named constructor
(element is ConstructorElement &&
parent is ConstructorName &&
- parent.parent is InstanceCreationExpression) ||
+ grandparent is InstanceCreationExpression) ||
// unnamed constructor
(element is ClassElement &&
- parent.parent is ConstructorName &&
- parent.parent!.parent is InstanceCreationExpression);
+ grandparent is ConstructorName &&
+ grandparent.parent is InstanceCreationExpression);
if (element is ExecutableElement &&
isIdentifierRead &&
!functionReferenceIsCall) {
@@ -224,6 +243,19 @@
}
}
+ @override
+ void visitVariableDeclarationList(VariableDeclarationList node) {
+ node.metadata.accept(this);
+ var enclosingVariableDeclarationOld = _enclosingVariableDeclaration;
+ try {
+ _enclosingVariableDeclaration = node;
+ node.type?.accept(this);
+ } finally {
+ _enclosingVariableDeclaration = enclosingVariableDeclarationOld;
+ }
+ node.variables.accept(this);
+ }
+
/// Add [element] as a used member and, if [element] is a setter, add its
/// corresponding getter as a used member.
void _addMemberAndCorrespondingGetter(Element element) {
@@ -236,7 +268,11 @@
}
/// Marks the [element] of [node] as used in the library.
- void _useIdentifierElement(Identifier node, Element? element) {
+ void _useIdentifierElement(
+ Identifier node,
+ Element? element, {
+ required AstNode parent,
+ }) {
if (element == null) {
return;
}
@@ -252,17 +288,17 @@
return;
}
// Ignore places where the element is not actually used.
- if (node.parent is NamedType) {
+ if (parent is NamedType) {
if (element is ClassElement) {
- AstNode parent2 = node.parent!.parent!;
- if (parent2 is IsExpression) {
- return;
- }
- if (parent2 is VariableDeclarationList) {
+ var enclosingVariableDeclaration = _enclosingVariableDeclaration;
+ if (enclosingVariableDeclaration != null) {
// If it's a field's type, it still counts as used.
- if (parent2.parent is! FieldDeclaration) {
+ if (enclosingVariableDeclaration.parent is! FieldDeclaration) {
return;
}
+ } else if (_enclosingIsExpression != null) {
+ // An interface type found in an `is` expression is not used.
+ return;
}
}
}
diff --git a/pkg/analyzer/test/src/diagnostics/unused_element_test.dart b/pkg/analyzer/test/src/diagnostics/unused_element_test.dart
index c377ca0..b9104c2 100644
--- a/pkg/analyzer/test/src/diagnostics/unused_element_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/unused_element_test.dart
@@ -1700,6 +1700,78 @@
@reflectiveTest
class UnusedElementWithNullSafetyTest extends PubPackageResolutionTest {
+ test_class_isUsed_isExpression_expression() async {
+ await assertNoErrorsInCode('''
+class _A {}
+void f(Object p) {
+ if (_A() is int) {
+ }
+}
+''');
+ }
+
+ test_class_notUsed_isExpression_typeArgument() async {
+ await assertErrorsInCode(r'''
+class _A {}
+void f(Object p) {
+ if (p is List<_A>) {
+ }
+}
+''', [
+ error(HintCode.UNUSED_ELEMENT, 6, 2),
+ ]);
+ }
+
+ test_class_notUsed_isExpression_typeInFunctionType() async {
+ await assertErrorsInCode(r'''
+class _A {}
+void f(Object p) {
+ if (p is void Function(_A)) {
+ }
+}
+''', [
+ error(HintCode.UNUSED_ELEMENT, 6, 2),
+ ]);
+ }
+
+ test_class_notUsed_isExpression_typeInTypeParameter() async {
+ await assertErrorsInCode(r'''
+class _A {}
+void f(Object p) {
+ if (p is void Function<T extends _A>()) {
+ }
+}
+''', [
+ error(HintCode.UNUSED_ELEMENT, 6, 2),
+ ]);
+ }
+
+ test_class_notUsed_variableDeclaration() async {
+ await assertErrorsInCode('''
+class _A {}
+void f() {
+ _A? v;
+ print(v);
+}
+print(x) {}
+''', [
+ error(HintCode.UNUSED_ELEMENT, 6, 2),
+ ]);
+ }
+
+ test_class_notUsed_variableDeclaration_typeArgument() async {
+ await assertErrorsInCode('''
+class _A {}
+main() {
+ List<_A>? v;
+ print(v);
+}
+print(x) {}
+''', [
+ error(HintCode.UNUSED_ELEMENT, 6, 2),
+ ]);
+ }
+
test_optionalParameter_isUsed_genericConstructor() async {
await assertNoErrorsInCode('''
class C<T> {