| // 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/types/shared_type.dart'; | |
| import 'package:analyzer/dart/analysis/analysis_options.dart'; | |
| import 'package:analyzer/dart/element/scope.dart'; | |
| import 'package:analyzer/error/listener.dart'; | |
| import 'package:analyzer/src/dart/analysis/testing_data.dart'; | |
| import 'package:analyzer/src/dart/ast/ast.dart'; | |
| import 'package:analyzer/src/dart/element/element.dart'; | |
| import 'package:analyzer/src/dart/element/inheritance_manager3.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/resolver/element_binding_visitor.dart'; | |
| import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart'; | |
| import 'package:analyzer/src/dart/resolver/scope_analyzer.dart'; | |
| import 'package:analyzer/src/dart/resolver/type_analyzer.dart'; | |
| import 'package:analyzer/src/dart/resolver/type_analyzer_options.dart'; | |
| import 'package:analyzer/src/utilities/extensions/object.dart'; | |
| /// Resolves AST in the enclosing context given to the constructor: units | |
| /// during library analysis, and variable initializers, default values, | |
| /// annotations, and constructor initializers during summary linking. | |
| /// | |
| /// Resolution runs two walks. Scope analysis ([ScopeAnalyzer]) builds scopes | |
| /// and resolves everything found by scope lookup, and type analysis | |
| /// ([TypeAnalyzer]) computes static types, performs flow analysis, and | |
| /// resolves everything that depends on types: members, operators, and | |
| /// invocations. | |
| class AstResolver { | |
| final LibraryFragmentImpl _libraryFragment; | |
| final InterfaceElementImpl? _enclosingClassElement; | |
| final ExecutableElementImpl? _enclosingExecutableElement; | |
| final TestingData? _testingData; | |
| final ScopeAnalyzer _scopeAnalyzer; | |
| final FlowAnalysisHelper _flowAnalysis; | |
| final TypeAnalyzer _typeAnalyzer; | |
| /// Creates a resolver for a unit of a library being analyzed. | |
| factory forLibraryAnalysis({ | |
| required InheritanceManager3 inheritance, | |
| required LibraryFragmentImpl libraryFragment, | |
| required AnalysisOptions analysisOptions, | |
| required DiagnosticListener diagnosticListener, | |
| required DocImportScope? docImportScope, | |
| required LibraryResolutionContext libraryResolutionContext, | |
| required TypeSystemOperations typeSystemOperations, | |
| required TestingData? testingData, | |
| }) { | |
| return AstResolver._create( | |
| inheritance: inheritance, | |
| libraryFragment: libraryFragment, | |
| nameScope: libraryFragment.scope, | |
| analysisOptions: analysisOptions, | |
| diagnosticListener: diagnosticListener, | |
| docImportScope: docImportScope, | |
| libraryResolutionContext: libraryResolutionContext, | |
| typeSystemOperations: typeSystemOperations, | |
| testingData: testingData, | |
| enableFlowAnalysisLog: true, | |
| enclosingClassElement: null, | |
| enclosingExecutableElement: null, | |
| ); | |
| } | |
| /// Creates a resolver for AST nodes that the summary linker resolves. | |
| /// | |
| /// Diagnostics are not reported. | |
| factory forLinking({ | |
| required InheritanceManager3 inheritance, | |
| required LibraryFragmentImpl libraryFragment, | |
| required Scope nameScope, | |
| required AnalysisOptions analysisOptions, | |
| required InterfaceElementImpl? enclosingClassElement, | |
| required ExecutableElementImpl? enclosingExecutableElement, | |
| }) { | |
| return AstResolver._create( | |
| inheritance: inheritance, | |
| libraryFragment: libraryFragment, | |
| nameScope: nameScope, | |
| analysisOptions: analysisOptions, | |
| diagnosticListener: DiagnosticListener.nullListener, | |
| docImportScope: null, | |
| libraryResolutionContext: LibraryResolutionContext(), | |
| typeSystemOperations: TypeSystemOperations( | |
| libraryFragment.library.typeSystem, | |
| strictCasts: analysisOptions.strictCasts, | |
| ), | |
| testingData: null, | |
| enableFlowAnalysisLog: false, | |
| enclosingClassElement: enclosingClassElement, | |
| enclosingExecutableElement: enclosingExecutableElement, | |
| ); | |
| } | |
| new _({ | |
| required this._libraryFragment, | |
| required this._enclosingClassElement, | |
| required this._enclosingExecutableElement, | |
| required this._testingData, | |
| required this._scopeAnalyzer, | |
| required this._flowAnalysis, | |
| required this._typeAnalyzer, | |
| }); | |
| factory _create({ | |
| required InheritanceManager3 inheritance, | |
| required LibraryFragmentImpl libraryFragment, | |
| required Scope nameScope, | |
| required AnalysisOptions analysisOptions, | |
| required DiagnosticListener diagnosticListener, | |
| required DocImportScope? docImportScope, | |
| required LibraryResolutionContext libraryResolutionContext, | |
| required TypeSystemOperations typeSystemOperations, | |
| required TestingData? testingData, | |
| required bool enableFlowAnalysisLog, | |
| required InterfaceElementImpl? enclosingClassElement, | |
| required ExecutableElementImpl? enclosingExecutableElement, | |
| }) { | |
| var scopeAnalyzer = ScopeAnalyzer( | |
| libraryFragment: libraryFragment, | |
| nameScope: nameScope, | |
| docImportScope: docImportScope, | |
| diagnosticListener: diagnosticListener, | |
| strictCasts: analysisOptions.strictCasts, | |
| dataForTesting: testingData != null | |
| ? TypeConstraintGenerationDataForTesting() | |
| : null, | |
| ); | |
| var typeAnalyzerOptions = computeTypeAnalyzerOptions( | |
| libraryFragment.library.featureSet, | |
| ); | |
| var flowAnalysis = FlowAnalysisHelper( | |
| testingData != null, | |
| typeSystemOperations: typeSystemOperations, | |
| typeAnalyzerOptions: typeAnalyzerOptions, | |
| enableLog: enableFlowAnalysisLog, | |
| ); | |
| var typeAnalyzer = TypeAnalyzer( | |
| inheritanceManager: inheritance, | |
| libraryFragment: libraryFragment, | |
| libraryResolutionContext: libraryResolutionContext, | |
| diagnosticListener: diagnosticListener, | |
| analysisOptions: analysisOptions, | |
| flowAnalysisHelper: flowAnalysis, | |
| typeAnalyzerOptions: typeAnalyzerOptions, | |
| ); | |
| return AstResolver._( | |
| libraryFragment: libraryFragment, | |
| enclosingClassElement: enclosingClassElement, | |
| enclosingExecutableElement: enclosingExecutableElement, | |
| testingData: testingData, | |
| scopeAnalyzer: scopeAnalyzer, | |
| flowAnalysis: flowAnalysis, | |
| typeAnalyzer: typeAnalyzer, | |
| ); | |
| } | |
| void resolveAnnotation(AnnotationImpl node) { | |
| ElementBindingVisitor(_libraryFragment).bindSubtree(_libraryFragment, node); | |
| _prepareEnclosingDeclarations(); | |
| node.accept2(_scopeAnalyzer); | |
| _flowAnalysis.flowAnalysisRoot_enter( | |
| node, | |
| null, | |
| // Offsets are ignored when doing summary linking. | |
| offset: 0, | |
| ); | |
| node.accept2(_typeAnalyzer); | |
| _typeAnalyzer.checkIdle(); | |
| _flowAnalysis.flowAnalysisRoot_exit(); | |
| } | |
| void resolveConstructorDeclaration(ConstructorDeclaration2Impl node) { | |
| var fragment = node.declaredFragment!; | |
| var bindingVisitor = ElementBindingVisitor(_libraryFragment); | |
| for (var initializer in node.initializers) { | |
| bindingVisitor.bindSubtree(fragment, initializer); | |
| } | |
| if (node.factoryRedirectionTarget case var factoryRedirectionTarget?) { | |
| bindingVisitor.bindSubtree(fragment, factoryRedirectionTarget); | |
| } | |
| // We don't want to visit the whole node because that will try to create an | |
| // element for it; we just want to process its children so that we can | |
| // resolve initializers and/or a redirection. | |
| void accept(AstVisitor2<Object?> visitor) { | |
| node.initializers.accept2(visitor); | |
| node.factoryRedirectionTarget?.accept2(visitor); | |
| } | |
| _prepareEnclosingDeclarations(); | |
| accept(_scopeAnalyzer); | |
| _flowAnalysis.flowAnalysisRoot_enter( | |
| node, | |
| fragment.declaredFormalParameterElements, | |
| visit: accept, | |
| // Offsets are ignored when doing summary linking. | |
| offset: 0, | |
| ); | |
| accept(_typeAnalyzer); | |
| _typeAnalyzer.checkIdle(); | |
| _flowAnalysis.flowAnalysisRoot_exit(); | |
| } | |
| /// Resolves the default value of a formal parameter, and returns it. | |
| /// | |
| /// The returned expression might be not the original `node.value2`, | |
| /// because resolution can replace it. | |
| ExpressionImpl resolveDefaultValue( | |
| FormalParameterDefaultClauseImpl node, { | |
| required TypeImpl contextType, | |
| }) { | |
| return _resolveExpression( | |
| root: node, | |
| readExpression: () => node.value2, | |
| contextType: contextType, | |
| inScopePrimaryConstructorParameters: null, | |
| isThisAccessible: false, | |
| ); | |
| } | |
| /// Resolves [node] of [unit] for completion, and returns whether it did. | |
| /// | |
| /// Scope analysis covers the whole [unit], but type analysis covers only | |
| /// [node]. Returns `false`, without type analysis, if [node] cannot be | |
| /// resolved separately from its enclosing declarations. | |
| bool resolveNodeForCompletion(CompilationUnitImpl unit, AstNode node) { | |
| // TODO(scheglov): We don't need to do this for the whole unit. | |
| _runScopeAnalysis(unit); | |
| if (!_typeAnalyzer.prepareForResolving(node)) { | |
| return false; | |
| } | |
| node.accept2(_typeAnalyzer); | |
| _typeAnalyzer.checkIdle(); | |
| _recordTypeAnalysisTestingData(); | |
| return true; | |
| } | |
| void resolvePrimaryConstructor( | |
| PrimaryConstructorDeclarationImpl node, | |
| PrimaryConstructorBody2Impl body, | |
| ) { | |
| var fragment = node.declaredFragment!; | |
| void accept(AstVisitor2<Object?> visitor) { | |
| body.initializers.accept2(visitor); | |
| } | |
| var bindingVisitor = ElementBindingVisitor(_libraryFragment); | |
| for (var initializer in body.initializers) { | |
| bindingVisitor.bindSubtree(fragment, initializer); | |
| } | |
| _prepareEnclosingDeclarations(); | |
| accept(_scopeAnalyzer); | |
| _flowAnalysis.flowAnalysisRoot_enter( | |
| body, | |
| fragment.declaredFormalParameterElements, | |
| visit: accept, | |
| // Offsets are ignored when doing summary linking. | |
| offset: 0, | |
| ); | |
| accept(_typeAnalyzer); | |
| _typeAnalyzer.checkIdle(); | |
| _flowAnalysis.flowAnalysisRoot_exit(); | |
| } | |
| /// Resolves [unit], the unit of the library fragment. | |
| /// | |
| /// Unlike resolving subtrees, this does not enter a flow analysis root, | |
| /// because type analysis enters roots itself while walking the unit. | |
| void resolveUnit(CompilationUnitImpl unit) { | |
| _runScopeAnalysis(unit); | |
| unit.accept2(_typeAnalyzer); | |
| _recordTypeAnalysisTestingData(); | |
| } | |
| void _prepareEnclosingDeclarations() { | |
| _scopeAnalyzer.prepareEnclosingDeclarations( | |
| enclosingClassElement: _enclosingClassElement, | |
| ); | |
| _typeAnalyzer.prepareEnclosingDeclarations( | |
| enclosingInstanceElement: _enclosingClassElement, | |
| enclosingExecutableElement: _enclosingExecutableElement, | |
| ); | |
| } | |
| /// Records the testing data of type analysis, after it finished. | |
| /// | |
| /// Type constraints are merged into the data already recorded for scope | |
| /// analysis by copying, so recording them earlier would lose them. | |
| void _recordTypeAnalysisTestingData() { | |
| if (_testingData case var testingData?) { | |
| var uri = _libraryFragment.source.uri; | |
| testingData.recordFlowAnalysisDataForTesting( | |
| uri, | |
| _flowAnalysis.dataForTesting!, | |
| ); | |
| testingData.recordTypeConstraintGenerationDataForTesting( | |
| uri, | |
| _typeAnalyzer.inferenceHelper.dataForTesting!, | |
| ); | |
| } | |
| } | |
| /// Resolves the expression that [readExpression] reads from [root]. | |
| /// | |
| /// Both scope analysis and type analysis can replace the expression in its | |
| /// parent, so it is read again after scope analysis, and the final | |
| /// expression is returned. | |
| ExpressionImpl _resolveExpression({ | |
| required FlowAnalysisRootImpl root, | |
| required ExpressionImpl Function() readExpression, | |
| required TypeImpl contextType, | |
| required List<FormalParameterElementImpl>? | |
| inScopePrimaryConstructorParameters, | |
| required bool isThisAccessible, | |
| }) { | |
| var expression = readExpression(); | |
| var bindingVisitor = ElementBindingVisitor(_libraryFragment); | |
| bindingVisitor.bindSubtree(_libraryFragment, expression); | |
| _prepareEnclosingDeclarations(); | |
| expression.accept2(_scopeAnalyzer); | |
| // Scope analysis can replace the expression. | |
| expression = readExpression(); | |
| _flowAnalysis.flowAnalysisRoot_enter( | |
| root, | |
| inScopePrimaryConstructorParameters, | |
| // Offsets are ignored when doing summary linking. | |
| offset: 0, | |
| ); | |
| if (isThisAccessible) { | |
| _typeAnalyzer.flow.thisBinding_begin( | |
| null, | |
| thisType: SharedTypeView( | |
| _typeAnalyzer.thisType ?? InvalidTypeImpl.instance, | |
| ), | |
| ); | |
| } | |
| _typeAnalyzer.withThisAccessibility( | |
| isThisAccessible, | |
| () => _typeAnalyzer.analyzeExpression( | |
| expression, | |
| SharedTypeSchemaView(contextType), | |
| ), | |
| ); | |
| // Type analysis can replace it too, and keeps the slot in sync. | |
| var result = _typeAnalyzer.popRewrite()!; | |
| assert(identical(result, readExpression())); | |
| _typeAnalyzer.checkIdle(); | |
| _flowAnalysis.flowAnalysisRoot_exit(); | |
| return result; | |
| } | |
| /// Binds elements in [unit], and runs scope analysis over it. | |
| void _runScopeAnalysis(CompilationUnitImpl unit) { | |
| unit.accept2(ElementBindingVisitor(_libraryFragment)); | |
| _prepareEnclosingDeclarations(); | |
| unit.accept2(_scopeAnalyzer); | |
| if (_testingData case var testingData?) { | |
| testingData.recordTypeConstraintGenerationDataForTesting( | |
| _libraryFragment.source.uri, | |
| _scopeAnalyzer.dataForTesting!, | |
| ); | |
| } | |
| } | |
| /// Resolves the initializer of the variable [fragment] during summary | |
| /// linking, and returns it. | |
| /// | |
| /// The returned expression might be not the original `node.initializer2`, | |
| /// because resolution can replace it. | |
| /// | |
| /// The enclosing declarations and the accessibility of `this` are derived | |
| /// from [fragment] here, rather than by the callers, so that they cannot | |
| /// disagree, and the initializer of every fragment is resolved in the same | |
| /// context. | |
| static ExpressionImpl resolveVariableInitializerForLinking({ | |
| required InheritanceManager3 inheritance, | |
| required AnalysisOptions analysisOptions, | |
| required PropertyInducingFragmentImpl fragment, | |
| required VariableBindingImpl node, | |
| required TypeImpl contextType, | |
| }) { | |
| var element = fragment.element; | |
| var enclosingInterfaceElement = element.enclosingElement | |
| .tryCast<InterfaceElementImpl>(); | |
| var astResolver = AstResolver.forLinking( | |
| inheritance: inheritance, | |
| libraryFragment: fragment.libraryFragment, | |
| nameScope: node.initializerScope!, | |
| analysisOptions: analysisOptions, | |
| enclosingClassElement: enclosingInterfaceElement, | |
| enclosingExecutableElement: null, | |
| ); | |
| // Initializers of non-late instance fields see the parameters of the | |
| // primary constructor, initializers of late instance fields see `this`. | |
| List<FormalParameterElementImpl>? inScopePrimaryConstructorParameters; | |
| var isThisAccessible = false; | |
| if (element case FieldElementImpl field when field.isInstanceField) { | |
| if (field.isLate) { | |
| isThisAccessible = true; | |
| } else { | |
| inScopePrimaryConstructorParameters = | |
| enclosingInterfaceElement?.primaryConstructor?.formalParameters; | |
| } | |
| } | |
| return astResolver._resolveExpression( | |
| root: node, | |
| readExpression: () => node.initializer2!, | |
| contextType: contextType, | |
| inScopePrimaryConstructorParameters: inScopePrimaryConstructorParameters, | |
| isThisAccessible: isThisAccessible, | |
| ); | |
| } | |
| } |