[analyzer] Combine ElementUsageFrontierDetectors

Originally attempted to stab away at the cost of
https://dart-review.googlesource.com/c/sdk/+/478701 - and while it did
get better it was still much more expensive than before.

With https://dart-review.googlesource.com/c/sdk/+/478701 having been
reverted I've rebased and this is the new numbers:

Parent CL:

    42,517,120,185      instructions:u
    42,517,237,423      instructions:u
    42,514,559,436      instructions:u

This CL:

    41,687,688,230      instructions:u
    41,687,684,807      instructions:u
    41,687,673,825      instructions:u

Saves ~827 mio instructions

Change-Id: I752163a27dd779f27fafecce219cdc15e0f73d29
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/482241
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Jens Johansen
2026-02-27 07:13:03 -08:00
committed by Commit Queue
parent be0a084a73
commit 951471ebf2
7 changed files with 255 additions and 236 deletions
@@ -26,6 +26,7 @@ import 'package:analyzer/src/error/deprecated_functionality_verifier.dart';
import 'package:analyzer/src/error/deprecated_member_use_verifier.dart';
import 'package:analyzer/src/error/do_not_submit_member_use_verifier.dart';
import 'package:analyzer/src/error/doc_comment_verifier.dart';
import 'package:analyzer/src/error/element_usage_detector.dart';
import 'package:analyzer/src/error/element_usage_frontier_detector.dart';
import 'package:analyzer/src/error/error_handler_verifier.dart';
import 'package:analyzer/src/error/experimental_member_use_verifier.dart';
@@ -66,7 +67,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
final DeprecatedFunctionalityVerifier _deprecatedFunctionalityVerifier;
final List<ElementUsageFrontierDetector> _elementUsageFrontierDetectors;
final ElementUsageFrontierDetector _elementUsageFrontierDetector;
final ErrorHandlerVerifier _errorHandlerVerifier;
@@ -117,29 +118,29 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
_diagnosticReporter,
_currentLibrary,
),
_elementUsageFrontierDetectors = [
ElementUsageFrontierDetector(
workspacePackage: workspacePackage,
elementUsageSet: const DeprecatedElementUsageSet(),
elementUsageReporter: DeprecatedElementUsageReporter(
diagnosticReporter: _diagnosticReporter,
_elementUsageFrontierDetector = ElementUsageFrontierDetector(
workspacePackage: workspacePackage,
usagesAndReporters: [
UsageSetAndReporter(
const DeprecatedElementUsageSet(),
DeprecatedElementUsageReporter(
diagnosticReporter: _diagnosticReporter,
),
),
),
ElementUsageFrontierDetector(
workspacePackage: workspacePackage,
elementUsageSet: const ExperimentalElementUsageSet(),
elementUsageReporter: ExperimentalElementUsageReporter(
diagnosticReporter: _diagnosticReporter,
UsageSetAndReporter(
const ExperimentalElementUsageSet(),
ExperimentalElementUsageReporter(
diagnosticReporter: _diagnosticReporter,
),
),
),
ElementUsageFrontierDetector(
workspacePackage: workspacePackage,
elementUsageSet: const DoNotSubmitElementUsageSet(),
elementUsageReporter: DoNotSubmitElementUsageReporter(
diagnosticReporter: _diagnosticReporter,
UsageSetAndReporter(
const DoNotSubmitElementUsageSet(),
DoNotSubmitElementUsageReporter(
diagnosticReporter: _diagnosticReporter,
),
),
),
],
],
),
_errorHandlerVerifier = ErrorHandlerVerifier(
_diagnosticReporter,
typeProvider,
@@ -159,9 +160,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
),
_widgetPreviewVerifier = WidgetPreviewVerifier(_diagnosticReporter),
_workspacePackage = workspacePackage {
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(_currentLibrary);
}
_elementUsageFrontierDetector.pushElement(_currentLibrary);
_inDoNotStoreMember = _currentLibrary.metadata.hasDoNotStore;
}
@@ -188,17 +187,13 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
@override
void visitAssignmentExpression(AssignmentExpression node) {
for (var v in _elementUsageFrontierDetectors) {
v.assignmentExpression(node);
}
_elementUsageFrontierDetector.assignmentExpression(node);
super.visitAssignmentExpression(node);
}
@override
void visitBinaryExpression(BinaryExpression node) {
for (var v in _elementUsageFrontierDetectors) {
v.binaryExpression(node);
}
_elementUsageFrontierDetector.binaryExpression(node);
_checkForInvariantNanComparison(node);
_checkForInvariantNullComparison(node);
_invalidAccessVerifier.verifyBinary(node);
@@ -233,9 +228,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
bool wasInDoNotStoreMember = _inDoNotStoreMember;
_deprecatedFunctionalityVerifier.classDeclaration(node);
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(element);
}
_elementUsageFrontierDetector.pushElement(element);
if (element.metadata.hasDoNotStore) {
_inDoNotStoreMember = true;
}
@@ -250,9 +243,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
} finally {
_enclosingClass = null;
_invalidAccessVerifier._enclosingClass = null;
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
_inDoNotStoreMember = wasInDoNotStoreMember;
}
}
@@ -262,16 +253,12 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
_immutableVerifier.checkDeclaration(node, nameToken: node.name);
_checkForInvalidSealedSuperclass(node);
_deprecatedFunctionalityVerifier.classTypeAlias(node);
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(node.declaredFragment!.element);
}
_elementUsageFrontierDetector.pushElement(node.declaredFragment!.element);
try {
super.visitClassTypeAlias(node);
} finally {
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
}
}
@@ -314,29 +301,21 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
initializers: node.initializers,
);
var element = node.declaredFragment!.element;
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(element);
}
for (var v in _elementUsageFrontierDetectors) {
v.constructorDeclaration(node);
}
_elementUsageFrontierDetector.pushElement(element);
_elementUsageFrontierDetector.constructorDeclaration(node);
_deprecatedFunctionalityVerifier.constructorDeclaration(node);
try {
super.visitConstructorDeclaration(node);
} finally {
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
}
}
@override
void visitConstructorName(ConstructorName node) {
for (var v in _elementUsageFrontierDetectors) {
v.constructorName(node);
}
_elementUsageFrontierDetector.constructorName(node);
_deprecatedFunctionalityVerifier.constructorName(node);
super.visitConstructorName(node);
}
@@ -359,20 +338,13 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
);
}
}
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(node.declaredFragment!.element);
}
for (var v in _elementUsageFrontierDetectors) {
v.formalParameter(node);
}
_elementUsageFrontierDetector.pushElement(node.declaredFragment!.element);
_elementUsageFrontierDetector.formalParameter(node);
try {
super.visitDefaultFormalParameter(node);
} finally {
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
}
}
@@ -381,9 +353,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
DotShorthandConstructorInvocation node,
) {
_deprecatedFunctionalityVerifier.dotShorthandConstructorInvocation(node);
for (var v in _elementUsageFrontierDetectors) {
v.dotShorthandConstructorInvocation(node);
}
_elementUsageFrontierDetector.dotShorthandConstructorInvocation(node);
_checkForLiteralConstructorUseInDotShorthand(node);
super.visitDotShorthandConstructorInvocation(node);
}
@@ -391,41 +361,31 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
@override
void visitDotShorthandInvocation(DotShorthandInvocation node) {
_deprecatedFunctionalityVerifier.dotShorthandInvocation(node);
for (var v in _elementUsageFrontierDetectors) {
v.dotShorthandInvocation(node);
}
_elementUsageFrontierDetector.dotShorthandInvocation(node);
super.visitDotShorthandInvocation(node);
}
@override
void visitDotShorthandPropertyAccess(DotShorthandPropertyAccess node) {
for (var v in _elementUsageFrontierDetectors) {
v.dotShorthandPropertyAccess(node);
}
_elementUsageFrontierDetector.dotShorthandPropertyAccess(node);
super.visitDotShorthandPropertyAccess(node);
}
@override
void visitEnumDeclaration(EnumDeclaration node) {
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(node.declaredFragment!.element);
}
_elementUsageFrontierDetector.pushElement(node.declaredFragment!.element);
_deprecatedFunctionalityVerifier.enumDeclaration(node);
try {
super.visitEnumDeclaration(node);
} finally {
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
}
}
@override
void visitExportDirective(covariant ExportDirectiveImpl node) {
for (var v in _elementUsageFrontierDetectors) {
v.exportDirective(node);
}
_elementUsageFrontierDetector.exportDirective(node);
_checkForInternalExport(node);
super.visitExportDirective(node);
}
@@ -440,47 +400,35 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
@override
void visitExtensionDeclaration(ExtensionDeclaration node) {
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(node.declaredFragment!.element);
}
_elementUsageFrontierDetector.pushElement(node.declaredFragment!.element);
try {
super.visitExtensionDeclaration(node);
} finally {
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
}
}
@override
void visitExtensionOverride(ExtensionOverride node) {
for (var v in _elementUsageFrontierDetectors) {
v.extensionOverride(node);
}
_elementUsageFrontierDetector.extensionOverride(node);
super.visitExtensionOverride(node);
}
@override
void visitExtensionTypeDeclaration(ExtensionTypeDeclaration node) {
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(node.declaredFragment!.element);
}
_elementUsageFrontierDetector.pushElement(node.declaredFragment!.element);
try {
super.visitExtensionTypeDeclaration(node);
} finally {
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
}
}
@override
void visitFieldDeclaration(FieldDeclaration node) {
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(node.firstVariableElement);
}
_elementUsageFrontierDetector.pushElement(node.firstVariableElement);
try {
super.visitFieldDeclaration(node);
@@ -522,9 +470,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
}
}
} finally {
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
}
}
@@ -538,9 +484,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
void visitFunctionDeclaration(FunctionDeclaration node) {
bool wasInDoNotStoreMember = _inDoNotStoreMember;
var element = node.declaredFragment!.element;
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(element);
}
_elementUsageFrontierDetector.pushElement(element);
if (element.metadata.hasDoNotStore) {
_inDoNotStoreMember = true;
}
@@ -559,9 +503,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
);
super.visitFunctionDeclaration(node);
} finally {
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
_inDoNotStoreMember = wasInDoNotStoreMember;
}
}
@@ -585,9 +527,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
@override
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
for (var v in _elementUsageFrontierDetectors) {
v.functionExpressionInvocation(node);
}
_elementUsageFrontierDetector.functionExpressionInvocation(node);
super.visitFunctionExpressionInvocation(node);
}
@@ -595,16 +535,12 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
void visitFunctionTypeAlias(FunctionTypeAlias node) {
_checkStrictInferenceReturnType(node.returnType, node, node.name.lexeme);
_checkStrictInferenceInParameters(node.parameters);
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(node.declaredFragment!.element);
}
_elementUsageFrontierDetector.pushElement(node.declaredFragment!.element);
try {
super.visitFunctionTypeAlias(node);
} finally {
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
}
}
@@ -634,24 +570,18 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
node.name.lexeme,
);
}
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(node.declaredFragment!.element);
}
_elementUsageFrontierDetector.pushElement(node.declaredFragment!.element);
try {
super.visitGenericTypeAlias(node);
} finally {
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
}
}
@override
void visitImportDirective(ImportDirective node) {
for (var v in _elementUsageFrontierDetectors) {
v.importDirective(node);
}
_elementUsageFrontierDetector.importDirective(node);
var import = node.libraryImport;
if (import != null && import.prefix?.isDeferred == true) {
_checkForLoadLibraryFunction(node, import);
@@ -662,9 +592,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
@override
void visitIndexExpression(IndexExpression node) {
for (var v in _elementUsageFrontierDetectors) {
v.indexExpression(node);
}
_elementUsageFrontierDetector.indexExpression(node);
super.visitIndexExpression(node);
}
@@ -672,9 +600,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
void visitInstanceCreationExpression(
covariant InstanceCreationExpressionImpl node,
) {
for (var v in _elementUsageFrontierDetectors) {
v.instanceCreationExpression(node);
}
_elementUsageFrontierDetector.instanceCreationExpression(node);
_deprecatedFunctionalityVerifier.instanceCreationExpression(node);
_nullSafeApiVerifier.instanceCreation(node);
_checkForLiteralConstructorUse(node);
@@ -694,9 +620,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
var element = node.declaredFragment!.element;
var enclosingElement = element.enclosingElement;
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(element);
}
_elementUsageFrontierDetector.pushElement(element);
if (element.metadata.hasDoNotStore) {
_inDoNotStoreMember = true;
}
@@ -776,18 +700,14 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
super.visitMethodDeclaration(node);
} finally {
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
_inDoNotStoreMember = wasInDoNotStoreMember;
}
}
@override
void visitMethodInvocation(covariant MethodInvocationImpl node) {
for (var v in _elementUsageFrontierDetectors) {
v.methodInvocation(node);
}
_elementUsageFrontierDetector.methodInvocation(node);
_deprecatedFunctionalityVerifier.methodInvocation(node);
_errorHandlerVerifier.verifyMethodInvocation(node);
_nullSafeApiVerifier.methodInvocation(node);
@@ -802,9 +722,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
_invalidAccessVerifier._enclosingClass = _enclosingClass;
_deprecatedFunctionalityVerifier.mixinDeclaration(node);
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(element);
}
_elementUsageFrontierDetector.pushElement(element);
try {
_immutableVerifier.checkDeclaration(node, nameToken: node.name);
@@ -813,17 +731,13 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
} finally {
_enclosingClass = null;
_invalidAccessVerifier._enclosingClass = null;
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
}
}
@override
void visitNamedType(NamedType node) {
for (var v in _elementUsageFrontierDetectors) {
v.namedType(node);
}
_elementUsageFrontierDetector.namedType(node);
_invalidAccessVerifier.verifyNamedType(node);
var question = node.question;
if (question != null) {
@@ -845,18 +759,14 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
@override
void visitPatternField(PatternField node) {
for (var v in _elementUsageFrontierDetectors) {
v.patternField(node);
}
_elementUsageFrontierDetector.patternField(node);
_invalidAccessVerifier.verifyPatternField(node as PatternFieldImpl);
super.visitPatternField(node);
}
@override
void visitPostfixExpression(PostfixExpression node) {
for (var v in _elementUsageFrontierDetectors) {
v.postfixExpression(node);
}
_elementUsageFrontierDetector.postfixExpression(node);
if (node.operator.type == TokenType.BANG &&
node.operand.typeOrThrow.isDartCoreNull) {
_diagnosticReporter.report(diag.nullCheckAlwaysFails.at(node));
@@ -866,23 +776,17 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
@override
void visitPrefixExpression(PrefixExpression node) {
for (var v in _elementUsageFrontierDetectors) {
v.prefixExpression(node);
}
_elementUsageFrontierDetector.prefixExpression(node);
super.visitPrefixExpression(node);
}
@override
void visitPrimaryConstructorBody(PrimaryConstructorBody node) {
var element = node.declaration?.declaredFragment!.element;
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(element);
}
_elementUsageFrontierDetector.pushElement(element);
// TODO(srawlins): Account for @doNotStore, as in `visitFunctionDeclaration`.
super.visitPrimaryConstructorBody(node);
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
}
@override
@@ -896,9 +800,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
void visitRedirectingConstructorInvocation(
RedirectingConstructorInvocation node,
) {
for (var v in _elementUsageFrontierDetectors) {
v.redirectingConstructorInvocation(node);
}
_elementUsageFrontierDetector.redirectingConstructorInvocation(node);
super.visitRedirectingConstructorInvocation(node);
}
@@ -918,55 +820,40 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
@override
void visitSimpleFormalParameter(SimpleFormalParameter node) {
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(node.declaredFragment!.element);
}
for (var v in _elementUsageFrontierDetectors) {
v.formalParameter(node);
}
_elementUsageFrontierDetector.pushElement(node.declaredFragment!.element);
_elementUsageFrontierDetector.formalParameter(node);
try {
super.visitSimpleFormalParameter(node);
} finally {
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
}
}
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
for (var v in _elementUsageFrontierDetectors) {
v.simpleIdentifier(node);
}
_elementUsageFrontierDetector.simpleIdentifier(node);
_invalidAccessVerifier.verify(node);
super.visitSimpleIdentifier(node);
}
@override
void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
for (var v in _elementUsageFrontierDetectors) {
v.superConstructorInvocation(node);
}
_elementUsageFrontierDetector.superConstructorInvocation(node);
_invalidAccessVerifier.verifySuperConstructorInvocation(node);
super.visitSuperConstructorInvocation(node);
}
@override
void visitSuperFormalParameter(SuperFormalParameter node) {
for (var v in _elementUsageFrontierDetectors) {
v.superFormalParameter(node);
}
_elementUsageFrontierDetector.superFormalParameter(node);
_checkFinalParameter(node, node.keyword);
super.visitSuperFormalParameter(node);
}
@override
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
for (var v in _elementUsageFrontierDetectors) {
v.pushElement(node.firstVariableElement);
}
_elementUsageFrontierDetector.pushElement(node.firstVariableElement);
if (!_invalidAccessVerifier._inTestDirectory) {
for (var decl in node.variables.variables) {
@@ -977,9 +864,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
try {
super.visitTopLevelVariableDeclaration(node);
} finally {
for (var v in _elementUsageFrontierDetectors) {
v.popElement();
}
_elementUsageFrontierDetector.popElement();
}
}
@@ -63,12 +63,15 @@ class DeprecatedElementUsageReporter implements ElementUsageReporter<String> {
class DeprecatedElementUsageSet implements ElementUsageSet<String> {
const DeprecatedElementUsageSet();
/// The message in the deprecated annotation on the given [element], or
@override
bool get reliesOnlyOnElementMetadata => true;
/// The message in the deprecated annotation in [elementMetadata], or
/// the empty string if the annotation does not have a message, or `null` if
/// the element doesn't have a deprecated annotation.
@override
String? getTagInfo(Element element) {
for (var annotation in element.metadata.annotations) {
String? getTagInfo(Element _, Metadata elementMetadata) {
for (var annotation in elementMetadata.annotations) {
if (!annotation.isDeprecated) continue;
var value = annotation.computeConstantValue();
if (value == null) continue;
@@ -37,6 +37,9 @@ class DoNotSubmitElementUsageSet implements ElementUsageSet<()> {
const DoNotSubmitElementUsageSet();
@override
()? getTagInfo(Element element) =>
element.metadata.hasDoNotSubmit ? () : null;
bool get reliesOnlyOnElementMetadata => true;
@override
()? getTagInfo(Element _, Metadata elementMetadata) =>
elementMetadata.hasDoNotSubmit ? () : null;
}
@@ -24,17 +24,21 @@ class ElementUsageDetector<TagInfo extends Object> {
/// parameter of [ElementUsageReporter.report].
final WorkspacePackage? _workspacePackage;
/// The set of elements to detect usages of.
final ElementUsageSet<TagInfo> elementUsageSet;
/// What to do when a usage of an element in [elementUsageSet] is detected.
final ElementUsageReporter<TagInfo> elementUsageReporter;
final List<UsageSetAndReporter> usagesArbitrary = [];
final List<UsageSetAndReporter> usagesMetadataOnly = [];
ElementUsageDetector({
required WorkspacePackage? workspacePackage,
required this.elementUsageSet,
required this.elementUsageReporter,
}) : _workspacePackage = workspacePackage;
required List<UsageSetAndReporter> usagesAndReporters,
}) : _workspacePackage = workspacePackage {
for (var usageSetAndReporter in usagesAndReporters) {
if (usageSetAndReporter.elementUsageSet.reliesOnlyOnElementMetadata) {
usagesMetadataOnly.add(usageSetAndReporter);
} else {
usagesArbitrary.add(usageSetAndReporter);
}
}
}
void assignmentExpression(AssignmentExpression node) {
checkUsage(node.readElement, node.leftHandSide);
@@ -47,7 +51,7 @@ class ElementUsageDetector<TagInfo extends Object> {
}
/// Reports the usage of [element] at [node] if [element] is in
/// [elementUsageSet].
/// any of [usagesMetadataOnly] or [usagesArbitrary].
void checkUsage(Element? element, AstNode node) {
if (element == null) {
return;
@@ -61,8 +65,33 @@ class ElementUsageDetector<TagInfo extends Object> {
return;
}
var tagInfo = elementUsageSet.getTagInfo(element);
if (tagInfo == null) return;
List<UsageSetAndReporter>? givesNonNullResults;
var elementMetadata = element.metadata;
if (elementMetadata.annotations.isNotEmpty) {
for (int i = 0; i < usagesMetadataOnly.length; i++) {
if (!shouldCheckMetadataOnlyForIndex(i)) continue;
var usagesAndReporter = usagesMetadataOnly[i];
var tagInfo = usagesAndReporter.elementUsageSet.getTagInfo(
element,
elementMetadata,
);
if (tagInfo != null) {
(givesNonNullResults ??= []).add(usagesAndReporter);
}
}
}
for (int i = 0; i < usagesArbitrary.length; i++) {
if (!shouldCheckArbitraryForIndex(i)) continue;
var usagesAndReporter = usagesArbitrary[i];
var tagInfo = usagesAndReporter.elementUsageSet.getTagInfo(
element,
elementMetadata,
);
if (tagInfo != null) {
(givesNonNullResults ??= []).add(usagesAndReporter);
}
}
if (givesNonNullResults == null) return;
if (_isLocalParameter(element, node)) {
return;
@@ -120,12 +149,15 @@ class ElementUsageDetector<TagInfo extends Object> {
// `ConstructorDeclaration.errorRange` here. This would stray from the API
// of passing a SyntacticEntity here.
elementUsageReporter.report(
errorEntity,
displayName,
tagInfo,
isInSamePackage: _isLibraryInWorkspacePackage(element.library),
);
for (var reportThis in givesNonNullResults) {
reportThis.elementUsageReporter.report(
errorEntity,
displayName,
// Getting it again might not be ideal...
reportThis.elementUsageSet.getTagInfo(element, elementMetadata)!,
isInSamePackage: _isLibraryInWorkspacePackage(element.library),
);
}
}
void constructorDeclaration(ConstructorDeclaration node) {
@@ -266,6 +298,18 @@ class ElementUsageDetector<TagInfo extends Object> {
_invocationArguments(node.element, node.argumentList);
}
/// If false [checkUsage] may skip the call to `getTagInfo` on the [i]th
/// entry of [usagesArbitrary].
bool shouldCheckArbitraryForIndex(int i) {
return true;
}
/// If false [checkUsage] may skip the call to `getTagInfo` on the [i]th
/// entry of [usagesMetadataOnly].
bool shouldCheckMetadataOnlyForIndex(int i) {
return true;
}
void simpleIdentifier(SimpleIdentifier node) {
// Don't report declared identifiers.
if (node.inDeclarationContext()) {
@@ -426,12 +470,28 @@ abstract class ElementUsageReporter<TagInfo extends Object> {
/// string). If there is no auxiliary information, supply `()` for this type
/// parameter.
abstract class ElementUsageSet<TagInfo extends Object> {
/// If [getTagInfo] only relies on the element metadata and can only return
/// non-null if the metadata is not empty.
///
/// Used to skip calls to [getTagInfo] is there is no metadata.
bool get reliesOnlyOnElementMetadata;
/// If [element] is in the set of elements that [ElementUsageDetector] should
/// detect usages of, returns auxiliary information associated with [element].
///
/// [elementMetadata] is the result of [element].metadata, but should be used
/// to avoid the overhead of asking again.
///
/// Otherwise returns `null`.
///
/// For example, [DeprecatedElementUsageSet]'s implementation of this method
/// returns the deprecation message if [element] is deprecated.
TagInfo? getTagInfo(Element element);
TagInfo? getTagInfo(Element element, Metadata elementMetadata);
}
class UsageSetAndReporter<TagInfo extends Object> {
final ElementUsageSet<TagInfo> elementUsageSet;
final ElementUsageReporter<TagInfo> elementUsageReporter;
UsageSetAndReporter(this.elementUsageSet, this.elementUsageReporter);
}
@@ -13,35 +13,96 @@ import 'package:analyzer/src/error/element_usage_detector.dart';
/// that _are_ in the set.
class ElementUsageFrontierDetector<TagInfo extends Object>
extends ElementUsageDetector<TagInfo> {
/// Stack corresponding to elements being visited that might be in
/// [elementUsageSet].
/// Stack corresponding to elements being visited that might be in ith
/// [usagesArbitrary], i.e. the first index corresponds to [usagesArbitrary]
/// and the second index to the stack depth.
///
/// Each element indicates whether the corresponding element is in
/// [elementUsageSet] or not.
final List<bool> _inElementStack = [false];
/// Each element indicates whether the corresponding element is in ith
/// [usagesArbitrary] or not.
final List<List<bool>> _inElementStacksArbitrary = [];
/// Stack corresponding to elements being visited that might be in ith
/// [usagesMetadataOnly], i.e. the first index corresponds to
/// [usagesMetadataOnly] and the second index to the stack depth.
///
/// Each element indicates whether the corresponding element is in ith
/// [usagesMetadataOnly] or not.
final List<List<bool>> _inElementStacksMetadataOnly = [];
ElementUsageFrontierDetector({
required super.workspacePackage,
required super.elementUsageSet,
required super.elementUsageReporter,
});
required super.usagesAndReporters,
}) {
for (int i = 0; i < usagesArbitrary.length; i++) {
_inElementStacksArbitrary.add([false]);
}
for (int i = 0; i < usagesMetadataOnly.length; i++) {
_inElementStacksMetadataOnly.add([false]);
}
}
@override
void checkUsage(Element? element, AstNode node) {
if (_inElementStack.last) {
return;
bool allTrue = true;
for (var inElementStack in _inElementStacksArbitrary) {
if (!inElementStack.last) {
allTrue = false;
break;
}
}
if (allTrue) {
for (var inElementStack in _inElementStacksMetadataOnly) {
if (!inElementStack.last) {
allTrue = false;
break;
}
}
}
if (allTrue) return;
super.checkUsage(element, node);
}
void popElement() {
_inElementStack.removeLast();
for (var inElementStack in _inElementStacksArbitrary) {
inElementStack.removeLast();
}
for (var inElementStack in _inElementStacksMetadataOnly) {
inElementStack.removeLast();
}
}
void pushElement(Element? element) {
var value = element != null && elementUsageSet.getTagInfo(element) != null;
var newValue = _inElementStack.last || value;
_inElementStack.add(newValue);
var elementMetadata = element?.metadata;
for (int i = 0; i < _inElementStacksArbitrary.length; i++) {
var inElementStack = _inElementStacksArbitrary[i];
var newValue = inElementStack.last;
if (!newValue && element != null) {
var elementUsageSet = usagesArbitrary[i].elementUsageSet;
newValue =
elementUsageSet.getTagInfo(element, elementMetadata!) != null;
}
inElementStack.add(newValue);
}
for (int i = 0; i < _inElementStacksMetadataOnly.length; i++) {
var inElementStack = _inElementStacksMetadataOnly[i];
var newValue = inElementStack.last;
if (!newValue && element != null) {
var elementUsageSet = usagesMetadataOnly[i].elementUsageSet;
newValue =
elementUsageSet.getTagInfo(element, elementMetadata!) != null;
}
inElementStack.add(newValue);
}
}
@override
bool shouldCheckArbitraryForIndex(int i) {
return !_inElementStacksArbitrary[i].last;
}
@override
bool shouldCheckMetadataOnlyForIndex(int i) {
return !_inElementStacksMetadataOnly[i].last;
}
}
@@ -40,6 +40,9 @@ class ExperimentalElementUsageSet implements ElementUsageSet<()> {
const ExperimentalElementUsageSet();
@override
()? getTagInfo(Element element) =>
element.metadata.annotations.any((e) => e.isExperimental) ? () : null;
bool get reliesOnlyOnElementMetadata => true;
@override
()? getTagInfo(Element _, Metadata elementMetadata) =>
elementMetadata.annotations.any((e) => e.isExperimental) ? () : null;
}
@@ -16,7 +16,7 @@ import 'package:analyzer/error/error.dart';
import 'package:analyzer/src/error/deprecated_member_use_verifier.dart' // ignore: implementation_imports
show DeprecatedElementUsageSet, normalizeDeprecationMessage;
import 'package:analyzer/src/error/element_usage_detector.dart' // ignore: implementation_imports
show ElementUsageReporter;
show ElementUsageReporter, UsageSetAndReporter;
import 'package:analyzer/src/error/element_usage_frontier_detector.dart' // ignore: implementation_imports
show ElementUsageFrontierDetector;
import 'package:analyzer/src/utilities/extensions/ast.dart'; // ignore: implementation_imports
@@ -95,8 +95,12 @@ class _RecursiveVisitor extends RecursiveAstVisitor<void> {
_RecursiveVisitor(MultiAnalysisRule rule, WorkspacePackage package)
: _deprecatedVerifier = ElementUsageFrontierDetector(
workspacePackage: package,
elementUsageSet: const DeprecatedElementUsageSet(),
elementUsageReporter: _DeprecatedElementUsageReporter(rule: rule),
usagesAndReporters: [
UsageSetAndReporter(
const DeprecatedElementUsageSet(),
_DeprecatedElementUsageReporter(rule: rule),
),
],
);
@override