blob: 112f2e7745f8b0360d17c9819c3a8dd7ee7b3bf8 [file] [log] [blame]
// Copyright (c) 2014, 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 'dart:collection';
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/scope.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/error/error.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/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/member.dart'
show ConstructorMember, Member;
import 'package:analyzer/src/dart/element/nullability_eliminator.dart';
import 'package:analyzer/src/dart/element/scope.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/dart/resolver/annotation_resolver.dart';
import 'package:analyzer/src/dart/resolver/assignment_expression_resolver.dart';
import 'package:analyzer/src/dart/resolver/binary_expression_resolver.dart';
import 'package:analyzer/src/dart/resolver/body_inference_context.dart';
import 'package:analyzer/src/dart/resolver/extension_member_resolver.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/dart/resolver/for_resolver.dart';
import 'package:analyzer/src/dart/resolver/function_expression_invocation_resolver.dart';
import 'package:analyzer/src/dart/resolver/function_expression_resolver.dart';
import 'package:analyzer/src/dart/resolver/function_reference_resolver.dart';
import 'package:analyzer/src/dart/resolver/invocation_inference_helper.dart';
import 'package:analyzer/src/dart/resolver/lexical_lookup.dart';
import 'package:analyzer/src/dart/resolver/method_invocation_resolver.dart';
import 'package:analyzer/src/dart/resolver/postfix_expression_resolver.dart';
import 'package:analyzer/src/dart/resolver/prefix_expression_resolver.dart';
import 'package:analyzer/src/dart/resolver/prefixed_identifier_resolver.dart';
import 'package:analyzer/src/dart/resolver/property_element_resolver.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
import 'package:analyzer/src/dart/resolver/simple_identifier_resolver.dart';
import 'package:analyzer/src/dart/resolver/type_property_resolver.dart';
import 'package:analyzer/src/dart/resolver/typed_literal_resolver.dart';
import 'package:analyzer/src/dart/resolver/variable_declaration_resolver.dart';
import 'package:analyzer/src/dart/resolver/yield_statement_resolver.dart';
import 'package:analyzer/src/diagnostic/diagnostic.dart';
import 'package:analyzer/src/error/bool_expression_verifier.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/error/dead_code_verifier.dart';
import 'package:analyzer/src/error/nullable_dereference_verifier.dart';
import 'package:analyzer/src/generated/element_resolver.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/error_detection_helpers.dart';
import 'package:analyzer/src/generated/migratable_ast_info_provider.dart';
import 'package:analyzer/src/generated/migration.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/static_type_analyzer.dart';
import 'package:analyzer/src/generated/this_access_tracker.dart';
import 'package:analyzer/src/generated/variable_type_provider.dart';
import 'package:analyzer/src/util/ast_data_extractor.dart';
import 'package:meta/meta.dart';
/// A function which returns [NonPromotionReason]s that various types are not
/// promoted.
typedef WhyNotPromotedGetter = Map<DartType, NonPromotionReason> Function();
/// Maintains and manages contextual type information used for
/// inferring types.
class InferenceContext {
// TODO(leafp): Consider replacing these node properties with a
// hash table help in an instance of this class.
static const String _typeProperty =
'analyzer.src.generated.InferenceContext.contextType';
final ResolverVisitor _resolver;
/// The type system in use.
final TypeSystemImpl _typeSystem;
/// The stack of contexts for nested function bodies.
final List<BodyInferenceContext> _bodyContexts = [];
InferenceContext._(ResolverVisitor resolver)
: _resolver = resolver,
_typeSystem = resolver.typeSystem;
BodyInferenceContext? get bodyContext {
if (_bodyContexts.isNotEmpty) {
return _bodyContexts.last;
} else {
return null;
}
}
void popFunctionBodyContext(FunctionBody node) {
var context = _bodyContexts.removeLast();
var flow = _resolver.flowAnalysis?.flow;
var resultType = context.computeInferredReturnType(
endOfBlockIsReachable: flow == null || flow.isReachable,
);
setType(node, resultType);
}
void pushFunctionBodyContext(FunctionBody node) {
var imposedType = getContext(node);
_bodyContexts.add(
BodyInferenceContext(
typeSystem: _typeSystem,
node: node,
imposedType: imposedType,
),
);
}
/// Clear the type information associated with [node].
static void clearType(AstNode? node) {
node?.setProperty(_typeProperty, null);
}
/// Look for contextual type information attached to [node], and returns
/// the type if found.
///
/// The returned type may be partially or completely unknown, denoted with an
/// unknown type `_`, for example `List<_>` or `(_, int) -> void`.
/// You can use [TypeSystemImpl.upperBoundForType] or
/// [TypeSystemImpl.lowerBoundForType] if you would prefer a known type
/// that represents the bound of the context type.
static DartType? getContext(AstNode? node) =>
node?.getProperty(_typeProperty);
/// Attach contextual type information [type] to [node] for use during
/// inference.
static void setType(AstNode? node, DartType? type) {
if (type == null || type.isDynamic) {
clearType(node);
} else {
node?.setProperty(_typeProperty, type);
}
}
/// Attach contextual type information [type] to [node] for use during
/// inference.
static void setTypeFromNode(AstNode innerNode, AstNode outerNode) {
setType(innerNode, getContext(outerNode));
}
}
/// Instances of the class `ResolverVisitor` are used to resolve the nodes
/// within a single compilation unit.
class ResolverVisitor extends ScopedVisitor with ErrorDetectionHelpers {
/// The manager for the inheritance mappings.
final InheritanceManager3 inheritance;
/// The feature set that is enabled for the current unit.
final FeatureSet _featureSet;
final MigratableAstInfoProvider _migratableAstInfoProvider;
final MigrationResolutionHooks? migrationResolutionHooks;
/// Helper for checking expression that should have the `bool` type.
late final BoolExpressionVerifier boolExpressionVerifier;
/// Helper for checking potentially nullable dereferences.
late final NullableDereferenceVerifier nullableDereferenceVerifier;
/// Helper for extension method resolution.
late final ExtensionMemberResolver extensionResolver;
/// Helper for resolving properties on types.
late final TypePropertyResolver typePropertyResolver;
/// Helper for resolving [ListLiteral] and [SetOrMapLiteral].
late final TypedLiteralResolver _typedLiteralResolver;
late final AssignmentExpressionResolver _assignmentExpressionResolver;
late final BinaryExpressionResolver _binaryExpressionResolver;
late final FunctionExpressionInvocationResolver
_functionExpressionInvocationResolver;
late final FunctionExpressionResolver _functionExpressionResolver;
late final ForResolver _forResolver;
late final PostfixExpressionResolver _postfixExpressionResolver;
late final PrefixedIdentifierResolver _prefixedIdentifierResolver;
late final PrefixExpressionResolver _prefixExpressionResolver;
late final VariableDeclarationResolver _variableDeclarationResolver;
late final YieldStatementResolver _yieldStatementResolver;
late final NullSafetyDeadCodeVerifier nullSafetyDeadCodeVerifier;
late final InvocationInferenceHelper inferenceHelper;
/// The object used to resolve the element associated with the current node.
late final ElementResolver elementResolver;
/// The object used to compute the type associated with the current node.
late final StaticTypeAnalyzer typeAnalyzer;
/// The type system in use during resolution.
@override
final TypeSystemImpl typeSystem;
/// The element representing the function containing the current node, or
/// `null` if the current node is not contained in a function.
ExecutableElement? _enclosingFunction;
/// The helper for tracking if the current location has access to `this`.
final ThisAccessTracker _thisAccessTracker = ThisAccessTracker.unit();
late final InferenceContext inferenceContext;
/// If a class, or mixin, is being resolved, the type of the class.
/// Otherwise `null`.
DartType? _thisType;
final FlowAnalysisHelper? flowAnalysis;
/// A comment before a function should be resolved in the context of the
/// function. But when we incrementally resolve a comment, we don't want to
/// resolve the whole function.
///
/// So, this flag is set to `true`, when just context of the function should
/// be built and the comment resolved.
bool resolveOnlyCommentInFunctionBody = false;
/// The type of the expression of the immediately enclosing [SwitchStatement],
/// or `null` if not in a [SwitchStatement].
DartType? _enclosingSwitchStatementExpressionType;
/// Stack of expressions which we have not yet finished visiting, that should
/// terminate a null-shorting expression.
///
/// The stack contains a `null` sentinel as its first entry so that it is
/// always safe to use `.last` to examine the top of the stack.
final List<Expression?> _unfinishedNullShorts = [null];
/// During resolution we clone annotation ASTs into the element model.
/// But we should not do this during linking element models, moreover
/// currently by doing this we will lose elements for parameters of
/// generic function types.
/// TODO(scheglov) Stop cloning altogether.
bool shouldCloneAnnotations = true;
late final FunctionReferenceResolver _functionReferenceResolver;
/// Initialize a newly created visitor to resolve the nodes in an AST node.
///
/// The [definingLibrary] is the element for the library containing the node
/// being visited. The [source] is the source representing the compilation
/// unit containing the node being visited. The [typeProvider] is the object
/// used to access the types from the core library. The [errorListener] is the
/// error listener that will be informed of any errors that are found during
/// resolution. The [nameScope] is the scope used to resolve identifiers in
/// the node that will first be visited. If `null` or unspecified, a new
/// [LibraryScope] will be created based on [definingLibrary] and
/// [typeProvider].
///
/// TODO(paulberry): make [featureSet] a required parameter (this will be a
/// breaking change).
ResolverVisitor(
InheritanceManager3 inheritanceManager,
LibraryElement definingLibrary,
Source source,
TypeProvider typeProvider,
AnalysisErrorListener errorListener,
{FeatureSet? featureSet,
Scope? nameScope,
FlowAnalysisHelper? flowAnalysisHelper})
: this._(
inheritanceManager,
definingLibrary,
source,
definingLibrary.typeSystem as TypeSystemImpl,
typeProvider,
errorListener,
featureSet ??
definingLibrary.context.analysisOptions.contextFeatures,
nameScope,
flowAnalysisHelper,
const MigratableAstInfoProvider(),
null);
ResolverVisitor._(
this.inheritance,
LibraryElement definingLibrary,
Source source,
this.typeSystem,
TypeProvider typeProvider,
AnalysisErrorListener errorListener,
FeatureSet featureSet,
Scope? nameScope,
this.flowAnalysis,
this._migratableAstInfoProvider,
MigrationResolutionHooks? migrationResolutionHooks)
: _featureSet = featureSet,
migrationResolutionHooks = migrationResolutionHooks,
super(definingLibrary, source, typeProvider as TypeProviderImpl,
errorListener,
nameScope: nameScope) {
var analysisOptions =
definingLibrary.context.analysisOptions as AnalysisOptionsImpl;
nullableDereferenceVerifier = NullableDereferenceVerifier(
typeSystem: typeSystem,
errorReporter: errorReporter,
resolver: this,
);
boolExpressionVerifier = BoolExpressionVerifier(
resolver: this,
errorReporter: errorReporter,
nullableDereferenceVerifier: nullableDereferenceVerifier,
);
_typedLiteralResolver = TypedLiteralResolver(
this, _featureSet, typeSystem, typeProvider,
migratableAstInfoProvider: _migratableAstInfoProvider);
extensionResolver = ExtensionMemberResolver(this);
typePropertyResolver = TypePropertyResolver(this);
inferenceHelper = InvocationInferenceHelper(
resolver: this,
errorReporter: errorReporter,
typeSystem: typeSystem,
migrationResolutionHooks: migrationResolutionHooks,
);
_assignmentExpressionResolver = AssignmentExpressionResolver(
resolver: this,
);
_binaryExpressionResolver = BinaryExpressionResolver(
resolver: this,
);
_functionExpressionInvocationResolver =
FunctionExpressionInvocationResolver(
resolver: this,
);
_functionExpressionResolver = FunctionExpressionResolver(
resolver: this,
migrationResolutionHooks: migrationResolutionHooks,
);
_forResolver = ForResolver(
resolver: this,
);
_postfixExpressionResolver = PostfixExpressionResolver(
resolver: this,
);
_prefixedIdentifierResolver = PrefixedIdentifierResolver(this);
_prefixExpressionResolver = PrefixExpressionResolver(
resolver: this,
);
_variableDeclarationResolver = VariableDeclarationResolver(
resolver: this,
strictInference: analysisOptions.strictInference,
);
_yieldStatementResolver = YieldStatementResolver(
resolver: this,
);
nullSafetyDeadCodeVerifier = NullSafetyDeadCodeVerifier(
typeSystem,
errorReporter,
flowAnalysis,
);
elementResolver = ElementResolver(this,
migratableAstInfoProvider: _migratableAstInfoProvider);
inferenceContext = InferenceContext._(this);
typeAnalyzer = StaticTypeAnalyzer(this, migrationResolutionHooks);
_functionReferenceResolver =
FunctionReferenceResolver(this, _isNonNullableByDefault);
}
/// Return the element representing the function containing the current node,
/// or `null` if the current node is not contained in a function.
///
/// @return the element representing the function containing the current node
ExecutableElement? get enclosingFunction => _enclosingFunction;
/// Return the object providing promoted or declared types of variables.
LocalVariableTypeProvider get localVariableTypeProvider {
if (flowAnalysis != null) {
return flowAnalysis!.localVariableTypeProvider;
} else {
return const NonPromotingLocalVariableTypeProvider();
}
}
NullabilitySuffix get noneOrStarSuffix {
return _isNonNullableByDefault
? NullabilitySuffix.none
: NullabilitySuffix.star;
}
/// If a class, or mixin, is being resolved, the type of the class.
///
/// If an extension is being resolved, the type of `this`, the declared
/// extended type, or promoted.
///
/// Otherwise `null`.
DartType? get thisType {
return _thisType;
}
/// Return `true` if NNBD is enabled for this compilation unit.
bool get _isNonNullableByDefault =>
_featureSet.isEnabled(Feature.non_nullable);
/// Verify that the arguments in the given [argumentList] can be assigned to
/// their corresponding parameters.
///
/// This method corresponds to
/// [BestPracticesVerifier.checkForArgumentTypesNotAssignableInList].
///
/// See [StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE].
void checkForArgumentTypesNotAssignableInList(ArgumentList argumentList,
List<WhyNotPromotedGetter> whyNotPromotedList) {
var arguments = argumentList.arguments;
for (int i = 0; i < arguments.length; i++) {
checkForArgumentTypeNotAssignableForArgument(arguments[i],
whyNotPromoted:
flowAnalysis?.flow == null ? null : whyNotPromotedList[i]);
}
}
void checkForBodyMayCompleteNormally({
required DartType? returnType,
required FunctionBody body,
required AstNode errorNode,
}) {
if (!_isNonNullableByDefault) return;
if (!flowAnalysis!.flow!.isReachable) {
return;
}
if (returnType == null) {
return;
}
if (body is BlockFunctionBody) {
if (body.isGenerator) {
return;
}
if (typeSystem.isPotentiallyNonNullable(returnType)) {
if (errorNode is ConstructorDeclaration) {
errorReporter.reportErrorForName(
CompileTimeErrorCode.BODY_MIGHT_COMPLETE_NORMALLY,
errorNode,
);
} else if (errorNode is BlockFunctionBody) {
errorReporter.reportErrorForToken(
CompileTimeErrorCode.BODY_MIGHT_COMPLETE_NORMALLY,
errorNode.block.leftBracket,
);
} else {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.BODY_MIGHT_COMPLETE_NORMALLY,
errorNode,
);
}
}
}
}
void checkReadOfNotAssignedLocalVariable(
SimpleIdentifier node,
Element? element,
) {
if (flowAnalysis?.flow == null) {
return;
}
if (!node.inGetterContext()) {
return;
}
if (element is VariableElement) {
var assigned = flowAnalysis!
.isDefinitelyAssigned(node, element as PromotableElement);
var unassigned = flowAnalysis!.isDefinitelyUnassigned(node, element);
if (element.isLate) {
if (unassigned) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.DEFINITELY_UNASSIGNED_LATE_LOCAL_VARIABLE,
node,
[node.name],
);
}
return;
}
if (!assigned) {
if (element.isFinal) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.READ_POTENTIALLY_UNASSIGNED_FINAL,
node,
[node.name],
);
return;
}
if (typeSystem.isPotentiallyNonNullable(element.type)) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode
.NOT_ASSIGNED_POTENTIALLY_NON_NULLABLE_LOCAL_VARIABLE,
node,
[node.name],
);
return;
}
}
}
}
void checkUnreachableNode(AstNode node) {
nullSafetyDeadCodeVerifier.visitNode(node);
}
@override
List<DiagnosticMessage> computeWhyNotPromotedMessages(
SyntacticEntity errorEntity,
Map<DartType, NonPromotionReason>? whyNotPromoted) {
List<DiagnosticMessage> messages = [];
if (whyNotPromoted != null) {
for (var entry in whyNotPromoted.entries) {
var whyNotPromotedVisitor = _WhyNotPromotedVisitor(
source, errorEntity, flowAnalysis!.dataForTesting);
if (typeSystem.isPotentiallyNullable(entry.key)) continue;
var message = entry.value.accept(whyNotPromotedVisitor);
if (message != null) {
if (flowAnalysis!.dataForTesting != null) {
var nonPromotionReasonText = entry.value.shortName;
var args = <String>[];
if (whyNotPromotedVisitor.propertyReference != null) {
var id =
computeMemberId(whyNotPromotedVisitor.propertyReference!);
args.add('target: $id');
}
if (whyNotPromotedVisitor.propertyType != null) {
args.add('type: ${whyNotPromotedVisitor.propertyType}');
}
if (args.isNotEmpty) {
nonPromotionReasonText += '(${args.join(', ')})';
}
flowAnalysis!.dataForTesting!.nonPromotionReasons[errorEntity] =
nonPromotionReasonText;
}
messages = [message];
}
break;
}
}
return messages;
}
/// Return the static element associated with the given expression whose type
/// can be overridden, or `null` if there is no element whose type can be
/// overridden.
///
/// @param expression the expression with which the element is associated
/// @return the element associated with the given expression
VariableElement? getOverridableStaticElement(Expression expression) {
Element? element;
if (expression is SimpleIdentifier) {
element = expression.staticElement;
} else if (expression is PrefixedIdentifier) {
element = expression.staticElement;
} else if (expression is PropertyAccess) {
element = expression.propertyName.staticElement;
}
if (element is VariableElement) {
return element;
}
return null;
}
/// Return the result of lexical lookup for the [node], not `null`.
///
/// Implements `16.35 Lexical Lookup` from the language specification.
LexicalLookupResult lexicalLookup({
required SimpleIdentifier node,
required bool setter,
}) {
return LexicalLookup(this).perform(node: node, setter: setter);
}
/// If we reached a null-shorting termination, and the [node] has null
/// shorting, make the type of the [node] nullable.
void nullShortingTermination(ExpressionImpl node,
{bool discardType = false}) {
if (!_isNonNullableByDefault) return;
if (identical(_unfinishedNullShorts.last, node)) {
do {
_unfinishedNullShorts.removeLast();
flowAnalysis!.flow!.nullAwareAccess_end();
} while (identical(_unfinishedNullShorts.last, node));
if (node is! CascadeExpression && !discardType) {
node.staticType = typeSystem.makeNullable(node.staticType as TypeImpl);
}
}
}
/// If it is appropriate to do so, override the current type of the static
/// element associated with the given expression with the given type.
/// Generally speaking, it is appropriate if the given type is more specific
/// than the current type.
///
/// @param expression the expression used to access the static element whose
/// types might be overridden
/// @param potentialType the potential type of the elements
/// @param allowPrecisionLoss see @{code overrideVariable} docs
void overrideExpression(Expression expression, DartType potentialType,
bool allowPrecisionLoss, bool setExpressionType) {
// TODO(brianwilkerson) Remove this method.
}
/// Set information about enclosing declarations.
void prepareEnclosingDeclarations({
ClassElement? enclosingClassElement,
ExecutableElement? enclosingExecutableElement,
}) {
enclosingClass = enclosingClassElement;
_thisType = enclosingClass?.thisType;
_enclosingFunction = enclosingExecutableElement;
}
/// We are going to resolve [node], without visiting its parent.
/// Do necessary preparations - set enclosing elements, scopes, etc.
/// This [ResolverVisitor] instance is fresh, just created.
///
/// Return `true` if we were able to do this, or `false` if it is not
/// possible to resolve only [node].
bool prepareForResolving(AstNode node) {
var parent = node.parent;
if (parent is CompilationUnit) {
return node is ClassDeclaration ||
node is ExtensionDeclaration ||
node is FunctionDeclaration;
}
void forClassElement(ClassElement parentElement) {
enclosingClass = parentElement;
nameScope = ClassScope(
TypeParameterScope(
nameScope,
parentElement.typeParameters,
),
parentElement,
);
_thisType = parentElement.thisType;
}
if (parent is ClassDeclaration) {
forClassElement(parent.declaredElement!);
return true;
}
if (parent is MixinDeclaration) {
forClassElement(parent.declaredElement!);
return true;
}
return false;
}
/// Resolve LHS [node] of an assignment, an explicit [AssignmentExpression],
/// or implicit [PrefixExpression] or [PostfixExpression].
PropertyElementResolverResult resolveForWrite({
required AstNode node,
required bool hasRead,
}) {
if (node is IndexExpression) {
node.target?.accept(this);
startNullAwareIndexExpression(node);
var resolver = PropertyElementResolver(this);
var result = resolver.resolveIndexExpression(
node: node,
hasRead: hasRead,
hasWrite: true,
);
InferenceContext.setType(node.index, result.indexContextType);
node.index.accept(this);
var whyNotPromoted = flowAnalysis?.flow?.whyNotPromoted(node.index);
checkIndexExpressionIndex(
node.index,
readElement: result.readElement as ExecutableElement?,
writeElement: result.writeElement as ExecutableElement?,
whyNotPromoted: whyNotPromoted,
);
return result;
} else if (node is PrefixedIdentifier) {
node.prefix.accept(this);
var resolver = PropertyElementResolver(this);
return resolver.resolvePrefixedIdentifier(
node: node,
hasRead: hasRead,
hasWrite: true,
);
} else if (node is PropertyAccess) {
node.target?.accept(this);
startNullAwarePropertyAccess(node);
var resolver = PropertyElementResolver(this);
return resolver.resolvePropertyAccess(
node: node,
hasRead: hasRead,
hasWrite: true,
);
} else if (node is SimpleIdentifier) {
var resolver = PropertyElementResolver(this);
var result = resolver.resolveSimpleIdentifier(
node: node,
hasRead: hasRead,
hasWrite: true,
);
if (hasRead && result.readElementRequested == null) {
errorReporter.reportErrorForNode(
CompileTimeErrorCode.UNDEFINED_IDENTIFIER,
node,
[node.name],
);
}
return result;
} else {
node.accept(this);
return PropertyElementResolverResult();
}
}
/// Visit the given [comment] if it is not `null`.
void safelyVisitComment(Comment? comment) {
if (comment != null) {
super.visitComment(comment);
}
}
void setReadElement(Expression node, Element? element) {
DartType readType = DynamicTypeImpl.instance;
if (node is IndexExpression) {
if (element is MethodElement) {
readType = element.returnType;
}
} else if (node is PrefixedIdentifier ||
node is PropertyAccess ||
node is SimpleIdentifier) {
if (element is PropertyAccessorElement && element.isGetter) {
readType = element.returnType;
} else if (element is VariableElement) {
readType = localVariableTypeProvider.getType(node as SimpleIdentifier,
isRead: true);
}
}
var parent = node.parent;
if (parent is AssignmentExpressionImpl && parent.leftHandSide == node) {
parent.readElement = element;
parent.readType = readType;
} else if (parent is PostfixExpressionImpl &&
parent.operator.type.isIncrementOperator) {
parent.readElement = element;
parent.readType = readType;
} else if (parent is PrefixExpressionImpl &&
parent.operator.type.isIncrementOperator) {
parent.readElement = element;
parent.readType = readType;
}
}
@visibleForTesting
void setThisInterfaceType(InterfaceType thisType) {
_thisType = thisType;
}
void setWriteElement(Expression node, Element? element) {
DartType writeType = DynamicTypeImpl.instance;
if (node is IndexExpression) {
if (element is MethodElement) {
var parameters = element.parameters;
if (parameters.length == 2) {
writeType = parameters[1].type;
}
}
} else if (node is PrefixedIdentifier ||
node is PropertyAccess ||
node is SimpleIdentifier) {
if (element is PropertyAccessorElement && element.isSetter) {
if (element.isSynthetic) {
writeType = element.variable.type;
} else {
var parameters = element.parameters;
if (parameters.length == 1) {
writeType = parameters[0].type;
}
}
} else if (element is VariableElement) {
writeType = element.type;
}
}
var parent = node.parent;
if (parent is AssignmentExpressionImpl && parent.leftHandSide == node) {
parent.writeElement = element;
parent.writeType = writeType;
} else if (parent is PostfixExpressionImpl &&
parent.operator.type.isIncrementOperator) {
parent.writeElement = element;
parent.writeType = writeType;
} else if (parent is PrefixExpressionImpl &&
parent.operator.type.isIncrementOperator) {
parent.writeElement = element;
parent.writeType = writeType;
}
}
void startNullAwareIndexExpression(IndexExpression node) {
if (_migratableAstInfoProvider.isIndexExpressionNullAware(node)) {
var flow = flowAnalysis?.flow;
if (flow != null) {
flow.nullAwareAccess_rightBegin(node.target,
node.realTarget.staticType ?? typeProvider.dynamicType);
_unfinishedNullShorts.add(node.nullShortingTermination);
}
}
}
void startNullAwarePropertyAccess(PropertyAccess node) {
if (_migratableAstInfoProvider.isPropertyAccessNullAware(node)) {
var flow = flowAnalysis?.flow;
if (flow != null) {
var target = node.target;
if (target is SimpleIdentifier &&
target.staticElement is ClassElement) {
// `?.` to access static methods is equivalent to `.`, so do nothing.
} else {
flow.nullAwareAccess_rightBegin(
target, node.realTarget.staticType ?? typeProvider.dynamicType);
_unfinishedNullShorts.add(node.nullShortingTermination);
}
}
}
}
/// If in a legacy library, return the legacy view on the [element].
/// Otherwise, return the original element.
T toLegacyElement<T extends Element?>(T element) {
if (_isNonNullableByDefault) return element;
if (element == null) return element;
return Member.legacy(element) as T;
}
/// If in a legacy library, return the legacy version of the [type].
/// Otherwise, return the original type.
DartType toLegacyTypeIfOptOut(DartType type) {
if (_isNonNullableByDefault) return type;
return NullabilityEliminator.perform(typeProvider, type);
}
@override
void visitAnnotation(covariant AnnotationImpl node) {
var whyNotPromotedList = <Map<DartType, NonPromotionReason> Function()>[];
AnnotationResolver(this).resolve(node, whyNotPromotedList);
var arguments = node.arguments;
if (arguments != null) {
checkForArgumentTypesNotAssignableInList(arguments, whyNotPromotedList);
}
}
@override
void visitArgumentList(ArgumentList node,
{bool isIdentical = false,
List<WhyNotPromotedGetter>? whyNotPromotedList}) {
whyNotPromotedList ??= [];
var callerType = InferenceContext.getContext(node);
NodeList<Expression> arguments = node.arguments;
if (callerType is FunctionType) {
Map<String, DartType> namedParameterTypes =
callerType.namedParameterTypes;
List<DartType> normalParameterTypes = callerType.normalParameterTypes;
List<DartType> optionalParameterTypes = callerType.optionalParameterTypes;
int normalCount = normalParameterTypes.length;
int optionalCount = optionalParameterTypes.length;
Iterable<Expression> positional =
arguments.takeWhile((l) => l is! NamedExpression);
Iterable<Expression> required = positional.take(normalCount);
Iterable<Expression> optional =
positional.skip(normalCount).take(optionalCount);
Iterable<Expression> named =
arguments.skipWhile((l) => l is! NamedExpression);
var parent = node.parent;
DartType? targetType;
Element? methodElement;
DartType? invocationContext;
if (parent is MethodInvocation) {
targetType = parent.realTarget?.staticType;
methodElement = parent.methodName.staticElement;
invocationContext = InferenceContext.getContext(parent);
}
//TODO(leafp): Consider using the parameter elements here instead.
//TODO(leafp): Make sure that the parameter elements are getting
// setup correctly with inference.
int index = 0;
for (Expression argument in required) {
var parameterType = normalParameterTypes[index++];
if (targetType != null) {
InferenceContext.setType(
argument,
typeSystem.refineNumericInvocationContext(
targetType, methodElement, invocationContext, parameterType));
} else {
InferenceContext.setType(argument, parameterType);
}
}
index = 0;
for (Expression argument in optional) {
InferenceContext.setType(argument, optionalParameterTypes[index++]);
}
for (Expression argument in named) {
if (argument is NamedExpression) {
var type = namedParameterTypes[argument.name.label.name];
if (type != null) {
InferenceContext.setType(argument, type);
}
}
}
}
checkUnreachableNode(node);
int length = arguments.length;
var flow = flowAnalysis?.flow;
for (var i = 0; i < length; i++) {
if (isIdentical && length > 1 && i == 1) {
var firstArg = arguments[0];
flow?.equalityOp_rightBegin(firstArg, firstArg.typeOrThrow);
}
arguments[i].accept(this);
if (flow != null) {
whyNotPromotedList.add(flow.whyNotPromoted(arguments[i]));
}
}
if (isIdentical && length > 1) {
var secondArg = arguments[1];
flow?.equalityOp_end(
node.parent as Expression, secondArg, secondArg.typeOrThrow);
}
node.accept(elementResolver);
node.accept(typeAnalyzer);
}
@override
void visitAsExpression(AsExpression node) {
super.visitAsExpression(node);
flowAnalysis?.asExpression(node);
}
@override
void visitAssertInitializer(AssertInitializer node) {
InferenceContext.setType(node.condition, typeProvider.boolType);
flowAnalysis?.flow?.assert_begin();
node.condition.accept(this);
boolExpressionVerifier.checkForNonBoolExpression(
node.condition,
errorCode: CompileTimeErrorCode.NON_BOOL_EXPRESSION,
whyNotPromoted: flowAnalysis?.flow?.whyNotPromoted(node.condition),
);
flowAnalysis?.flow?.assert_afterCondition(node.condition);
node.message?.accept(this);
flowAnalysis?.flow?.assert_end();
}
@override
void visitAssertStatement(AssertStatement node) {
InferenceContext.setType(node.condition, typeProvider.boolType);
flowAnalysis?.flow?.assert_begin();
node.condition.accept(this);
boolExpressionVerifier.checkForNonBoolExpression(
node.condition,
errorCode: CompileTimeErrorCode.NON_BOOL_EXPRESSION,
whyNotPromoted: flowAnalysis?.flow?.whyNotPromoted(node.condition),
);
flowAnalysis?.flow?.assert_afterCondition(node.condition);
node.message?.accept(this);
flowAnalysis?.flow?.assert_end();
}
@override
void visitAssignmentExpression(AssignmentExpression node) {
_assignmentExpressionResolver.resolve(node as AssignmentExpressionImpl);
}
@override
void visitAwaitExpression(AwaitExpression node) {
var contextType = InferenceContext.getContext(node);
if (contextType != null) {
var futureUnion = _createFutureOr(contextType);
InferenceContext.setType(node.expression, futureUnion);
}
super.visitAwaitExpression(node);
}
@override
void visitBinaryExpression(BinaryExpression node) {
_binaryExpressionResolver.resolve(node as BinaryExpressionImpl);
}
@override
void visitBlockFunctionBody(BlockFunctionBody node) {
try {
inferenceContext.pushFunctionBodyContext(node);
_thisAccessTracker.enterFunctionBody(node);
super.visitBlockFunctionBody(node);
} finally {
_thisAccessTracker.exitFunctionBody(node);
inferenceContext.popFunctionBodyContext(node);
}
}
@override
void visitBooleanLiteral(BooleanLiteral node) {
flowAnalysis?.flow?.booleanLiteral(node, node.value);
super.visitBooleanLiteral(node);
}
@override
void visitBreakStatement(BreakStatement node) {
//
// We do not visit the label because it needs to be visited in the context
// of the statement.
//
checkUnreachableNode(node);
node.accept(elementResolver);
node.accept(typeAnalyzer);
flowAnalysis?.breakStatement(node);
}
@override
void visitCascadeExpression(covariant CascadeExpressionImpl node) {
InferenceContext.setTypeFromNode(node.target, node);
node.target.accept(this);
if (node.isNullAware && flowAnalysis != null) {
flowAnalysis!.flow!.nullAwareAccess_rightBegin(
node.target, node.target.staticType ?? typeProvider.dynamicType);
_unfinishedNullShorts.add(node.nullShortingTermination);
}
node.cascadeSections.accept(this);
node.accept(elementResolver);
node.accept(typeAnalyzer);
nullShortingTermination(node);
}
@override
void visitClassDeclaration(ClassDeclaration node) {
//
// Continue the class resolution.
//
var outerType = enclosingClass;
try {
super.visitClassDeclaration(node);
node.accept(elementResolver);
node.accept(typeAnalyzer);
} finally {
_thisType = outerType?.thisType;
enclosingClass = outerType;
}
}
@override
void visitClassDeclarationInScope(ClassDeclaration node) {
enclosingClass = node.declaredElement;
_thisType = enclosingClass?.thisType;
super.visitClassDeclarationInScope(node);
}
@override
void visitClassTypeAlias(ClassTypeAlias node) {
super.visitClassTypeAlias(node);
node.accept(elementResolver);
// Note: no need to call the typeAnalyzer since it does not override
// visitClassTypeAlias.
}
@override
void visitComment(Comment node) {
var parent = node.parent;
if (parent is FunctionDeclaration ||
parent is FunctionTypeAlias ||
parent is ConstructorDeclaration) {
return;
}
// TODO(scheglov) Change corresponding visiting places to visit comments
// with name scopes set for correct comments resolution.
if (parent is GenericTypeAlias) {
var element = parent.declaredElement as TypeAliasElement;
var outerScope = nameScope;
try {
nameScope = TypeParameterScope(nameScope, element.typeParameters);
var aliasedElement = element.aliasedElement;
if (aliasedElement is GenericFunctionTypeElement) {
nameScope = FormalParameterScope(
TypeParameterScope(nameScope, aliasedElement.typeParameters),
aliasedElement.parameters,
);
}
super.visitComment(node);
return;
} finally {
nameScope = outerScope;
}
} else if (parent is MethodDeclaration) {
var outerScope = nameScope;
try {
var element = parent.declaredElement!;
nameScope = FormalParameterScope(nameScope, element.parameters);
super.visitComment(node);
return;
} finally {
nameScope = outerScope;
}
}
super.visitComment(node);
}
@override
void visitCommentReference(CommentReference node) {
//
// We do not visit the identifier because it needs to be visited in the
// context of the reference.
//
node.accept(elementResolver);
node.accept(typeAnalyzer);
}
@override
void visitCompilationUnit(CompilationUnit node) {
NodeList<Directive> directives = node.directives;
int directiveCount = directives.length;
for (int i = 0; i < directiveCount; i++) {
directives[i].accept(this);
}
NodeList<CompilationUnitMember> declarations = node.declarations;
int declarationCount = declarations.length;
for (int i = 0; i < declarationCount; i++) {
declarations[i].accept(this);
}
node.accept(elementResolver);
node.accept(typeAnalyzer);
}
@override
void visitConditionalExpression(ConditionalExpression node) {
Expression condition = node.condition;
var flow = flowAnalysis?.flow;
flow?.conditional_conditionBegin();
// TODO(scheglov) Do we need these checks for null?
condition.accept(this);
condition = node.condition;
var whyNotPromoted = flowAnalysis?.flow?.whyNotPromoted(condition);
boolExpressionVerifier.checkForNonBoolCondition(condition,
whyNotPromoted: whyNotPromoted);
Expression thenExpression = node.thenExpression;
InferenceContext.setTypeFromNode(thenExpression, node);
if (flow != null) {
flow.conditional_thenBegin(condition, node);
checkUnreachableNode(thenExpression);
}
thenExpression.accept(this);
nullSafetyDeadCodeVerifier.flowEnd(thenExpression);
Expression elseExpression = node.elseExpression;
InferenceContext.setTypeFromNode(elseExpression, node);
if (flow != null) {
flow.conditional_elseBegin(thenExpression);
checkUnreachableNode(elseExpression);
elseExpression.accept(this);
flow.conditional_end(node, elseExpression);
nullSafetyDeadCodeVerifier.flowEnd(elseExpression);
} else {
elseExpression.accept(this);
}
node.accept(elementResolver);
node.accept(typeAnalyzer);
}
@override
void visitConfiguration(Configuration node) {
// Don't visit the children. For the time being we don't resolve anything
// inside the configuration.
}
@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
var outerFunction = _enclosingFunction;
_enclosingFunction = node.declaredElement;
flowAnalysis!.topLevelDeclaration_enter(node, node.parameters);
flowAnalysis!.executableDeclaration_enter(node, node.parameters, false);
var returnType = _enclosingFunction!.type.returnType;
InferenceContext.setType(node.body, returnType);
super.visitConstructorDeclaration(node);
if (node.factoryKeyword != null) {
var bodyContext = BodyInferenceContext.of(node.body);
checkForBodyMayCompleteNormally(
returnType: bodyContext?.contextType,
body: node.body,
errorNode: node,
);
}
flowAnalysis!.executableDeclaration_exit(node.body, false);
flowAnalysis!.topLevelDeclaration_exit();
nullSafetyDeadCodeVerifier.flowEnd(node);
_enclosingFunction = outerFunction;
}
@override
void visitConstructorDeclarationInScope(ConstructorDeclaration node) {
super.visitConstructorDeclarationInScope(node);
// Because of needing a different scope for the initializer list, the
// overridden implementation of this method cannot cause the visitNode
// method to be invoked. As a result, we have to hard-code using the
// element resolver and type analyzer to visit the constructor declaration.
node.accept(elementResolver);
node.accept(typeAnalyzer);
safelyVisitComment(node.documentationComment);
}
@override
void visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
//
// We visit the expression, but do not visit the field name because it needs
// to be visited in the context of the constructor field initializer node.
//
var fieldElement = enclosingClass!.getField(node.fieldName.name);
InferenceContext.setType(node.expression, fieldElement?.type);
node.expression.accept(this);
var whyNotPromoted = flowAnalysis?.flow?.whyNotPromoted(node.expression);
node.accept(elementResolver);
node.accept(typeAnalyzer);
var enclosingConstructor = enclosingFunction as ConstructorElement;
if (fieldElement != null) {
checkForFieldInitializerNotAssignable(node, fieldElement,
isConstConstructor: enclosingConstructor.isConst,
whyNotPromoted: whyNotPromoted);
}
}
@override
void visitConstructorName(ConstructorName node) {
//
// We do not visit either the type name, because it won't be visited anyway,
// or the name, because it needs to be visited in the context of the
// constructor name.
//
node.accept(elementResolver);
node.accept(typeAnalyzer);
}
@override
void visitContinueStatement(ContinueStatement node) {
//
// We do not visit the label because it needs to be visited in the context
// of the statement.
//
checkUnreachableNode(node);
node.accept(elementResolver);
node.accept(typeAnalyzer);
flowAnalysis?.continueStatement(node);
}
@override
void visitDefaultFormalParameter(DefaultFormalParameter node) {
InferenceContext.setType(node.defaultValue, node.declaredElement?.type);
super.visitDefaultFormalParameter(node);
ParameterElement element = node.declaredElement!;
if (element is DefaultParameterElementImpl && node.isOfLocalFunction) {
element.constantInitializer = node.defaultValue;
}
}
@override
void visitDoStatementInScope(DoStatement node) {
checkUnreachableNode(node);
var body = node.body;
var condition = node.condition;
flowAnalysis?.flow?.doStatement_bodyBegin(node);
visitStatementInScope(body);
flowAnalysis?.flow?.doStatement_conditionBegin();
InferenceContext.setType(condition, typeProvider.boolType);
condition.accept(this);
condition = node.condition;
var whyNotPromoted = flowAnalysis?.flow?.whyNotPromoted(condition);
boolExpressionVerifier.checkForNonBoolCondition(condition,
whyNotPromoted: whyNotPromoted);
flowAnalysis?.flow?.doStatement_end(condition);
}
@override
void visitEmptyFunctionBody(EmptyFunctionBody node) {
if (resolveOnlyCommentInFunctionBody) {
return;
}
super.visitEmptyFunctionBody(node);
}
@override
void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
node.metadata.accept(this);
super.visitEnumConstantDeclaration(node);
}
@override
void visitEnumDeclaration(EnumDeclaration node) {
//
// Continue the enum resolution.
//
var outerType = enclosingClass;
try {
enclosingClass = node.declaredElement;
_thisType = enclosingClass?.thisType;
super.visitEnumDeclaration(node);
node.accept(elementResolver);
node.accept(typeAnalyzer);
} finally {
_thisType = outerType?.thisType;
enclosingClass = outerType;
}
}
@override
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
if (resolveOnlyCommentInFunctionBody) {
return;
}
try {
inferenceContext.pushFunctionBodyContext(node);
InferenceContext.setType(
node.expression,
inferenceContext.bodyContext!.contextType,
);
_thisAccessTracker.enterFunctionBody(node);
super.visitExpressionFunctionBody(node);
flowAnalysis?.flow?.handleExit();
inferenceContext.bodyContext!.addReturnExpression(node.expression);
} finally {
_thisAccessTracker.exitFunctionBody(node);
inferenceContext.popFunctionBodyContext(node);
}
}
@override
void visitExtensionDeclaration(ExtensionDeclaration node) {
try {
super.visitExtensionDeclaration(node);
node.accept(elementResolver);
node.accept(typeAnalyzer);
} finally {
_thisType = null;
}
}
@override
void visitExtensionDeclarationInScope(ExtensionDeclaration node) {
_thisType = node.declaredElement!.extendedType;
super.visitExtensionDeclarationInScope(node);
}
@override
void visitExtensionOverride(ExtensionOverride node) {
var whyNotPromotedList = <Map<DartType, NonPromotionReason> Function()>[];
node.extensionName.accept(this);
node.typeArguments?.accept(this);
ExtensionMemberResolver(this).setOverrideReceiverContextType(node);
visitArgumentList(node.argumentList,
whyNotPromotedList: whyNotPromotedList);
node.accept(elementResolver);
extensionResolver.resolveOverride(node, whyNotPromotedList);
}
@override
void visitFieldDeclaration(FieldDeclaration node) {
_thisAccessTracker.enterFieldDeclaration(node);
try {
super.visitFieldDeclaration(node);
} finally {
_thisAccessTracker.exitFieldDeclaration(node);
}
}
@override
void visitForElementInScope(ForElement node) {
_forResolver.resolveElement(node as ForElementImpl);
}
@override
void visitForStatementInScope(ForStatement node) {
_forResolver.resolveStatement(node as ForStatementImpl);
nullSafetyDeadCodeVerifier.flowEnd(node.body);
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
var outerFunction = _enclosingFunction;
_enclosingFunction = node.declaredElement;
bool isLocal = node.parent is FunctionDeclarationStatement;
if (isLocal) {
flowAnalysis!.flow!.functionExpression_begin(node);
} else {
flowAnalysis!
.topLevelDeclaration_enter(node, node.functionExpression.parameters);
}
flowAnalysis!.executableDeclaration_enter(
node,
node.functionExpression.parameters,
isLocal,
);
var functionType = _enclosingFunction!.type;
InferenceContext.setType(node.functionExpression, functionType);
super.visitFunctionDeclaration(node);
// TODO(scheglov) encapsulate
var bodyContext = BodyInferenceContext.of(
node.functionExpression.body,
);
checkForBodyMayCompleteNormally(
returnType: bodyContext?.contextType,
body: node.functionExpression.body,
errorNode: node.name,
);
flowAnalysis!.executableDeclaration_exit(
node.functionExpression.body,
isLocal,
);
if (isLocal) {
flowAnalysis!.flow!.functionExpression_end();
} else {
flowAnalysis!.topLevelDeclaration_exit();
}
nullSafetyDeadCodeVerifier.flowEnd(node);
_enclosingFunction = outerFunction;
node.accept(elementResolver);
// Note: no need to call the typeAnalyzer since it does not override
// visitFunctionDeclaration
}
@override
void visitFunctionDeclarationInScope(FunctionDeclaration node) {
super.visitFunctionDeclarationInScope(node);
safelyVisitComment(node.documentationComment);
}
@override
void visitFunctionExpression(covariant FunctionExpressionImpl node) {
var outerFunction = _enclosingFunction;
_enclosingFunction = node.declaredElement;
if (node.parent is FunctionDeclaration) {
_functionExpressionResolver.resolve(node);
} else {
Scope outerScope = nameScope;
try {
ExecutableElement element = node.declaredElement!;
nameScope = FormalParameterScope(
TypeParameterScope(nameScope, element.typeParameters),
element.parameters,
);
_functionExpressionResolver.resolve(node);
} finally {
nameScope = outerScope;
}
}
_enclosingFunction = outerFunction;
}
@override
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
var whyNotPromotedList = <Map<DartType, NonPromotionReason> Function()>[];
node.function.accept(this);
_functionExpressionInvocationResolver.resolve(
node as FunctionExpressionInvocationImpl, whyNotPromotedList);
nullShortingTermination(node);
checkForArgumentTypesNotAssignableInList(
node.argumentList, whyNotPromotedList);
}
@override
void visitFunctionReference(FunctionReference node) {
_functionReferenceResolver.resolve(node as FunctionReferenceImpl);
}
@override
void visitFunctionTypeAlias(FunctionTypeAlias node) {
super.visitFunctionTypeAlias(node);
node.accept(elementResolver);
// Note: no need to call the typeAnalyzer since it does not override
// visitFunctionTypeAlias.
}
@override
void visitFunctionTypeAliasInScope(FunctionTypeAlias node) {
super.visitFunctionTypeAliasInScope(node);
safelyVisitComment(node.documentationComment);
}
@override
void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
super.visitFunctionTypedFormalParameter(node);
node.accept(elementResolver);
// Note: no need to call the typeAnalyzer since it does not override
// visitFunctionTypedFormalParameter.
}
@override
void visitGenericTypeAlias(GenericTypeAlias node) {
super.visitGenericTypeAlias(node);
node.accept(elementResolver);
// Note: no need to call the typeAnalyzer since it does not override
// visitGenericTypeAlias.
}
@override
void visitGenericTypeAliasInFunctionScope(GenericTypeAlias node) {
super.visitGenericTypeAliasInFunctionScope(node);
safelyVisitComment(node.documentationComment);
}
@override
void visitHideCombinator(HideCombinator node) {}
@override
void visitIfElement(IfElement node) {
flowAnalysis?.flow?.ifStatement_conditionBegin();
Expression condition = node.condition;
InferenceContext.setType(condition, typeProvider.boolType);
condition.accept(this);
condition = node.condition;
var whyNotPromoted = flowAnalysis?.flow?.whyNotPromoted(condition);
boolExpressionVerifier.checkForNonBoolCondition(condition,
whyNotPromoted: whyNotPromoted);
CollectionElement thenElement = node.thenElement;
flowAnalysis!.flow?.ifStatement_thenBegin(condition, node);
thenElement.accept(this);
var elseElement = node.elseElement;
if (elseElement != null) {
flowAnalysis?.flow?.ifStatement_elseBegin();
elseElement.accept(this);
}
flowAnalysis?.flow?.ifStatement_end(elseElement != null);
node.accept(elementResolver);
node.accept(typeAnalyzer);
}
@override
void visitIfStatement(IfStatement node) {
checkUnreachableNode(node);
flowAnalysis?.flow?.ifStatement_conditionBegin();
Expression condition = node.condition;
InferenceContext.setType(condition, typeProvider.boolType);
condition.accept(this);
condition = node.condition;
var whyNotPromoted = flowAnalysis?.flow?.whyNotPromoted(condition);
boolExpressionVerifier.checkForNonBoolCondition(condition,
whyNotPromoted: whyNotPromoted);
Statement thenStatement = node.thenStatement;
flowAnalysis!.flow?.ifStatement_thenBegin(condition, node);
visitStatementInScope(thenStatement);
nullSafetyDeadCodeVerifier.flowEnd(thenStatement);
var elseStatement = node.elseStatement;
if (elseStatement != null) {
flowAnalysis?.flow?.ifStatement_elseBegin();
visitStatementInScope(elseStatement);
nullSafetyDeadCodeVerifier.flowEnd(elseStatement);
}
flowAnalysis?.flow?.ifStatement_end(elseStatement != null);
node.accept(elementResolver);
node.accept(typeAnalyzer);
}
@override
void visitIndexExpression(covariant IndexExpressionImpl node) {
node.target?.accept(this);
startNullAwareIndexExpression(node);
var resolver = PropertyElementResolver(this);
var result = resolver.resolveIndexExpression(
node: node,
hasRead: true,
hasWrite: false,
);
var element = result.readElement;
node.staticElement = element as MethodElement?;
InferenceContext.setType(node.index, result.indexContextType);
node.index.accept(this);
var whyNotPromoted = flowAnalysis?.flow?.whyNotPromoted(node.index);
checkIndexExpressionIndex(
node.index,
readElement: result.readElement as ExecutableElement?,
writeElement: null,
whyNotPromoted: whyNotPromoted,
);
DartType type;
if (identical(node.realTarget.staticType, NeverTypeImpl.instance)) {
type = NeverTypeImpl.instance;
} else if (element is MethodElement) {
type = element.returnType;
} else {
type = DynamicTypeImpl.instance;
}
inferenceHelper.recordStaticType(node, type);
nullShortingTermination(node);
}
@override
void visitInstanceCreationExpression(
covariant InstanceCreationExpressionImpl node) {
var whyNotPromotedList = <Map<DartType, NonPromotionReason> Function()>[];
node.constructorName.accept(this);
_inferArgumentTypesForInstanceCreate(node);
visitArgumentList(node.argumentList,
whyNotPromotedList: whyNotPromotedList);
node.accept(elementResolver);
node.accept(typeAnalyzer);
checkForArgumentTypesNotAssignableInList(
node.argumentList, whyNotPromotedList);
}
@override
void visitIsExpression(IsExpression node) {
super.visitIsExpression(node);
flowAnalysis?.isExpression(node);
}
@override
void visitLabel(Label node) {}
@override
void visitLabeledStatement(LabeledStatement node) {
flowAnalysis?.labeledStatement_enter(node);
super.visitLabeledStatement(node);
flowAnalysis?.labeledStatement_exit(node);
}
@override
void visitLibraryIdentifier(LibraryIdentifier node) {}
@override
void visitListLiteral(covariant ListLiteralImpl node) {
checkUnreachableNode(node);
_typedLiteralResolver.resolveListLiteral(node);
}
@override
void visitMethodDeclaration(MethodDeclaration node) {
var outerFunction = _enclosingFunction;
_enclosingFunction = node.declaredElement;
flowAnalysis!.topLevelDeclaration_enter(node, node.parameters);
flowAnalysis!.executableDeclaration_enter(node, node.parameters, false);
DartType returnType = _enclosingFunction!.returnType;
InferenceContext.setType(node.body, returnType);
super.visitMethodDeclaration(node);
// TODO(scheglov) encapsulate
var bodyContext = BodyInferenceContext.of(node.body);
checkForBodyMayCompleteNormally(
returnType: bodyContext?.contextType,
body: node.body,
errorNode: node.name,
);
flowAnalysis!.executableDeclaration_exit(node.body, false);
flowAnalysis!.topLevelDeclaration_exit();
nullSafetyDeadCodeVerifier.flowEnd(node);
_enclosingFunction = outerFunction;
node.accept(elementResolver);
// Note: no need to call the typeAnalyzer since it does not override
// visitMethodDeclaration.
}
@override
void visitMethodInvocation(covariant MethodInvocationImpl node) {
var whyNotPromotedList = <Map<DartType, NonPromotionReason> Function()>[];
var target = node.target;
target?.accept(this);
if (_migratableAstInfoProvider.isMethodInvocationNullAware(node)) {
var flow = flowAnalysis?.flow;
if (flow != null) {
if (target is SimpleIdentifierImpl &&
target.staticElement is ClassElement) {
// `?.` to access static methods is equivalent to `.`, so do nothing.
} else {
flow.nullAwareAccess_rightBegin(
target, node.realTarget!.staticType ?? typeProvider.dynamicType);
_unfinishedNullShorts.add(node.nullShortingTermination);
}
}
}
node.typeArguments?.accept(this);
elementResolver.visitMethodInvocation(node,
whyNotPromotedList: whyNotPromotedList);
var functionRewrite = MethodInvocationResolver.getRewriteResult(node);
if (functionRewrite != null) {
nullShortingTermination(node, discardType: true);
_resolveRewrittenFunctionExpressionInvocation(
functionRewrite, whyNotPromotedList);
} else {
nullShortingTermination(node);
}
checkForArgumentTypesNotAssignableInList(
node.argumentList, whyNotPromotedList);
}
@override
void visitMixinDeclaration(MixinDeclaration node) {
//
// Continue the class resolution.
//
var outerType = enclosingClass;
try {
super.visitMixinDeclaration(node);
node.accept(elementResolver);
node.accept(typeAnalyzer);
} finally {
_thisType = outerType?.thisType;
enclosingClass = outerType;
}
}
@override
void visitMixinDeclarationInScope(MixinDeclaration node) {
enclosingClass = node.declaredElement;
_thisType = enclosingClass?.thisType;
super.visitMixinDeclarationInScope(node);
}
@override
void visitNamedExpression(NamedExpression node) {
InferenceContext.setTypeFromNode(node.expression, node);
super.visitNamedExpression(node);
// Any "why not promoted" information that flow analysis had associated with
// `node.expression` now needs to be forwarded to `node`, so that when
// `visitArgumentList` iterates through the arguments, it will find it.
flowAnalysis?.flow?.forwardExpression(node, node.expression);
}
@override
void visitNode(AstNode node) {
checkUnreachableNode(node);
node.visitChildren(this);
node.accept(elementResolver);
node.accept(typeAnalyzer);
}
@override
void visitNullLiteral(NullLiteral node) {
flowAnalysis?.flow?.nullLiteral(node);
super.visitNullLiteral(node);
}
@override
void visitParenthesizedExpression(ParenthesizedExpression node) {
InferenceContext.setTypeFromNode(node.expression, node);
super.visitParenthesizedExpression(node);
flowAnalysis?.flow?.parenthesizedExpression(node, node.expression);
}
@override
void visitPostfixExpression(PostfixExpression node) {
_postfixExpressionResolver.resolve(node as PostfixExpressionImpl);
}
@override
void visitPrefixedIdentifier(covariant PrefixedIdentifierImpl node) {
_prefixedIdentifierResolver.resolve(node);
}
@override
void visitPrefixExpression(PrefixExpression node) {
_prefixExpressionResolver.resolve(node as PrefixExpressionImpl);
}
@override
void visitPropertyAccess(covariant PropertyAccessImpl node) {
node.target?.accept(this);
startNullAwarePropertyAccess(node);
var resolver = PropertyElementResolver(this);
var result = resolver.resolvePropertyAccess(
node: node,
hasRead: true,
hasWrite: false,
);
var element = result.readElement;
var propertyName = node.propertyName;
propertyName.staticElement = element;
DartType type;
if (element is MethodElement) {
type = element.type;
} else if (element is PropertyAccessorElement && element.isGetter) {
type = element.returnType;
} else if (result.functionTypeCallType != null) {
type = result.functionTypeCallType!;
} else {
type = DynamicTypeImpl.instance;
}
type = inferenceHelper.inferTearOff(node, propertyName, type);
inferenceHelper.recordStaticType(propertyName, type);
inferenceHelper.recordStaticType(node, type);
nullShortingTermination(node);
}
@override
void visitRedirectingConstructorInvocation(
RedirectingConstructorInvocation node) {
//
// We visit the argument list, but do not visit the optional identifier
// because it needs to be visited in the context of the constructor
// invocation.
//
var whyNotPromotedList = <Map<DartType, NonPromotionReason> Function()>[];
node.accept(elementResolver);
InferenceContext.setType(node.argumentList, node.staticElement?.type);
visitArgumentList(node.argumentList,
whyNotPromotedList: whyNotPromotedList);
node.accept(typeAnalyzer);
checkForArgumentTypesNotAssignableInList(
node.argumentList, whyNotPromotedList);
}
@override
void visitRethrowExpression(RethrowExpression node) {
super.visitRethrowExpression(node);
flowAnalysis?.flow?.handleExit();
}
@override
void visitReturnStatement(ReturnStatement node) {
InferenceContext.setType(
node.expression,
inferenceContext.bodyContext?.contextType,
);
super.visitReturnStatement(node);
inferenceContext.bodyContext?.addReturnExpression(node.expression);
flowAnalysis?.flow?.handleExit();
}
@override
void visitSetOrMapLiteral(SetOrMapLiteral node) {
checkUnreachableNode(node);
_typedLiteralResolver.resolveSetOrMapLiteral(node);
}
@override
void visitShowCombinator(ShowCombinator node) {}
@override
void visitSimpleIdentifier(covariant SimpleIdentifierImpl node) {
SimpleIdentifierResolver(this, flowAnalysis).resolve(node);
}
@override
void visitSpreadElement(SpreadElement node) {
super.visitSpreadElement(node);
if (!node.isNullAware) {
nullableDereferenceVerifier.expression(node.expression,
errorCode:
CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE_IN_SPREAD);
}
}
@override
void visitSuperConstructorInvocation(SuperConstructorInvocation node) {
//
// We visit the argument list, but do not visit the optional identifier
// because it needs to be visited in the context of the constructor
// invocation.
//
var whyNotPromotedList = <Map<DartType, NonPromotionReason> Function()>[];
node.accept(elementResolver);
InferenceContext.setType(node.argumentList, node.staticElement?.type);
visitArgumentList(node.argumentList,
whyNotPromotedList: whyNotPromotedList);
node.accept(typeAnalyzer);
checkForArgumentTypesNotAssignableInList(
node.argumentList, whyNotPromotedList);
}
@override
void visitSwitchCase(SwitchCase node) {
checkUnreachableNode(node);
InferenceContext.setType(
node.expression, _enclosingSwitchStatementExpressionType);
super.visitSwitchCase(node);
var flow = flowAnalysis?.flow;
if (flow != null && flow.isReachable && _isNonNullableByDefault) {
var switchStatement = node.parent as SwitchStatement;
if (switchStatement.members.last != node && node.statements.isNotEmpty) {
errorReporter.reportErrorForToken(
CompileTimeErrorCode.SWITCH_CASE_COMPLETES_NORMALLY,
node.keyword,
);
}
}
nullSafetyDeadCodeVerifier.flowEnd(node);
}
@override
void visitSwitchDefault(SwitchDefault node) {
super.visitSwitchDefault(node);
nullSafetyDeadCodeVerifier.flowEnd(node);
}
@override
void visitSwitchStatementInScope(SwitchStatement node) {
checkUnreachableNode(node);
var previousExpressionType = _enclosingSwitchStatementExpressionType;
try {
var expression = node.expression;
expression.accept(this);
expression = node.expression;
_enclosingSwitchStatementExpressionType = expression.typeOrThrow;
if (flowAnalysis != null) {
var flow = flowAnalysis!.flow!;
flow.switchStatement_expressionEnd(node);
var exhaustiveness = _SwitchExhaustiveness(
_enclosingSwitchStatementExpressionType!,
);
var members = node.members;
for (var member in members) {
flow.switchStatement_beginCase(member.labels.isNotEmpty, node);
member.accept(this);
exhaustiveness.visitSwitchMember(member);
}
flow.switchStatement_end(exhaustiveness.isExhaustive);
} else {
node.members.accept(this);
}
} finally {
_enclosingSwitchStatementExpressionType = previousExpressionType;
}
}
@override
void visitThrowExpression(ThrowExpression node) {
super.visitThrowExpression(node);
flowAnalysis?.flow?.handleExit();
}
@override
void visitTryStatement(TryStatement node) {
if (flowAnalysis == null) {
return super.visitTryStatement(node);
}
checkUnreachableNode(node);
var flow = flowAnalysis!.flow!;
var body = node.body;
var catchClauses = node.catchClauses;
var finallyBlock = node.finallyBlock;
if (finallyBlock != null) {
flow.tryFinallyStatement_bodyBegin();
}
if (catchClauses.isNotEmpty) {
flow.tryCatchStatement_bodyBegin();
}
body.accept(this);
if (catchClauses.isNotEmpty) {
flow.tryCatchStatement_bodyEnd(body);
nullSafetyDeadCodeVerifier.flowEnd(node.body);
nullSafetyDeadCodeVerifier.tryStatementEnter(node);
var catchLength = catchClauses.length;
for (var i = 0; i < catchLength; ++i) {
var catchClause = catchClauses[i];
nullSafetyDeadCodeVerifier.verifyCatchClause(catchClause);
flow.tryCatchStatement_catchBegin(
catchClause.exceptionParameter?.staticElement as PromotableElement?,
catchClause.stackTraceParameter?.staticElement as PromotableElement?,
);
catchClause.accept(this);
flow.tryCatchStatement_catchEnd();
nullSafetyDeadCodeVerifier.flowEnd(catchClause.body);
}
flow.tryCatchStatement_end();
nullSafetyDeadCodeVerifier.tryStatementExit(node);
}
if (finallyBlock != null) {
flow.tryFinallyStatement_finallyBegin(
catchClauses.isNotEmpty ? node : body);
finallyBlock.accept(this);
flow.tryFinallyStatement_end();
}
}
@override
void visitTypeName(TypeName node) {}
@override
void visitVariableDeclaration(VariableDeclaration node) {
_variableDeclarationResolver.resolve(node as VariableDeclarationImpl);
var declaredElement = node.declaredElement!;
if (node.parent!.parent is ForParts) {
_define(declaredElement);
}
var initializer = node.initializer;
var parent = node.parent as VariableDeclarationList;
var declaredType = parent.type;
if (initializer != null) {
var initializerStaticType = initializer.typeOrThrow;
if (declaredType == null) {
if (_isNonNullableByDefault &&
initializerStaticType is TypeParameterType) {
flowAnalysis?.flow?.promote(
declaredElement as PromotableElement, initializerStaticType);
}
} else {
flowAnalysis?.flow?.initialize(declaredElement as PromotableElement,
initializerStaticType, initializer,
isFinal: parent.isFinal, isLate: parent.isLate);
}
}
}
@override
void visitVariableDeclarationList(VariableDeclarationList node) {
flowAnalysis?.variableDeclarationList(node);
for (VariableDeclaration decl in node.variables) {
VariableElement variableElement = decl.declaredElement!;
InferenceContext.setType(decl, variableElement.type);
}
super.visitVariableDeclarationList(node);
}
@override
void visitWhileStatement(WhileStatement node) {
checkUnreachableNode(node);
// Note: since we don't call the base class, we have to maintain
// _implicitLabelScope ourselves.
ImplicitLabelScope outerImplicitScope = _implicitLabelScope;
try {
_implicitLabelScope = _implicitLabelScope.nest(node);
Expression condition = node.condition;
InferenceContext.setType(condition, typeProvider.boolType);
flowAnalysis?.flow?.whileStatement_conditionBegin(node);
condition.accept(this);
condition = node.condition;
var whyNotPromoted = flowAnalysis?.flow?.whyNotPromoted(condition);
boolExpressionVerifier.checkForNonBoolCondition(node.condition,
whyNotPromoted: whyNotPromoted);
Statement body = node.body;
flowAnalysis?.flow?.whileStatement_bodyBegin(node, condition);
visitStatementInScope(body);
flowAnalysis?.flow?.whileStatement_end();
nullSafetyDeadCodeVerifier.flowEnd(node.body);
} finally {
_implicitLabelScope = outerImplicitScope;
}
// TODO(brianwilkerson) If the loop can only be exited because the condition
// is false, then propagateFalseState(condition);
node.accept(elementResolver);
node.accept(typeAnalyzer);
}
@override
void visitYieldStatement(YieldStatement node) {
_yieldStatementResolver.resolve(node);
}
/// Creates a union of `T | Future<T>`, unless `T` is already a
/// future-union, in which case it simply returns `T`.
DartType _createFutureOr(DartType type) {
if (type.isDartAsyncFutureOr) {
return type;
}
return typeProvider.futureOrType(type);
}
void _inferArgumentTypesForInstanceCreate(
covariant InstanceCreationExpressionImpl node) {
var constructorName = node.constructorName;
var typeName = constructorName.type;
var typeArguments = typeName.typeArguments;
var elementToInfer = inferenceHelper.constructorElementToInfer(
constructorName: constructorName,
definingLibrary: definingLibrary,
);
FunctionType? inferred;
// If the constructor is generic, we'll have a ConstructorMember that
// substitutes in type arguments (possibly `dynamic`) from earlier in
// resolution.
//
// Otherwise we'll have a ConstructorElement, and we can skip inference
// because there's nothing to infer in a non-generic type.
if (elementToInfer != null) {
// TODO(leafp): Currently, we may re-infer types here, since we
// sometimes resolve multiple times. We should really check that we
// have not already inferred something. However, the obvious ways to
// check this don't work, since we may have been instantiated
// to bounds in an earlier phase, and we *do* want to do inference
// in that case.
// Get back to the uninstantiated generic constructor.
// TODO(jmesserly): should we store this earlier in resolution?
// Or look it up, instead of jumping backwards through the Member?
var rawElement = elementToInfer.element;
var constructorType = elementToInfer.asType;
inferred = inferenceHelper.inferArgumentTypesForGeneric(
node, constructorType, typeArguments,
isConst: node.isConst, errorNode: node.constructorName);
if (inferred != null) {
var arguments = node.argumentList;
InferenceContext.setType(arguments, inferred);
// Fix up the parameter elements based on inferred method.
arguments.correspondingStaticParameters =
resolveArgumentsToParameters(arguments, inferred.parameters, null);
constructorName.type.type = inferred.returnType;
// Update the static element as well. This is used in some cases, such
// as computing constant values. It is stored in two places.
var constructorElement = ConstructorMember.from(
rawElement,
inferred.returnType as InterfaceType,
);
constructorName.staticElement = constructorElement;
}
}
if (inferred == null) {
var constructorElement = constructorName.staticElement;
if (constructorElement != null) {
var type = constructorElement.type;
type = toLegacyTypeIfOptOut(type) as FunctionType;
InferenceContext.setType(node.argumentList, type);
}
}
}
/// Continues resolution of a [FunctionExpressionInvocation] that was created
/// from a rewritten [MethodInvocation]. The target function is already
/// resolved.
///
/// The specification says that `target.getter()` should be treated as an
/// ordinary method invocation. So, we need to perform the same null shorting
/// as for method invocations.
void _resolveRewrittenFunctionExpressionInvocation(
FunctionExpressionInvocation node,
List<WhyNotPromotedGetter> whyNotPromotedList,
) {
var function = node.function;
if (function is PropertyAccess &&
_migratableAstInfoProvider.isPropertyAccessNullAware(function) &&
_isNonNullableByDefault) {
var target = function.target;
if (target is SimpleIdentifier && target.staticElement is ClassElement) {
// `?.` to access static methods is equivalent to `.`, so do nothing.
} else {
flowAnalysis!.flow!.nullAwareAccess_rightBegin(function,
function.realTarget.staticType ?? typeProvider.dynamicType);
_unfinishedNullShorts.add(node.nullShortingTermination);
}
}
_functionExpressionInvocationResolver.resolve(
node as FunctionExpressionInvocationImpl, whyNotPromotedList);
nullShortingTermination(node);
}
/// Given an [argumentList] and the [parameters] related to the element that
/// will be invoked using those arguments, compute the list of parameters that
/// correspond to the list of arguments.
///
/// An error will be reported to [onError] if any of the arguments cannot be
/// matched to a parameter. onError will be provided the node of the first
/// argument that is not matched. onError can be null to ignore the error.
///
/// Returns the parameters that correspond to the arguments. If no parameter
/// matched an argument, that position will be `null` in the list.
static List<ParameterElement?> resolveArgumentsToParameters(
ArgumentList argumentList,
List<ParameterElement> parameters,
void Function(ErrorCode errorCode, AstNode node,
[List<Object> arguments])?
onError) {
if (parameters.isEmpty && argumentList.arguments.isEmpty) {
return const <ParameterElement>[];
}
int requiredParameterCount = 0;
int unnamedParameterCount = 0;
List<ParameterElement> unnamedParameters = <ParameterElement>[];
Map<String, ParameterElement>? namedParameters;
int length = parameters.length;
for (int i = 0; i < length; i++) {
ParameterElement parameter = parameters[i];
if (parameter.isRequiredPositional) {
unnamedParameters.add(parameter);
unnamedParameterCount++;
requiredParameterCount++;
} else if (parameter.isOptionalPositional) {
unnamedParameters.add(parameter);
unnamedParameterCount++;
} else {
namedParameters ??= HashMap<String, ParameterElement>();
namedParameters[parameter.name] = parameter;
}
}
int unnamedIndex = 0;
NodeList<Expression> arguments = argumentList.arguments;
int argumentCount = arguments.length;
List<ParameterElement?> resolvedParameters =
List<ParameterElement?>.filled(argumentCount, null);
int positionalArgumentCount = 0;
HashSet<String>? usedNames;
bool noBlankArguments = true;
Expression? firstUnresolvedArgument;
for (int i = 0; i < argumentCount; i++) {
Expression argument = arguments[i];
if (argument is NamedExpressionImpl) {
var nameNode = argument.name.label;
String name = nameNode.name;
var element = namedParameters != null ? namedParameters[name] : null;
if (element == null) {
if (onError != null) {
onError(CompileTimeErrorCode.UNDEFINED_NAMED_PARAMETER, nameNode,
[name]);
}
} else {
resolvedParameters[i] = element;
nameNode.staticElement = element;
}
usedNames ??= HashSet<String>();
if (!usedNames.add(name)) {
if (onError != null) {
onError(CompileTimeErrorCode.DUPLICATE_NAMED_ARGUMENT, nameNode,
[name]);
}
}
} else {
if (argument is SimpleIdentifier && argument.name.isEmpty) {
noBlankArguments = false;
}
positionalArgumentCount++;
if (unnamedIndex < unnamedParameterCount) {
resolvedParameters[i] = unnamedParameters[unnamedIndex++];
} else {
firstUnresolvedArgument ??= argument;
}
}
}
if (positionalArgumentCount < requiredParameterCount && noBlankArguments) {
if (onError != null) {
onError(CompileTimeErrorCode.NOT_ENOUGH_POSITIONAL_ARGUMENTS,
argumentList, [requiredParameterCount, positionalArgumentCount]);
}
} else if (positionalArgumentCount > unnamedParameterCount &&
noBlankArguments) {
ErrorCode errorCode;
int namedParameterCount = namedParameters?.length ?? 0;
int namedArgumentCount = usedNames?.length ?? 0;
if (namedParameterCount > namedArgumentCount) {
errorCode =
CompileTimeErrorCode.EXTRA_POSITIONAL_ARGUMENTS_COULD_BE_NAMED;
} else {
errorCode = CompileTimeErrorCode.EXTRA_POSITIONAL_ARGUMENTS;
}
if (onError != null) {
onError(errorCode, firstUnresolvedArgument!,
[unnamedParameterCount, positionalArgumentCount]);
}
}
return resolvedParameters;
}
}
/// Override of [ResolverVisitorForMigration] that invokes methods of
/// [MigrationResolutionHooks] when appropriate.
class ResolverVisitorForMigration extends ResolverVisitor {
final MigrationResolutionHooks _migrationResolutionHooks;
ResolverVisitorForMigration(
InheritanceManager3 inheritanceManager,
LibraryElement definingLibrary,
Source source,
TypeProvider typeProvider,
AnalysisErrorListener errorListener,
TypeSystemImpl typeSystem,
FeatureSet featureSet,
MigrationResolutionHooks migrationResolutionHooks)
: _migrationResolutionHooks = migrationResolutionHooks,
super._(
inheritanceManager,
definingLibrary,
source,
typeSystem,
typeProvider,
errorListener,
featureSet,
null,
FlowAnalysisHelperForMigration(
typeSystem, migrationResolutionHooks, true),
migrationResolutionHooks,
migrationResolutionHooks);
@override
void visitConditionalExpression(covariant ConditionalExpressionImpl node) {
var conditionalKnownValue =
_migrationResolutionHooks.getConditionalKnownValue(node);
if (conditionalKnownValue == null) {
super.visitConditionalExpression(node);
return;
} else {
var subexpressionToKeep =
conditionalKnownValue ? node.thenExpression : node.elseExpression;
subexpressionToKeep.accept(this);
typeAnalyzer.recordStaticType(node, subexpressionToKeep.typeOrThrow);
}
}
@override
void visitIfElement(IfElement node) {
var conditionalKnownValue =
_migrationResolutionHooks.getConditionalKnownValue(node);
if (conditionalKnownValue == null) {
super.visitIfElement(node);
return;
} else {
(conditionalKnownValue ? node.thenElement : node.elseElement)
?.accept(this);
}
}
@override
void visitIfStatement(IfStatement node) {
var conditionalKnownValue =
_migrationResolutionHooks.getConditionalKnownValue(node);
if (conditionalKnownValue == null) {
super.visitIfStatement(node);
return;
} else {
(conditionalKnownValue ? node.thenStatement : node.elseStatement)
?.accept(this);
}
}
}
/// The abstract class `ScopedVisitor` maintains name and label scopes as an AST
/// structure is being visited.
abstract class ScopedVisitor extends UnifyingAstVisitor<void> {
static const _nameScopeProperty = 'nameScope';
/// The element for the library containing the compilation unit being visited.
final LibraryElement definingLibrary;
/// The source representing the compilation unit being visited.
final Source source;
/// The object used to access the types from the core library.
final TypeProviderImpl typeProvider;
/// The error reporter that will be informed of any errors that are found
/// during resolution.
final ErrorReporter errorReporter;
/// The scope used to resolve identifiers.
Scope nameScope;
/// The scope used to resolve unlabeled `break` and `continue` statements.
ImplicitLabelScope _implicitLabelScope = ImplicitLabelScope.ROOT;
/// The scope used to resolve labels for `break` and `continue` statements, or
/// `null` if no labels have been defined in the current context.
LabelScope? labelScope;
/// The class containing the AST nodes being visited,
/// or `null` if we are not in the scope of a class.
ClassElement? enclosingClass;
/// The element representing the extension containing the AST nodes being
/// visited, or `null` if we are not in the scope of an extension.
ExtensionElement? enclosingExtension;
/// Initialize a newly created visitor to resolve the nodes in a compilation
/// unit.
///
/// [definingLibrary] is the element for the library containing the
/// compilation unit being visited.
/// [source] is the source representing the compilation unit being visited.
/// [typeProvider] is the object used to access the types from the core
/// library.
/// [errorListener] is the error listener that will be informed of any errors
/// that are found during resolution.
/// [nameScope] is the scope used to resolve identifiers in the node that will
/// first be visited. If `null` or unspecified, a new [LibraryScope] will be
/// created based on [definingLibrary] and [typeProvider].
ScopedVisitor(this.definingLibrary, Source source, this.typeProvider,
AnalysisErrorListener errorListener,
{Scope? nameScope})
: source = source,
errorReporter = ErrorReporter(
errorListener,
source,
isNonNullableByDefault: definingLibrary.isNonNullableByDefault,
),
nameScope = nameScope ?? LibraryScope(definingLibrary);
/// Return the implicit label scope in which the current node is being
/// resolved.
ImplicitLabelScope get implicitLabelScope => _implicitLabelScope;
/// Replaces the current [Scope] with the enclosing [Scope].
///
/// @return the enclosing [Scope].
Scope popNameScope() {
nameScope = (nameScope as EnclosedScope).parent;
return nameScope;
}
/// Pushes a new [Scope] into the visitor.
///
/// @return the new [Scope].
Scope pushNameScope() {
Scope newScope = LocalScope(nameScope);
nameScope = newScope;
return nameScope;
}
@override
void visitBlock(Block node) {
_withDeclaredLocals(node, node.statements, () {
super.visitBlock(node);
});
}
@override
void visitBlockFunctionBody(BlockFunctionBody node) {
ImplicitLabelScope implicitOuterScope = _implicitLabelScope;
try {
_implicitLabelScope = ImplicitLabelScope.ROOT;
super.visitBlockFunctionBody(node);
} finally {
_implicitLabelScope = implicitOuterScope;
}
}
@override
void visitCatchClause(CatchClause node) {
var exception = node.exceptionParameter;
if (exception != null) {
Scope outerScope = nameScope;
try {
nameScope = LocalScope(nameScope);
_define(exception.staticElement!);
var stackTrace = node.stackTraceParameter;
if (stackTrace != null) {
_define(stackTrace.staticElement!);
}
super.visitCatchClause(node);
} finally {
nameScope = outerScope;
}
} else {
super.visitCatchClause(node);
}
}
@override
void visitClassDeclaration(ClassDeclaration node) {
Scope outerScope = nameScope;
var outerClass = enclosingClass;
try {
ClassElement element = node.declaredElement!;
enclosingClass = node.declaredElement;
node.metadata.accept(this);
nameScope = TypeParameterScope(
nameScope,
element.typeParameters,
);
visitClassDeclarationInScope(node);
nameScope = ClassScope(nameScope, element);
visitClassMembersInScope(node);
} finally {
enclosingClass = outerClass;
nameScope = outerScope;
}
}
void visitClassDeclarationInScope(ClassDeclaration node) {
node.name.accept(this);
node.typeParameters?.accept(this);
node.extendsClause?.accept(this);
node.withClause?.accept(this);
node.implementsClause?.accept(this);
node.nativeClause?.accept(this);
}
void visitClassMembersInScope(ClassDeclaration node) {
node.documentationComment?.accept(this);
node.members.accept(this);
}
@override
void visitClassTypeAlias(ClassTypeAlias node) {
node.metadata.accept(this);
Scope outerScope = nameScope;
try {
ClassElement element = node.declaredElement!;
nameScope = ClassScope(
TypeParameterScope(nameScope, element.typeParameters),
element,
);
visitClassTypeAliasInScope(node);
} finally {
nameScope = outerScope;
}
}
void visitClassTypeAliasInScope(ClassTypeAlias node) {
// Note: we don't visit metadata because it's not inside the class type
// alias's type parameter scope. It was already visited in
// [visitClassTypeAlias].
node.documentationComment?.accept(this);
node.name.accept(this);
node.typeParameters?.accept(this);
node.superclass.accept(this);
node.withClause.accept(this);
node.implementsClause?.accept(this);
}
@override
void visitCompilationUnit(CompilationUnit node) {
_setNodeNameScope(node, nameScope);
super.visitCompilationUnit(node);
}
@override
void visitConstructorDeclaration(ConstructorDeclaration node) {
Scope outerScope = nameScope;
try {
ConstructorElement element = node.declaredElement!;
node.documentationComment?.accept(this);
node.metadata.accept(this);
node.returnType.accept(this);
node.name?.accept(this);
node.parameters.accept(this);
try {
nameScope = ConstructorInitializerScope(
nameScope,
element,
);
node.initializers.accept(this);
} finally {
nameScope = outerScope;
}
node.redirectedConstructor?.accept(this);
nameScope = FormalParameterScope(
nameScope,
element.parameters,
);
visitConstructorDeclarationInScope(node);
} finally {
nameScope = outerScope;
}
}
void visitConstructorDeclarationInScope(ConstructorDeclaration node) {
node.body.accept(this);
}
@override
void visitDeclaredIdentifier(DeclaredIdentifier node) {
_define(node.declaredElement!);
super.visitDeclaredIdentifier(node);
}
@override
void visitDoStatement(DoStatement node) {
ImplicitLabelScope outerImplicitScope = _implicitLabelScope;
try {
_implicitLabelScope = _implicitLabelScope.nest(node);
visitDoStatementInScope(node);
} finally {
_implicitLabelScope = outerImplicitScope;
}
}
void visitDoStatementInScope(DoStatement node) {
visitStatementInScope(node.body);
node.condition.accept(this);
}
@override
void visitEnumDeclaration(EnumDeclaration node) {
Scope outerScope = nameScope;
var outerClass = enclosingClass;
try {
ClassElement element = node.declaredElement!;
enclosingClass = node.declaredElement;
node.metadata.accept(this);
nameScope = ClassScope(nameScope, element);
visitEnumMembersInScope(node);
} finally {
enclosingClass = outerClass;
nameScope = outerScope;
}
}
void visitEnumMembersInScope(EnumDeclaration node) {
node.documentationComment?.accept(this);
node.constants.accept(this);
}
@override
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
_setNodeNameScope(node, nameScope);
super.visitExpressionFunctionBody(node);
}
@override
void visitExtensionDeclaration(ExtensionDeclaration node) {
Scope outerScope = nameScope;
var outerExtension = enclosingExtension;
try {
ExtensionElement element = node.declaredElement!;
enclosingExtension = element;
node.metadata.accept(this);
nameScope = TypeParameterScope(
nameScope,
element.typeParameters,
);
visitExtensionDeclarationInScope(node);
nameScope = ExtensionScope(nameScope, element);
visitExtensionMembersInScope(node);
} finally {
enclosingExtension = outerExtension;
nameScope = outerScope;
}
}
void visitExtensionDeclarationInScope(ExtensionDeclaration node) {
node.name?.accept(this);
node.typeParameters?.accept(this);
node.extendedType.accept(this);
}
void visitExtensionMembersInScope(ExtensionDeclaration node) {
node.documentationComment?.accept(this);
node.members.accept(this);
}
@override
void visitForEachPartsWithDeclaration(ForEachPartsWithDeclaration node) {
//
// We visit the iterator before the loop variable because the loop variable
// cannot be in scope while visiting the iterator.
//
node.iterable.accept(this);
node.loopVariable.accept(this);
}
@override
void visitForElement(ForElement node) {
Scope outerNameScope = nameScope;
try {
nameScope = LocalScope(nameScope);
_setNodeNameScope(node, nameScope);
visitForElementInScope(node);
} finally {
nameScope = outerNameScope;
}
}
/// Visit the given [node] after it's scope has been created. This replaces
/// the normal call to the inherited visit method so that ResolverVisitor can
/// intervene when type propagation is enabled.
void visitForElementInScope(ForElement node) {
// TODO(brianwilkerson) Investigate the possibility of removing the
// visit...InScope methods now that type propagation is no longer done.
node.forLoopParts.accept(this);
node.body.accept(this);
}
@override
void visitFormalParameterList(FormalParameterList node) {
super.visitFormalParameterList(node);
// We finished resolving function signature, now include formal parameters
// scope. Note: we must not do this if the parent is a
// FunctionTypedFormalParameter, because in that case we aren't finished
// resolving the full function signature, just a part of it.
var parent = node.parent;
if (parent is FunctionExpression) {
nameScope = FormalParameterScope(
nameScope,
parent.declaredElement!.parameters,
);
} else if (parent is FunctionTypeAlias) {
var aliasedElement = parent.declaredElement!.aliasedElement;
var functionElement = aliasedElement as GenericFunctionTypeElement;
nameScope = FormalParameterScope(
nameScope,
functionElement.parameters,
);
} else if (parent is MethodDeclaration) {
nameScope = FormalParameterScope(
nameScope,
parent.declaredElement!.parameters,
);
}
}
@override
void visitForStatement(ForStatement node) {
Scope outerNameScope = nameScope;
ImplicitLabelScope outerImplicitScope = _implicitLabelScope;
try {
nameScope = LocalScope(nameScope);
_implicitLabelScope = _implicitLabelScope.nest(node);
_setNodeNameScope(node, nameScope);
visitForStatementInScope(node);
} finally {
nameScope = outerNameScope;
_implicitLabelScope = outerImplicitScope;
}
}
/// Visit the given [node] after it's scope has been created. This replaces
/// the normal call to the inherited visit method so that ResolverVisitor can
/// intervene when type propagation is enabled.
void visitForStatementInScope(ForStatement node) {
// TODO(brianwilkerson) Investigate the possibility of removing the
// visit...InScope methods now that type propagation is no longer done.
node.forLoopParts.accept(this);
visitStatementInScope(node.body);
}
@override
void visitFunctionDeclaration(FunctionDeclaration node) {
node.metadata.accept(this);
Scope outerScope = nameScope;
try {
var element = node.declaredElement!;
nameScope = TypeParameterScope(
nameScope,
element.typeParameters,
);
visitFunctionDeclarationInScope(node);
} finally {
nameScope = outerScope;
}
}
void visitFunctionDeclarationInScope(FunctionDeclaration node) {
// Note: we don't visit metadata because it's not inside the function's type
// parameter scope. It was already visited in [visitFunctionDeclaration].
node.documentationComment?.accept(this);
node.returnType?.accept(this);
node.name.accept(this);
node.functionExpression.accept(this);
}
@override
void visitFunctionExpression(FunctionExpression node) {
if (node.parent is FunctionDeclaration) {
// We have already created a function scope and don't need to do so again.
super.visitFunctionExpression(node);
return;
}
Scope outerScope = nameScope;
try {
ExecutableElement element = node.declaredElement!;
nameScope = FormalParameterScope(
TypeParameterScope(nameScope, element.typeParameters),
element.parameters,
);
super.visitFunctionExpression(node);
} finally {
nameScope = outerScope;
}
}
@override
void visitFunctionTypeAlias(FunctionTypeAlias node) {
node.metadata.accept(this);
Scope outerScope = nameScope;
try {
var element = node.declaredElement!;
nameScope = TypeParameterScope(nameScope, element.typeParameters);
visitFunctionTypeAliasInScope(node);
} finally {
nameScope = outerScope;
}
}
void visitFunctionTypeAliasInScope(FunctionTypeAlias node) {
// Note: we don't visit metadata because it's not inside the function type
// alias's type parameter scope. It was already visited in
// [visitFunctionTypeAlias].
node.documentationComment?.accept(this);
node.returnType?.accept(this);
node.name.accept(this);
node.typeParameters?.accept(this);
node.parameters.accept(this);
}
@override
void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
node.metadata.accept(this);
Scope outerScope = nameScope;
try {
ParameterElement element = node.declaredElement!;
nameScope = TypeParameterScope(
nameScope,
element.typeParameters,
);
visitFunctionTypedFormalParameterInScope(node);
} finally {
nameScope = outerScope;
}
}
void visitFunctionTypedFormalParameterInScope(
FunctionTypedFormalParameter node) {
// Note: we don't visit metadata because it's not inside the function typed
// formal parameter's type parameter scope. It was already visited in
// [visitFunctionTypedFormalParameter].
node.documentationComment?.accept(this);
node.returnType?.accept(this);
node.identifier.accept(this);
node.typeParameters?.accept(this);
node.parameters.accept(this);
}
@override
void visitGenericFunctionType(GenericFunctionType node) {
var type = node.type;
if (type == null) {
// The function type hasn't been resolved yet, so we can't create a scope
// for its parameters.
super.visitGenericFunctionType(node);
return;
}
Scope outerScope = nameScope;
try {
GenericFunctionTypeElement element =
(node as GenericFunctionTypeImpl).declaredElement!;
nameScope = TypeParameterScope(nameScope, element.typeParameters);
super.visitGenericFunctionType(node);
} finally {
nameScope = outerScope;
}
}
@override
void visitGenericTypeAlias(GenericTypeAlias node) {
node.metadata.accept(this);
Scope outerScope = nameScope;
try {
var element = node.declaredElement as TypeAliasElement;
nameScope = TypeParameterScope(nameScope, element.typeParameters);
visitGenericTypeAliasInScope(node);
var aliasedElement = element.aliasedElement;
if (aliasedElement is GenericFunctionTypeElement) {
nameScope = FormalParameterScope(nameScope, aliasedElement.parameters);
visitGenericTypeAliasInFunctionScope(node);
}
} finally {
nameScope = outerScope;
}
}
void visitGenericTypeAliasInFunctionScope(GenericTypeAlias node) {}
void visitGenericTypeAliasInScope(GenericTypeAlias node) {
// Note: we don't visit metadata because it's not inside the generic type
// alias's type parameter scope. It was already visited in
// [visitGenericTypeAlias].
node.documentationComment?.accept(this);
node.name.accept(this);
node.typeParameters?.accept(this);
node.type.accept(this);
}
@override
void visitIfStatement(IfStatement node) {
node.condition.accept(this);
visitStatementInScope(node.thenStatement);
visitStatementInScope(node.elseStatement);
}
@override
void visitLabeledStatement(LabeledStatement node) {
var outerScope = _addScopesFor(node.labels, node.unlabeled);
try {
super.visitLabeledStatement(node);
} finally {
labelScope = outerScope;
}
}
@override
void visitMethodDeclaration(MethodDeclaration node) {
node.metadata.accept(this);
Scope outerScope = nameScope;
try {
ExecutableElement element = node.declaredElement!;
nameScope = TypeParameterScope(
nameScope,
element.typeParameters,
);
visitMethodDeclarationInScope(node);
} finally {
nameScope = outerScope;
}
}
void visitMethodDeclarationInScope(MethodDeclaration node) {
// Note: we don't visit metadata because it's not inside the method's type
// parameter scope. It was already visited in [visitMethodDeclaration].
node.documentationComment?.accept(this);
node.returnType?.accept(this);
node.name.accept(this);
node.typeParameters?.accept(this);
node.parameters?.accept(this);
node.body.accept(this);
}
@override
void visitMixinDeclaration(MixinDeclaration node) {
Scope outerScope = nameScope;
var outerClass = enclosingClass;
try {
ClassElement element = node.declaredElement!;
enclosingClass = element;
node.metadata.accept(this);
nameScope = TypeParameterScope(nameScope, element.typeParameters);
visitMixinDeclarationInScope(node);
nameScope = ClassScope(nameScope, element);
visitMixinMembersInScope(node);
} finally {
nameScope = outerScope;
enclosingClass = outerClass;
}
}
void visitMixinDeclarationInScope(MixinDeclaration node) {
node.name.accept(this);
node.typeParameters?.accept(this);
node.onClause?.accept(this);
node.implementsClause?.accept(this);
}
void visitMixinMembersInScope(MixinDeclaration node) {
node.documentationComment?.accept(this);
node.members.accept(this);
}
/// Visit the given statement after it's scope has been created. This is used
/// by ResolverVisitor to correctly visit the 'then' and 'else' statements of
/// an 'if' statement.
///
/// @param node the statement to be visited
void visitStatementInScope(Statement? node) {
if (node is Block) {
// Don't create a scope around a block because the block will create it's
// own scope.
visitBlock(node);
} else if (node != null) {
Scope outerNameScope = nameScope;
try {
nameScope = LocalScope(nameScope);
node.accept(this);
} finally {
nameScope = outerNameScope;
}
}
}
@override
void visitSwitchCase(SwitchCase node) {
node.expression.accept(this);
_withDeclaredLocals(node, node.statements, () {
node.statements.accept(this);
});
}
@override
void visitSwitchDefault(SwitchDefault node) {
_withDeclaredLocals(node, node.statements, () {
node.statements.accept(this);
});
}
@override
void visitSwitchStatement(SwitchStatement node) {
var outerScope = labelScope;
ImplicitLabelScope outerImplicitScope = _implicitLabelScope;
try {
_implicitLabelScope = _implicitLabelScope.nest(node);
for (SwitchMember member in node.members) {
for (Label label in member.labels) {