| // Copyright (c) 2024, 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/shared_inference_log.dart'; | |
| import 'package:_fe_analyzer_shared/src/types/shared_type.dart'; | |
| import 'package:analyzer/dart/ast/ast.dart'; | |
| import 'package:analyzer/src/dart/ast/ast.dart'; | |
| import 'package:analyzer/src/dart/element/type.dart'; | |
| import 'package:analyzer/src/dart/resolver/type_analyzer.dart'; | |
| final bool _assertionsEnabled = () { | |
| bool enabled = false; | |
| assert(enabled = true); | |
| return enabled; | |
| }(); | |
| /// Expando used by [_InferenceLogWriterImpl.setExpressionVisitCodePath] to | |
| /// record the code path that's being used by the [TypeAnalyzer] to visit | |
| /// a subexpression. | |
| final _expressionVisitCodePaths = Expando<ExpressionVisitCodePath>(); | |
| /// The [InferenceLogWriter] currently being used by the analyzer, if inference | |
| /// logging is active, otherwise `null`. | |
| InferenceLogWriter? _inferenceLogWriter; | |
| /// Expando storing a value `true` for each expression that has been passed to | |
| /// [_InferenceLogWriterImpl.enterExpression]. | |
| /// | |
| /// This is used by [_InferenceLogWriterImpl.assertExpressionWasRecorded] to | |
| /// verify that [_InferenceLogWriterImpl.enterExpression] was called when it | |
| /// should be. | |
| final _recordedExpressions = Expando<bool>(); | |
| /// Returns the [InferenceLogWriter] currently being used by the analyzer, if | |
| /// inference logging is active, otherwise `null`. | |
| InferenceLogWriter? get inferenceLogWriter => _inferenceLogWriter; | |
| /// Starts up inference logging if appropriate. | |
| /// | |
| /// Inference logging will be started up if either of the following conditions | |
| /// are met: | |
| /// - The [dump] parameter is `true` (in which case the log will immediately | |
| /// start dumping events to standard output) | |
| /// - Assertions are enabled. | |
| void conditionallyStartInferenceLogging({bool dump = false}) { | |
| assert(_inferenceLogWriter == null); | |
| if (_assertionsEnabled || dump) { | |
| var inferenceLogWriter = _inferenceLogWriter = _InferenceLogWriterImpl(); | |
| if (dump) { | |
| inferenceLogWriter.dump(); | |
| } | |
| } | |
| } | |
| /// Stops inference logging if it's been started. | |
| void stopInferenceLogging() { | |
| _inferenceLogWriter = null; | |
| } | |
| /// Enum of all the possible code paths that can be used by the | |
| /// [TypeAnalyzer] to visit expressions when inside a flow analysis root | |
| /// (see [InferenceLogWriter.enterFlowAnalysisRoot]). | |
| enum ExpressionVisitCodePath { | |
| /// The expression is being visited via | |
| /// [TypeAnalyzer.analyzeExpression]. | |
| analyzeExpression, | |
| /// The expression is the identifier in a "for each" loop, so it is not a true | |
| /// expression, and it is being visited directly using [Expression.accept2]. | |
| forEachIdentifier, | |
| } | |
| /// The [SharedInferenceLogWriter] interface, augmented with analyzer-specific | |
| /// functionality. | |
| abstract interface class InferenceLogWriter | |
| implements SharedInferenceLogWriter { | |
| /// Checks that [enterExpression] was properly called for [expression]. | |
| /// | |
| /// This is called from [TypeAnalyzer.dispatchExpression], to verify | |
| /// that each expression's visit method property calls [enterExpression]. | |
| void assertExpressionWasRecorded(Expression expression); | |
| /// Called when type inference enters a *flow analysis root*, i.e. a region of | |
| /// code that is analyzed by a single invocation of flow analysis. | |
| /// | |
| /// Examples include the body of a top level function or method, the | |
| /// initializer of a top level variable or field, and the initializers and | |
| /// body of a constructor. See `FlowAnalysisRootImpl` for the full list. | |
| void enterFlowAnalysisRoot(AstNode node); | |
| /// Called when type inference leaves the flow analysis root that was most | |
| /// recently entered using [enterFlowAnalysisRoot]. | |
| void exitFlowAnalysisRoot(); | |
| /// Records [source] as the code path that the [TypeAnalyzer] is about | |
| /// to use to visit the expression [node]. | |
| /// | |
| /// An assertion in [enterExpression] verifies that when inside a flow | |
| /// analysis root (see [enterFlowAnalysisRoot]), every call to | |
| /// [enterExpression] is preceded by exactly one call to | |
| /// [setExpressionVisitCodePath]. This ensures that the resolution process | |
| /// doesn't ever try to resolve a subexpression more than once. It also | |
| /// ensures that every code path that resolves subexpressions inside flow | |
| /// analysis roots calls this method, making it easy to statically locate | |
| /// these code paths. | |
| void setExpressionVisitCodePath( | |
| Expression node, | |
| ExpressionVisitCodePath source, | |
| ); | |
| } | |
| /// The [SharedInferenceLogWriterImpl] implementation, augmented with | |
| /// analyzer-specific functionality. | |
| final class _InferenceLogWriterImpl extends SharedInferenceLogWriterImpl | |
| implements InferenceLogWriter { | |
| /// Whether type inference is currently inside a *flow analysis root* (see | |
| /// [InferenceLogWriter.enterFlowAnalysisRoot]). | |
| /// | |
| /// When this value is `true`, flow analysis is active, and expressions must | |
| /// be visited using [TypeAnalyzer.analyzeExpression]. | |
| bool _inFlowAnalysisRoot = false; | |
| @override | |
| void assertExpressionWasRecorded(Object expression) { | |
| if (_recordedExpressions[expression] ?? false) return; | |
| fail('failed to record ${describe(expression)}'); | |
| } | |
| @override | |
| void enterAnnotation(covariant Annotation node) { | |
| // TypeAnalyzer.visitAnnotation is sometimes called from | |
| // AstResolver.resolveAnnotation during summary linking. When this happens, | |
| // the state will be a "top" state even though _traceableParent suggests | |
| // that we should be in some other state. So to avoid a bogus exception, if | |
| // the state is a "top" state, don't bother calling `checkCall`. | |
| if (state.kind != StateKind.top) { | |
| checkCall( | |
| method: 'enterAnnotation', | |
| arguments: [node], | |
| expectedNode: traceableAncestor(node), | |
| ); | |
| } | |
| super.enterAnnotation(node); | |
| } | |
| @override | |
| void enterElement(covariant CollectionElement node) { | |
| checkCall( | |
| method: 'enterElement', | |
| arguments: [node], | |
| expectedNode: traceableAncestor(node), | |
| ); | |
| super.enterElement(node); | |
| } | |
| @override | |
| void enterExpression(covariant Expression node, TypeImpl contextType) { | |
| assert( | |
| !_inFlowAnalysisRoot || _expressionVisitCodePaths[node] != null, | |
| 'When in a flow analysis root, setExpressionVisitCodePath should be ' | |
| 'called prior to enterExpression. Not called for $node.', | |
| ); | |
| checkCall( | |
| method: 'enterExpression', | |
| arguments: [node, contextType], | |
| expectedNode: traceableAncestor(node), | |
| ); | |
| super.enterExpression(node, contextType); | |
| _recordedExpressions[node] = true; | |
| } | |
| @override | |
| void enterExtensionOverride( | |
| covariant ExtensionOverride2 node, | |
| TypeImpl contextType, | |
| ) { | |
| checkCall( | |
| method: 'enterExtensionOverride', | |
| arguments: [node, contextType], | |
| expectedNode: traceableAncestor(node), | |
| ); | |
| super.enterExtensionOverride(node, contextType); | |
| _recordedExpressions[node] = true; | |
| } | |
| @override | |
| void enterFlowAnalysisRoot(AstNode node) { | |
| assert(!_inFlowAnalysisRoot, 'Already in a flow analysis root'); | |
| _inFlowAnalysisRoot = true; | |
| } | |
| @override | |
| void enterLValue(covariant Expression node) { | |
| checkCall( | |
| method: 'enterLValue', | |
| arguments: [node], | |
| expectedNode: traceableAncestor(node), | |
| ); | |
| super.enterLValue(node); | |
| } | |
| @override | |
| void enterPattern(covariant DartPattern node) { | |
| checkCall( | |
| method: 'enterPattern', | |
| arguments: [node], | |
| expectedNode: traceableAncestor(node), | |
| ); | |
| super.enterPattern(node); | |
| } | |
| @override | |
| void enterStatement(covariant Statement node) { | |
| checkCall( | |
| method: 'enterStatement', | |
| arguments: [node], | |
| expectedNode: traceableAncestor(node), | |
| ); | |
| super.enterStatement(node); | |
| } | |
| @override | |
| void exitFlowAnalysisRoot() { | |
| assert(_inFlowAnalysisRoot, 'Not in a flow analysis root'); | |
| _inFlowAnalysisRoot = false; | |
| } | |
| @override | |
| void recordExpressionRewrite({ | |
| Object? oldExpression, | |
| required Object newExpression, | |
| }) { | |
| if (oldExpression != null) { | |
| assertExpressionWasRecorded(oldExpression); | |
| } | |
| _recordedExpressions[newExpression] = true; | |
| super.recordExpressionRewrite( | |
| oldExpression: oldExpression, | |
| newExpression: newExpression, | |
| ); | |
| } | |
| @override | |
| void recordNullShortedType(Object expression, SharedType type) { | |
| assertExpressionWasRecorded(expression); | |
| super.recordNullShortedType(expression, type); | |
| } | |
| @override | |
| void setExpressionVisitCodePath( | |
| Expression node, | |
| ExpressionVisitCodePath source, | |
| ) { | |
| assert( | |
| _expressionVisitCodePaths[node] == null, | |
| 'An expression visit source was already set for $node', | |
| ); | |
| _expressionVisitCodePaths[node] = source; | |
| } | |
| /// Returns the nearest ancestor of [node] for which a call to `enter...` | |
| /// should have been made. | |
| /// | |
| /// This is used to verify proper nesting of `enter...` method calls. | |
| AstNode? traceableAncestor(covariant AstNode node) { | |
| for (var parent = node.parent2; ; parent = parent.parent2) { | |
| switch (parent) { | |
| case null: | |
| return null; | |
| // These nodes group the selector of a call that is still awaiting | |
| // member lookup; they are not independently inferred expressions. | |
| case ParsedNameAccess(): | |
| case ParsedTypeArguments(): | |
| break; | |
| case Annotation(): | |
| case ExtensionOverride2(): | |
| case CollectionElement(): | |
| case DartPattern(): | |
| case Statement(): | |
| return parent; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| } |