Files
sdk/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart
T
Konstantin Shcheglov 8779d4e9a7 Breaking changes for analyzer version 14.0.0
Change-Id: I3bd6b1bbf60bb1b45f46e62ebf448f27f103f98e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/509561
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
2026-06-11 11:59:47 -07:00

1660 lines
48 KiB
Dart

// Copyright (c) 2019, 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:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
as shared;
import 'package:_fe_analyzer_shared/src/type_inference/variable_bindings.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/scope.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/scope.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_constraint_gatherer.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/error/lint_codes.dart';
import 'package:analyzer/src/dart/resolver/ast_rewrite.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/dart/resolver/named_type_resolver.dart';
import 'package:analyzer/src/dart/resolver/record_type_annotation_resolver.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
import 'package:analyzer/src/dart/resolver/scope_context.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag;
import 'package:analyzer/src/diagnostic/diagnostic_factory.dart';
import 'package:analyzer/src/error/listener.dart';
import 'package:analyzer/src/utilities/extensions/object.dart';
class ResolutionVisitor extends RecursiveAstVisitor<void> {
final LibraryElementImpl _libraryElement;
final TypeProviderImpl _typeProvider;
final LibraryFragmentImpl _libraryFragment;
final DiagnosticReporter _diagnosticReporter;
final AstRewriter _astRewriter;
final NamedTypeResolver _namedTypeResolver;
final RecordTypeAnnotationResolver _recordTypeResolver;
/// Data structure for tracking declared pattern variables.
late final _VariableBinder _patternVariables = _VariableBinder(
errors: _VariableBinderErrors(this),
typeProvider: _typeProvider,
);
/// The set of required operations on types.
final TypeSystemOperations typeSystemOperations;
final TypeConstraintGenerationDataForTesting? dataForTesting;
final ScopeContext _scopeContext;
/// The container with information about local variables.
final LocalVariableInfo _localVariableInfo = LocalVariableInfo();
LabelScope? _labelScope;
UnlabeledBreakContinueContext _unlabeledBreakContinueContext =
UnlabeledBreakContinueContext.root;
LocalFunctionElement? _enclosingClosure;
int _libraryDirectiveIndex = 0;
factory ResolutionVisitor({
required LibraryFragmentImpl libraryFragment,
required DiagnosticListener diagnosticListener,
required Scope nameScope,
required List<LibraryElement> docImportLibraries,
required bool strictInference,
required bool strictCasts,
required TypeConstraintGenerationDataForTesting? dataForTesting,
}) {
var libraryElement = libraryFragment.library;
var typeProvider = libraryElement.typeProvider;
var unitSource = libraryFragment.source;
var diagnosticReporter = DiagnosticReporter(diagnosticListener, unitSource);
var typeSystemOperations = TypeSystemOperations(
libraryFragment.library.typeSystem,
strictCasts: strictCasts,
);
var scopeContext = ScopeContext(
libraryFragment: libraryFragment,
nameScope: nameScope,
docImportLibraries: docImportLibraries,
);
var namedTypeResolver = NamedTypeResolver(
libraryElement,
libraryFragment,
scopeContext,
diagnosticReporter,
strictInference: strictInference,
strictCasts: strictCasts,
typeSystemOperations: typeSystemOperations,
);
var recordTypeResolver = RecordTypeAnnotationResolver(
typeProvider: typeProvider,
diagnosticReporter: diagnosticReporter,
libraryElement: libraryElement,
);
return ResolutionVisitor._(
libraryElement,
typeProvider,
libraryFragment,
diagnosticReporter,
AstRewriter(diagnosticReporter),
namedTypeResolver,
recordTypeResolver,
scopeContext,
typeSystemOperations,
dataForTesting,
);
}
ResolutionVisitor._(
this._libraryElement,
this._typeProvider,
this._libraryFragment,
this._diagnosticReporter,
this._astRewriter,
this._namedTypeResolver,
this._recordTypeResolver,
this._scopeContext,
this.typeSystemOperations,
this.dataForTesting,
);
Scope get nameScope => _scopeContext.nameScope;
/// Set information about enclosing declarations.
void prepareEnclosingDeclarations({
InterfaceElementImpl? enclosingClassElement,
}) {
_namedTypeResolver.enclosingClass = enclosingClassElement;
}
@override
void visitAnnotation(covariant AnnotationImpl node) {
node.visitChildrenWithHooks(this, visitConstructorName: (_) {});
}
@override
void visitAnonymousMethodInvocation(
covariant AnonymousMethodInvocationImpl node,
) {
node.target?.accept(this);
_scopeContext.withLocalScope((scope) {
if (node.parameters case var parameters?) {
scope.addFormalParameterList(parameters);
parameters.accept(this);
}
node.body.accept(this);
});
}
@override
void visitAssignedVariablePattern(
covariant AssignedVariablePatternImpl node,
) {
var name = node.name.lexeme;
var element = nameScope.lookup(name).getter;
node.element = element;
if (element is PromotableElementImpl) {
_localVariableInfo.potentiallyMutatedInScope.add(element);
}
if (element == null) {
// Recovery: the code might try to refer to an instance field.
if (_scopeContext.enclosingInstanceElement
case InterfaceElementImpl enclosingInterfaceElement?) {
var element = enclosingInterfaceElement.inheritanceManager.getMember(
enclosingInterfaceElement,
Name.forLibrary(_libraryElement, name).forSetter,
);
if (element != null) {
node.element = element;
_diagnosticReporter.report(
diag.patternAssignmentNotLocalVariable.at(node.name),
);
return;
}
}
_diagnosticReporter.report(
diag.undefinedIdentifier.withArguments(name: name).at(node.name),
);
} else if (!(element is LocalVariableElement ||
element is FormalParameterElement)) {
_diagnosticReporter.report(
diag.patternAssignmentNotLocalVariable.at(node.name),
);
}
}
@override
void visitBlock(covariant BlockImpl node) {
_scopeContext.withLocalScope((scope) {
node.nameScope = scope;
_defineLocalElements(scope, node.statements);
node.statements.accept(this);
});
}
@override
void visitBlockFunctionBody(covariant BlockFunctionBodyImpl node) {
node.localVariableInfo = _localVariableInfo;
_withUnlabeledBreakContinueContext(UnlabeledBreakContinueContext.root, () {
super.visitBlockFunctionBody(node);
});
}
@override
void visitBreakStatement(covariant BreakStatementImpl node) {
node.target = _lookupBreakOrContinueTarget(
node,
node.label,
isContinue: false,
);
}
@override
void visitCatchClause(covariant CatchClauseImpl node) {
var exceptionTypeNode = node.exceptionType;
exceptionTypeNode?.accept(this);
_scopeContext.withLocalScope((scope) {
if (node.exceptionParameter case var exceptionNode?) {
var fragment = exceptionNode.declaredFragment!;
var element = fragment.element;
scope.add(element);
if (node.exceptionType case var typeNode?) {
element.type = typeNode.typeOrThrow;
} else {
element.type = _typeProvider.objectType;
}
}
var stackTraceNode = node.stackTraceParameter;
if (stackTraceNode != null) {
var fragment = stackTraceNode.declaredFragment!;
var element = fragment.element;
scope.add(element);
element.type = _typeProvider.stackTraceType;
}
node.body.accept(this);
});
}
@override
void visitClassDeclaration(covariant ClassDeclarationImpl node) {
var fragment = node.declaredFragment!;
var element = fragment.element;
_namedTypeResolver.enclosingClass = element;
_scopeContext.visitClassDeclaration(node, visitor: this);
_namedTypeResolver.enclosingClass = null;
}
@override
void visitClassTypeAlias(covariant ClassTypeAliasImpl node) {
var fragment = node.declaredFragment!;
_namedTypeResolver.enclosingClass = fragment.element;
_scopeContext.visitClassTypeAlias(
node,
visitor: this,
visitSuperclass: (superclass) {
_resolveType(declaration: node, clause: null, namedType: superclass);
},
);
_namedTypeResolver.enclosingClass = null;
}
@override
void visitComment(covariant CommentImpl node) {
_scopeContext.visitDocumentationComment(node, this);
}
@override
void visitCompilationUnit(covariant CompilationUnitImpl node) {
node.nameScope = nameScope;
super.visitCompilationUnit(node);
}
@override
void visitConstructorDeclaration(covariant ConstructorDeclarationImpl node) {
_scopeContext.visitConstructorDeclaration(node, visitor: this);
}
@override
void visitConstructorFieldInitializer(
covariant ConstructorFieldInitializerImpl node,
) {
node.visitChildrenWithHooks(this, visitFieldName: (_) {});
}
@override
void visitConstructorName(covariant ConstructorNameImpl node) {
node.visitChildrenWithHooks(this, visitName: (_) {});
}
@override
void visitContinueStatement(covariant ContinueStatementImpl node) {
node.target = _lookupBreakOrContinueTarget(
node,
node.label,
isContinue: true,
);
}
@override
void visitDeclaredIdentifier(covariant DeclaredIdentifierImpl node) {
super.visitDeclaredIdentifier(node);
if (node.type != null) {
node.declaredFragment!.element.type = node.type!.typeOrThrow;
} else {
node.declaredFragment!.element.type = _typeProvider.dynamicType;
}
}
@override
void visitDeclaredVariablePattern(
covariant DeclaredVariablePatternImpl node,
) {
var fragment = node.declaredFragment!;
var element = fragment.element;
node.type?.accept(this);
if (node.type != null) {
element.type = node.type!.typeOrThrow;
} else {
fragment.hasImplicitType = true;
}
var patternContext = node.patternContext;
if (patternContext is ForEachPartsWithPatternImpl) {
fragment.isFinal = patternContext.keyword.keyword == Keyword.FINAL;
} else if (patternContext is PatternVariableDeclarationImpl) {
fragment.isFinal = patternContext.keyword.keyword == Keyword.FINAL;
} else {
fragment.isFinal = node.keyword?.keyword == Keyword.FINAL;
}
}
@override
void visitDoStatement(covariant DoStatementImpl node) {
_withUnlabeledBreakContinueContextNested(node, () {
_visitStatementInScope(node.body);
node.condition.accept(this);
});
}
@override
void visitDotShorthandConstructorInvocation(
covariant DotShorthandConstructorInvocationImpl node,
) {
node.visitChildrenWithHooks(this, visitConstructorName: (_) {});
}
@override
void visitDotShorthandInvocation(covariant DotShorthandInvocationImpl node) {
node.visitChildrenWithHooks(this, visitMemberName: (_) {});
}
@override
void visitDotShorthandPropertyAccess(
covariant DotShorthandPropertyAccessImpl node,
) {
node.visitChildrenWithHooks(this, visitPropertyName: (_) {});
}
@override
void visitEmptyClassBody(EmptyClassBody node) {}
@override
void visitEmptyEnumBody(EmptyEnumBody node) {}
@override
void visitEmptyFunctionBody(covariant EmptyFunctionBodyImpl node) {
node.localVariableInfo = _localVariableInfo;
super.visitEmptyFunctionBody(node);
}
@override
void visitEnumDeclaration(covariant EnumDeclarationImpl node) {
var fragment = node.declaredFragment!;
var element = fragment.element;
_namedTypeResolver.enclosingClass = element;
_scopeContext.visitEnumDeclaration(node, visitor: this);
_namedTypeResolver.enclosingClass = null;
}
@override
void visitExportDirective(covariant ExportDirectiveImpl node) {
super.visitExportDirective(node);
}
@override
void visitExpressionFunctionBody(covariant ExpressionFunctionBodyImpl node) {
node.localVariableInfo = _localVariableInfo;
node.nameScope = nameScope;
super.visitExpressionFunctionBody(node);
}
@override
void visitExtendsClause(covariant ExtendsClauseImpl node) {
_resolveType(
declaration: node.parent as Declaration?,
clause: node,
namedType: node.superclass,
);
}
@override
void visitExtensionDeclaration(covariant ExtensionDeclarationImpl node) {
_scopeContext.visitExtensionDeclaration(node, visitor: this);
}
@override
void visitExtensionTypeDeclaration(
covariant ExtensionTypeDeclarationImpl node,
) {
var fragment = node.declaredFragment!;
var element = fragment.element;
_namedTypeResolver.enclosingClass = element;
_scopeContext.visitExtensionTypeDeclaration(node, visitor: this);
_namedTypeResolver.enclosingClass = null;
}
@override
void visitFieldDeclaration(covariant FieldDeclarationImpl node) {
_scopeContext.visitFieldDeclaration(node, visitor: this);
}
@override
void visitFieldFormalParameter(covariant FieldFormalParameterImpl node) {
_scopeContext.visitFormalParameter(node, visitor: this);
}
@override
void visitForEachPartsWithDeclaration(
covariant ForEachPartsWithDeclarationImpl node,
) {
throw StateError('Should not be invoked');
}
@override
void visitForEachPartsWithPattern(
covariant ForEachPartsWithPatternImpl node,
) {
throw StateError('Should not be invoked');
}
@override
void visitForElement(covariant ForElementImpl node) {
_scopeContext.withLocalScope((scope) {
node.nameScope = scope;
_visitForLoopParts(scope, node.forLoopParts);
_scopeContext.withLocalScope((_) {
node.body.accept(this);
});
});
}
@override
void visitForPartsWithPattern(covariant ForPartsWithPatternImpl node) {
throw StateError('Should not be invoked');
}
@override
void visitForStatement(covariant ForStatementImpl node) {
_withUnlabeledBreakContinueContextNested(node, () {
_scopeContext.withLocalScope((scope) {
node.nameScope = scope;
_visitForLoopParts(scope, node.forLoopParts);
_visitStatementInScope(node.body);
});
});
}
@override
void visitFunctionDeclaration(covariant FunctionDeclarationImpl node) {
var element = node.declaredFragment!.element;
var closure = element.tryCast<LocalFunctionElementImpl>();
_withEnclosingClosure(closure, () {
_scopeContext.visitFunctionDeclaration(node, visitor: this);
});
if (element is LocalFunctionElementImpl) {
element.returnType = node.returnType?.type ?? _typeProvider.dynamicType;
}
}
@override
void visitFunctionExpression(covariant FunctionExpressionImpl node) {
var element = node.declaredFragment!.element;
var closure = element.tryCast<LocalFunctionElementImpl>();
_withEnclosingClosure(closure, () {
_scopeContext.visitFunctionExpression(node, visitor: this);
});
}
@override
void visitFunctionTypeAlias(covariant FunctionTypeAliasImpl node) {
_scopeContext.visitFunctionTypeAlias(node, visitor: this);
}
@override
void visitGenericFunctionType(covariant GenericFunctionTypeImpl node) {
_scopeContext.visitGenericFunctionType(node, visitor: this);
var element = node.declaredFragment!.element;
element.returnType = node.returnType?.type ?? _typeProvider.dynamicType;
var type = FunctionTypeImpl(
typeParameters: element.typeParameters,
formalParameters: element.formalParameters,
returnType: element.returnType,
nullabilitySuffix: _getNullability(node.question != null),
);
element.type = type;
node.type = type;
}
@override
void visitGenericTypeAlias(covariant GenericTypeAliasImpl node) {
_scopeContext.visitGenericTypeAlias(
node,
visitor: this,
enterTypeParameterScope: () {
node.nameScope = nameScope;
},
);
}
@override
void visitHideCombinator(HideCombinator node) {
var scope = nameScope.tryCast<LibraryFragmentScope>();
scope?.importsTrackingActive(false);
try {
super.visitHideCombinator(node);
} finally {
scope?.importsTrackingActive(true);
}
}
@override
void visitIfElement(covariant IfElementImpl node) {
if (node.caseClause case var caseClause?) {
node.expression.accept(this);
_resolveGuardedPattern(
caseClause.guardedPattern,
then: () {
caseClause.nameScope = nameScope;
node.ifTrue.accept(this);
},
);
node.ifFalse?.accept(this);
} else {
node.visitChildren(this);
}
}
@override
void visitIfStatement(covariant IfStatementImpl node) {
if (node.caseClause case var caseClause?) {
node.expression.accept(this);
_resolveGuardedPattern(
caseClause.guardedPattern,
then: () {
caseClause.nameScope = nameScope;
_visitStatementInScope(node.ifTrue);
},
);
_visitStatementInScope(node.ifFalse);
} else {
node.expression.accept(this);
_visitStatementInScope(node.ifTrue);
_visitStatementInScope(node.ifFalse);
}
}
@override
void visitImplementsClause(covariant ImplementsClauseImpl node) {
_resolveImplementsClause(
declaration: node.parent as Declaration,
clause: node,
);
}
@override
void visitImportDirective(covariant ImportDirectiveImpl node) {
var element = node.libraryImport;
if (element != null) {
_setElementAnnotations(node.metadata, element.metadata.annotations);
}
node.visitChildrenWithHooks(this, visitPrefix: (_) {});
}
@override
void visitInstanceCreationExpression(
covariant InstanceCreationExpressionImpl node,
) {
var newNode = _astRewriter.instanceCreationExpression(
nameScope,
node,
libraryElement: _libraryElement,
enclosingInstanceElement: _scopeContext.enclosingInstanceElement,
);
if (newNode != node) {
if (node.constructorName.type.typeArguments != null &&
newNode is MethodInvocation &&
newNode.target is FunctionReference &&
!_libraryElement.featureSet.isEnabled(Feature.constructor_tearoffs)) {
// A function reference with explicit type arguments (an expression of
// the form `a<...>.m(...)` or `p.a<...>.m(...)` where `a` does not
// refer to a class name, nor a type alias), is illegal without the
// constructor tearoff feature.
//
// This is a case where the parser does not report an error, because the
// parser thinks this could be an InstanceCreationExpression.
_diagnosticReporter.report(diag.sdkVersionConstructorTearoffs.at(node));
}
return newNode.accept(this);
}
super.visitInstanceCreationExpression(node);
}
@override
void visitLabeledStatement(LabeledStatement node) {
var unlabeled = node.unlabeled;
var labelScope = _nestLabelScopes(_labelScope, node.labels, unlabeled);
_withLabelScope(labelScope, () {
unlabeled.accept(this);
});
}
@override
void visitLibraryDirective(covariant LibraryDirectiveImpl node) {
++_libraryDirectiveIndex;
var element = node.element;
if (element is LibraryElementImpl && _libraryDirectiveIndex == 1) {
_setElementAnnotations(node.metadata, element.metadata.annotations);
}
super.visitLibraryDirective(node);
}
@override
void visitMethodDeclaration(covariant MethodDeclarationImpl node) {
_scopeContext.visitMethodDeclaration(node, visitor: this);
}
@override
void visitMethodInvocation(covariant MethodInvocationImpl node) {
var newNode = _astRewriter.methodInvocation(nameScope, node);
if (newNode != node) {
return newNode.accept(this);
}
node.visitChildrenWithHooks(
this,
visitMethodName: (methodName) {
if (node.realTarget == null) {
methodName.accept(this);
}
},
);
}
@override
void visitMixinDeclaration(covariant MixinDeclarationImpl node) {
_scopeContext.visitMixinDeclaration(node, visitor: this);
}
@override
void visitMixinOnClause(covariant MixinOnClauseImpl node) {
_resolveMixinOnClause(
declaration: node.parent as Declaration,
clause: node,
);
}
@override
void visitNamedArgument(covariant NamedArgumentImpl node) {
node.visitChildren(this);
}
@override
void visitNamedType(covariant NamedTypeImpl node) {
node.typeArguments?.accept(this);
_namedTypeResolver.resolve(node, dataForTesting: dataForTesting);
if (_namedTypeResolver.rewriteResult != null) {
_namedTypeResolver.rewriteResult!.accept(this);
}
}
@override
void visitNativeFunctionBody(covariant NativeFunctionBodyImpl node) {
node.localVariableInfo = _localVariableInfo;
super.visitNativeFunctionBody(node);
}
@override
void visitPartDirective(covariant PartDirectiveImpl node) {
var partInclude = node.partInclude;
if (partInclude != null) {
_setElementAnnotations(node.metadata, partInclude.metadata.annotations);
}
super.visitPartDirective(node);
}
@override
void visitPatternAssignment(covariant PatternAssignmentImpl node) {
_scopeContext.withLocalScope((scope) {
var variables = _computeDeclaredPatternVariables(node.pattern);
scope.addAll(variables);
node.pattern.accept(this);
node.expression.accept(this);
});
}
@override
void visitPatternVariableDeclarationStatement(
covariant PatternVariableDeclarationStatementImpl node,
) {
node.declaration.accept(this);
}
@override
void visitPrefixedIdentifier(covariant PrefixedIdentifierImpl node) {
var newNode = _astRewriter.prefixedIdentifier(nameScope, node);
if (newNode != node) {
return newNode.accept(this);
}
node.visitChildrenWithHooks(this, visitIdentifier: (_) {});
}
@override
void visitPrimaryConstructorBody(covariant PrimaryConstructorBodyImpl node) {
_scopeContext.visitPrimaryConstructorBody(node, visitor: this);
}
@override
void visitPropertyAccess(covariant PropertyAccessImpl node) {
var newNode = _astRewriter.propertyAccess(nameScope, node);
if (newNode != node) {
return newNode.accept(this);
}
node.visitChildrenWithHooks(this, visitPropertyName: (_) {});
}
@override
void visitRecordTypeAnnotation(covariant RecordTypeAnnotationImpl node) {
node.visitChildren(this);
_recordTypeResolver.resolve(node);
}
@override
void visitRedirectingConstructorInvocation(
covariant RedirectingConstructorInvocationImpl node,
) {
node.visitChildrenWithHooks(this, visitConstructorName: (_) {});
}
@override
void visitRegularFormalParameter(covariant RegularFormalParameterImpl node) {
var element = node.declaredFragment!.element;
if (node.functionTypedSuffix case var functionTypedSuffix?) {
_scopeContext.visitFormalParameter(node, visitor: this);
element.type = FunctionTypeImpl(
typeParameters: [
for (var typeParameter
in functionTypedSuffix.typeParameters?.typeParameters ??
const <TypeParameterImpl>[])
typeParameter.declaredFragment!.element,
],
formalParameters: [
for (var parameter in functionTypedSuffix.formalParameters.parameters)
parameter.declaredFragment!.element,
],
returnType: node.type?.type ?? _typeProvider.dynamicType,
nullabilitySuffix: _getNullability(
functionTypedSuffix.question != null,
),
);
return;
}
node.visitChildren(this);
if (node.type case var type?) {
element.type = type.type ?? _typeProvider.dynamicType;
} else if (element.type is InvalidTypeImpl) {
// TODO(scheglov): review and improve resolution to not rely on dynamic.
element.type = _typeProvider.dynamicType;
}
}
@override
void visitShowCombinator(ShowCombinator node) {
var scope = nameScope.tryCast<LibraryFragmentScope>();
scope?.importsTrackingActive(false);
try {
super.visitShowCombinator(node);
} finally {
scope?.importsTrackingActive(true);
}
}
@override
void visitSimpleIdentifier(covariant SimpleIdentifierImpl node) {
var newNode = _astRewriter.simpleIdentifier(nameScope, node);
if (newNode != node) {
return newNode.accept(this);
}
var scopeLookupResult = nameScope.lookup(node.name);
node.scopeLookupResult = scopeLookupResult;
var element = scopeLookupResult.getter;
if (element is PromotableElementImpl) {
node.element = element;
if (element is JoinPatternVariableElementImpl) {
element.references.add(node);
}
if (node.inSetterContext()) {
_localVariableInfo.potentiallyMutatedInScope.add(element);
if (element is PatternVariableElementImpl &&
element.isVisitingWhenClause) {
_diagnosticReporter.report(
diag.patternVariableAssignmentInsideGuard.at(node),
);
}
}
}
}
@override
void visitSuperConstructorInvocation(
covariant SuperConstructorInvocationImpl node,
) {
node.visitChildrenWithHooks(this, visitConstructorName: (_) {});
}
@override
void visitSuperFormalParameter(covariant SuperFormalParameterImpl node) {
_scopeContext.visitFormalParameter(node, visitor: this);
}
@override
void visitSwitchCase(SwitchCase node) {
throw StateError('Should not be invoked');
}
@override
void visitSwitchDefault(SwitchDefault node) {
throw StateError('Should not be invoked');
}
@override
void visitSwitchExpression(covariant SwitchExpressionImpl node) {
node.expression.accept(this);
for (var case_ in node.cases) {
_resolveGuardedPattern(
case_.guardedPattern,
then: () {
case_.nameScope = nameScope;
case_.expression.accept(this);
},
);
}
}
@override
void visitSwitchExpressionCase(SwitchExpressionCase node) {
throw StateError('Should not be invoked');
}
@override
void visitSwitchPatternCase(SwitchPatternCase node) {
throw StateError('Should not be invoked');
}
@override
void visitSwitchStatement(covariant SwitchStatementImpl node) {
var labelScope = _labelScope;
for (var member in node.members) {
labelScope = _nestLabelScopes(labelScope, member.labels, member);
}
_withUnlabeledBreakContinueContextNested(node, () {
_withLabelScope(labelScope, () {
node.expression.accept(this);
for (var group in node.memberGroups) {
_patternVariables.switchStatementSharedCaseScopeStart(group);
for (var member in group.members) {
if (member is SwitchCaseImpl) {
member.expression.accept(this);
} else if (member is SwitchDefaultImpl) {
_patternVariables.switchStatementSharedCaseScopeEmpty(group);
} else if (member is SwitchPatternCaseImpl) {
_resolveGuardedPattern(
member.guardedPattern,
sharedCaseScopeKey: group,
);
} else {
throw UnimplementedError('(${member.runtimeType}) $member');
}
}
if (group.hasLabels) {
_patternVariables.switchStatementSharedCaseScopeEmpty(group);
}
group.variables = _patternVariables
.switchStatementSharedCaseScopeFinish(group);
_scopeContext.withLocalScope((scope) {
group.members.lastOrNull?.nameScope = scope;
_defineLocalElements(scope, group.statements);
scope.addAll(group.variables.values);
group.statements.accept(this);
});
}
});
});
}
@override
void visitTypeParameter(covariant TypeParameterImpl node) {
var fragment = node.declaredFragment!;
node.metadata.accept(this);
var boundNode = node.bound;
if (boundNode != null) {
boundNode.accept(this);
if (fragment.previousFragment == null) {
fragment.element.bound = boundNode.type;
}
}
}
@override
void visitVariableDeclaration(covariant VariableDeclarationImpl node) {
var element = node.declaredFragment!.element;
if (element is LocalVariableElementImpl) {
var varList = node.parent as VariableDeclarationListImpl;
if (varList.type case var typeNode?) {
element.type = typeNode.typeOrThrow;
} else {
// TODO(scheglov): review and improve resolution to not rely on dynamic.
element.type = _typeProvider.dynamicType;
}
}
node.initializer?.accept(this);
}
@override
void visitVariableDeclarationList(
covariant VariableDeclarationListImpl node,
) {
_scopeContext.visitVariableDeclarationList(node, visitor: this);
}
@override
void visitWhileStatement(covariant WhileStatementImpl node) {
_withUnlabeledBreakContinueContextNested(node, () {
node.condition.accept(this);
_visitStatementInScope(node.body);
});
}
@override
void visitWithClause(covariant WithClauseImpl node) {
_resolveWithClause(declaration: node.parent as Declaration?, clause: node);
}
List<BindPatternVariableElementImpl> _computeDeclaredPatternVariables(
DartPatternImpl pattern,
) {
var variables = _computePatternVariables(pattern);
return variables.values
.whereType<BindPatternVariableElementImpl>()
.toList();
}
Map<String, PatternVariableElementImpl> _computePatternVariables(
DartPatternImpl pattern, {
Object? sharedCaseScopeKey,
}) {
_patternVariables.casePatternStart();
pattern.accept(_PatternVariableBinderVisitor(_patternVariables));
return _patternVariables.casePatternFinish(
sharedCaseScopeKey: sharedCaseScopeKey,
);
}
void _defineLocalElements(LocalScope scope, List<Statement> statements) {
for (var statement in statements) {
statement = statement.unlabeled;
switch (statement) {
case FunctionDeclarationStatementImpl():
var declaration = statement.functionDeclaration;
var element = declaration.declaredFragment!.element;
scope.add(element);
case PatternVariableDeclarationStatementImpl():
var declaration = statement.declaration;
_definePatternVariableDeclarationElements(scope, declaration);
case VariableDeclarationStatementImpl():
scope.addAll(statement.variables.declaredElements);
}
}
}
void _definePatternVariableDeclarationElements(
LocalScope scope,
PatternVariableDeclarationImpl declaration,
) {
var pattern = declaration.pattern;
var variables = _computeDeclaredPatternVariables(pattern);
declaration.elements = variables;
scope.addAll(variables);
}
NullabilitySuffix _getNullability(bool hasQuestion) {
if (hasQuestion) {
return NullabilitySuffix.question;
} else {
return NullabilitySuffix.none;
}
}
AstNode? _lookupBreakOrContinueTarget(
AstNode parentNode,
LabelReferenceImpl? labelNode, {
required bool isContinue,
}) {
if (labelNode == null) {
return isContinue
? _unlabeledBreakContinueContext.continueTarget
: _unlabeledBreakContinueContext.breakTarget;
} else {
var labelName = labelNode.name.lexeme;
var definingScope = _labelScope?.lookup(labelName);
if (definingScope == null) {
_diagnosticReporter.report(
diag.labelUndefined.withArguments(name: labelName).at(labelNode),
);
return null;
}
labelNode.element = definingScope.element;
if (_enclosingClosure case var enclosingClosure?) {
var labelFragment = definingScope.element.firstFragment;
var labelContainer = labelFragment.enclosingFragment;
if (!identical(labelContainer, enclosingClosure.firstFragment)) {
_diagnosticReporter.report(
diag.labelInOuterScope.withArguments(name: labelName).at(labelNode),
);
}
}
var node = definingScope.node;
if (isContinue &&
node is! DoStatement &&
node is! ForStatement &&
node is! SwitchMember &&
node is! WhileStatement) {
_diagnosticReporter.report(diag.continueLabelInvalid.at(parentNode));
}
return node;
}
}
LabelScope? _nestLabelScopes(
LabelScope? outer,
List<Label> labels,
AstNode node,
) {
var current = outer;
for (var label in labels) {
var labelElement = label.declaredFragment!.element;
current = LabelScope(current, labelElement, node);
}
return current;
}
void _resolveGuardedPattern(
GuardedPatternImpl guardedPattern, {
Object? sharedCaseScopeKey,
void Function()? then,
}) {
var variables = _computePatternVariables(
guardedPattern.pattern,
sharedCaseScopeKey: sharedCaseScopeKey,
);
// Matched variables are available in `whenClause`.
_scopeContext.withLocalScope((scope) {
scope.addAll(variables.values);
guardedPattern.variables = variables;
guardedPattern.pattern.accept(this);
for (var variable in variables.values) {
variable.isVisitingWhenClause = true;
}
try {
guardedPattern.whenClause?.accept(this);
} finally {
for (var variable in variables.values) {
variable.isVisitingWhenClause = false;
}
}
if (then != null) {
then();
}
});
}
void _resolveImplementsClause({
required Declaration? declaration,
required ImplementsClauseImpl? clause,
}) {
if (clause == null) return;
_resolveTypes(
declaration: declaration,
clause: clause,
namedTypes: clause.interfaces,
);
}
void _resolveMixinOnClause({
required Declaration? declaration,
required MixinOnClauseImpl clause,
}) {
_resolveTypes(
declaration: declaration,
clause: clause,
namedTypes: clause.superclassConstraints,
);
}
/// Resolves the given [namedType], reports errors if the resulting type
/// is not valid in the context of the [declaration] and [clause].
void _resolveType({
required Declaration? declaration,
required AstNode? clause,
required NamedTypeImpl namedType,
}) {
_namedTypeResolver.classHierarchy_namedType = namedType;
visitNamedType(namedType);
_namedTypeResolver.classHierarchy_namedType = null;
if (_namedTypeResolver.hasErrorReported) {
return;
}
var type = namedType.typeOrThrow;
var enclosingElement = _namedTypeResolver.enclosingClass;
if (enclosingElement is ExtensionTypeElementImpl) {
_verifyExtensionElementImplements(enclosingElement, namedType, type);
return;
}
if (namedType.isSynthetic) {
return;
}
switch (type.element) {
case ClassElement():
return;
case MixinElement():
if (clause is ImplementsClause ||
clause is MixinOnClause ||
clause is WithClause) {
return;
}
}
if (_libraryFragment.shouldIgnoreUndefinedNamedType(namedType)) {
return;
}
LocatableDiagnostic? diagnosticCode;
switch (clause) {
case null:
if (declaration is ClassTypeAlias) {
diagnosticCode = diag.mixinWithNonClassSuperclass;
}
case ExtendsClause():
if (declaration is ClassDeclaration) {
diagnosticCode = declaration.withClause == null
? diag.extendsNonClass
: diag.mixinWithNonClassSuperclass;
}
case ImplementsClause():
diagnosticCode = diag.implementsNonClass;
case MixinOnClause():
diagnosticCode = diag.mixinSuperClassConstraintNonInterface;
case WithClause():
diagnosticCode = diag.mixinOfNonClass;
}
// Should not happen.
if (diagnosticCode == null) {
assert(false);
return;
}
var firstToken = namedType.importPrefix?.name ?? namedType.name;
var offset = firstToken.offset;
var length = namedType.name.end - offset;
_diagnosticReporter.report(
diagnosticCode.atOffset(offset: offset, length: length),
);
}
/// Resolve the types in the given list of type names.
///
/// @param typeNames the type names to be resolved
/// @param nonTypeError the error to produce if the type name is defined to be
/// something other than a type
/// @param enumTypeError the error to produce if the type name is defined to
/// be an enum
/// @param dynamicTypeError the error to produce if the type name is "dynamic"
/// @return an array containing all of the types that were resolved.
void _resolveTypes({
required Declaration? declaration,
required AstNode clause,
required NodeList<NamedTypeImpl> namedTypes,
}) {
for (var namedType in namedTypes) {
_resolveType(
declaration: declaration,
clause: clause,
namedType: namedType,
);
}
}
void _resolveWithClause({
required Declaration? declaration,
required WithClauseImpl? clause,
}) {
if (clause == null) return;
for (var namedType in clause.mixinTypes) {
_namedTypeResolver.withClause_namedType = namedType;
_resolveType(
declaration: declaration,
clause: clause,
namedType: namedType,
);
_namedTypeResolver.withClause_namedType = null;
}
}
void _verifyExtensionElementImplements(
ExtensionTypeElementImpl declaredElement,
NamedTypeImpl node,
TypeImpl type,
) {
var typeSystem = _libraryElement.typeSystem;
if (!typeSystem.isValidExtensionTypeSuperinterface(type)) {
_diagnosticReporter.report(
diag.extensionTypeImplementsDisallowedType
.withArguments(type: type)
.at(node),
);
return;
}
var declaredRepresentation = declaredElement.representation.type;
if (typeSystem.isSubtypeOf(declaredRepresentation, type)) {
return;
}
// When `type` is an extension type.
if (type is InterfaceTypeImpl) {
var implementedRepresentation = type.representationType;
if (implementedRepresentation != null) {
if (!typeSystem.isSubtypeOf(
declaredRepresentation,
implementedRepresentation,
)) {
_diagnosticReporter.report(
diag.extensionTypeImplementsRepresentationNotSupertype
.withArguments(
implementedRepresentationType: implementedRepresentation,
implementedExtensionTypeName: type.element.name ?? '',
representationType: declaredRepresentation,
extensionTypeName: declaredElement.name ?? '',
)
.at(node),
);
}
return;
}
}
_diagnosticReporter.report(
diag.extensionTypeImplementsNotSupertype
.withArguments(type: type, representationType: declaredRepresentation)
.at(node),
);
}
void _visitForLoopParts(LocalScope scope, ForLoopPartsImpl node) {
switch (node) {
case ForEachPartsWithDeclarationImpl():
node.iterable.accept(this);
var element = node.loopVariable.declaredFragment!.element;
scope.add(element);
node.loopVariable.accept(this);
case ForEachPartsWithIdentifierImpl():
node.iterable.accept(this);
node.identifier.accept(this);
case ForEachPartsWithPatternImpl():
node.iterable.accept(this);
var variables = _computeDeclaredPatternVariables(node.pattern);
node.variables = variables;
scope.addAll(variables);
node.pattern.accept(this);
node.metadata.accept(this);
case ForPartsWithDeclarationsImpl():
scope.addAll(node.variables.declaredElements);
node.variables.accept(this);
node.condition?.accept(this);
node.updaters.accept(this);
case ForPartsWithExpressionImpl():
node.initialization?.accept(this);
node.condition?.accept(this);
node.updaters.accept(this);
case ForPartsWithPatternImpl():
_definePatternVariableDeclarationElements(scope, node.variables);
node.variables.accept(this);
node.condition?.accept(this);
node.updaters.accept(this);
}
}
/// Visits [statement], ensuring that if it is a block it is visited as such,
/// and if it is not, it is wrapped in an implicit block scope.
///
/// This implements the requirement from the specification that sub-statements
/// of control flow statements (like `if`, `while`, `do`, `for`) introduce
/// a new scope, even if they are not explicitly blocks.
void _visitStatementInScope(StatementImpl? statement) {
if (statement != null) {
if (statement is BlockImpl) {
visitBlock(statement);
} else {
_scopeContext.withLocalScope((scope) {
_defineLocalElements(scope, [statement]);
statement.accept(this);
});
}
}
}
void _withEnclosingClosure(
LocalFunctionElement? scope,
void Function() callback,
) {
var previous = _enclosingClosure;
try {
_enclosingClosure = scope;
callback();
} finally {
_enclosingClosure = previous;
}
}
void _withLabelScope(LabelScope? scope, void Function() callback) {
var previous = _labelScope;
try {
_labelScope = scope;
callback();
} finally {
_labelScope = previous;
}
}
void _withUnlabeledBreakContinueContext(
UnlabeledBreakContinueContext context,
void Function() callback,
) {
var previous = _unlabeledBreakContinueContext;
try {
_unlabeledBreakContinueContext = context;
callback();
} finally {
_unlabeledBreakContinueContext = previous;
}
}
void _withUnlabeledBreakContinueContextNested(
Statement statement,
void Function() callback,
) {
_withUnlabeledBreakContinueContext(
_unlabeledBreakContinueContext.nest(statement),
callback,
);
}
/// We always build local elements for [VariableDeclarationStatement]s and
/// [FunctionDeclarationStatement]s in blocks, because invalid code might try
/// to use forward references.
/// Associate each of the annotation [nodes] with the corresponding
/// [ElementAnnotation] in [annotations].
static void _setElementAnnotations(
List<AnnotationImpl> nodes,
List<ElementAnnotationImpl> annotations,
) {
int nodeCount = nodes.length;
if (nodeCount != annotations.length) {
throw StateError(
'Found $nodeCount annotation nodes and '
'${annotations.length} element annotations',
);
}
for (int i = 0; i < nodeCount; i++) {
nodes[i].elementAnnotation = annotations[i];
}
}
}
class _PatternVariableBinderVisitor extends ThrowingAstVisitor<void> {
final VariableBinder<DartPatternImpl, PatternVariableElementImpl> _binder;
_PatternVariableBinderVisitor(this._binder);
@override
void visitAssignedVariablePattern(AssignedVariablePattern node) {}
@override
void visitCastPattern(CastPattern node) {
node.pattern.accept(this);
}
@override
void visitConstantPattern(ConstantPattern node) {}
@override
void visitDeclaredVariablePattern(
covariant DeclaredVariablePatternImpl node,
) {
var element = node.declaredFragment!.element;
_binder.add(node.name.lexeme, element);
}
@override
void visitListPattern(ListPattern node) {
node.elements.accept(this);
}
@override
void visitLogicalAndPattern(LogicalAndPattern node) {
node.leftOperand.accept(this);
node.rightOperand.accept(this);
}
@override
void visitLogicalOrPattern(covariant LogicalOrPatternImpl node) {
_binder.logicalOrPatternStart();
node.leftOperand.accept(this);
_binder.logicalOrPatternFinishLeft();
node.rightOperand.accept(this);
_binder.logicalOrPatternFinish(node);
}
@override
void visitMapPattern(MapPattern node) {
node.elements.accept(this);
}
@override
void visitMapPatternEntry(MapPatternEntry node) {
node.value.accept(this);
}
@override
void visitNullAssertPattern(NullAssertPattern node) {
node.pattern.accept(this);
}
@override
void visitNullCheckPattern(NullCheckPattern node) {
node.pattern.accept(this);
}
@override
void visitObjectPattern(ObjectPattern node) {
node.fields.accept(this);
}
@override
void visitParenthesizedPattern(ParenthesizedPattern node) {
node.pattern.accept(this);
}
@override
void visitPatternField(PatternField node) {
node.pattern.accept(this);
}
@override
void visitRecordPattern(RecordPattern node) {
node.fields.accept(this);
}
@override
void visitRelationalPattern(RelationalPattern node) {}
@override
void visitRestPatternElement(RestPatternElement node) {
node.pattern?.accept(this);
}
@override
void visitWildcardPattern(WildcardPattern node) {}
}
class _VariableBinder
extends VariableBinder<DartPatternImpl, PatternVariableElementImpl> {
final TypeProvider typeProvider;
_VariableBinder({required super.errors, required this.typeProvider});
@override
JoinPatternVariableElementImpl joinPatternVariables({
required Object key,
required List<PatternVariableElementImpl> components,
required shared.JoinedPatternVariableInconsistency inconsistency,
}) {
var first = components.first;
List<PatternVariableElementImpl> expandedVariables;
if (key is LogicalOrPatternImpl) {
expandedVariables = components
.expand((variable) {
if (variable is JoinPatternVariableElementImpl) {
return variable.variables;
} else {
return [variable];
}
})
.toList(growable: false);
} else if (key is SwitchStatementCaseGroup) {
expandedVariables = components;
} else {
throw UnimplementedError('(${key.runtimeType}) $key');
}
var resultFragment = JoinPatternVariableFragmentImpl(
name: first.name,
firstTokenOffset: null,
variables: expandedVariables.map((e) => e.firstFragment).toList(),
inconsistency: inconsistency.maxWithAll(
components.whereType<JoinPatternVariableElementImpl>().map(
(e) => e.inconsistency,
),
),
);
resultFragment.enclosingFragment = first.firstFragment.enclosingFragment;
return resultFragment.element;
}
}
class _VariableBinderErrors
implements
VariableBinderErrors<DartPatternImpl, PatternVariableElementImpl> {
final ResolutionVisitor visitor;
_VariableBinderErrors(this.visitor);
@override
void assertInErrorRecovery() {
// TODO(scheglov): implement assertInErrorRecovery
throw UnimplementedError();
}
@override
void duplicateVariablePattern({
required String name,
required covariant BindPatternVariableElementImpl original,
required covariant BindPatternVariableElementImpl duplicate,
}) {
visitor._diagnosticReporter.report(
DiagnosticFactory().duplicateDefinitionForNodes(
visitor._diagnosticReporter.source,
diag.duplicateVariablePattern.withArguments(name: name),
duplicate.node.name,
original.node.name,
),
);
duplicate.isDuplicate = true;
}
@override
void logicalOrPatternBranchMissingVariable({
required covariant LogicalOrPatternImpl node,
required bool hasInLeft,
required String name,
required PromotableElementImpl variable,
}) {
visitor._diagnosticReporter.report(
diag.missingVariablePattern
.withArguments(name: name)
.at(hasInLeft ? node.rightOperand : node.leftOperand),
);
}
}
extension _VariableDeclarationList on VariableDeclarationList {
List<LocalVariableElementImpl> get declaredElements {
return variables
.map((v) => v.declaredFragment!.element)
.cast<LocalVariableElementImpl>()
.toList();
}
}