blob: e455354b4d13a55ec4c884ad7fdfcd9f1b44f02c [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 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis_operations.dart'
as shared;
import 'package:_fe_analyzer_shared/src/type_inference/null_shorting.dart';
import 'package:_fe_analyzer_shared/src/type_inference/type_analysis_result.dart'
as shared;
import 'package:_fe_analyzer_shared/src/type_inference/type_analysis_result.dart';
import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart'
as shared;
import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart';
import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer_operations.dart'
as shared;
import 'package:_fe_analyzer_shared/src/types/shared_type.dart';
import 'package:analyzer/dart/analysis/analysis_options.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/scope.dart';
import 'package:analyzer/dart/element/type.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/source/source.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/extensions.dart';
import 'package:analyzer/src/dart/element/generic_inferrer.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/scope.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_constraint_gatherer.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_schema.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/constructor_reference_resolver.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/instance_creation_expression_resolver.dart';
import 'package:analyzer/src/dart/resolver/invocation_inference_helper.dart';
import 'package:analyzer/src/dart/resolver/invocation_inferrer.dart';
import 'package:analyzer/src/dart/resolver/lexical_lookup.dart';
import 'package:analyzer/src/dart/resolver/list_pattern_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/record_literal_resolver.dart';
import 'package:analyzer/src/dart/resolver/scope.dart';
import 'package:analyzer/src/dart/resolver/shared_type_analyzer.dart';
import 'package:analyzer/src/dart/resolver/simple_identifier_resolver.dart';
import 'package:analyzer/src/dart/resolver/this_lookup.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/base_or_final_type_verifier.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/inference_error.dart';
import 'package:analyzer/src/error/nullable_dereference_verifier.dart';
import 'package:analyzer/src/error/super_formal_parameters_verifier.dart';
import 'package:analyzer/src/generated/element_resolver.dart';
import 'package:analyzer/src/generated/error_detection_helpers.dart';
import 'package:analyzer/src/generated/inference_log.dart';
import 'package:analyzer/src/generated/static_type_analyzer.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/generated/variable_type_provider.dart';
import 'package:analyzer/src/util/ast_data_extractor.dart';
import 'package:analyzer/src/utilities/extensions/element.dart';
import 'package:analyzer/src/utilities/extensions/object.dart';
/// Function determining which source files should have inference logging
/// enabled.
///
/// By default, no files have inference logging enabled.
bool Function(Source) inferenceLoggingPredicate = (_) => false;
typedef SharedMatchContext =
shared.MatchContext<
AstNodeImpl,
ExpressionImpl,
DartPatternImpl,
SharedTypeView,
PromotableElementImpl2
>;
typedef SharedPatternField =
shared.RecordPatternField<PatternFieldImpl, DartPatternImpl>;
/// A function which returns [NonPromotionReason]s that various types are not
/// promoted.
typedef WhyNotPromotedGetter =
Map<SharedTypeView, NonPromotionReason> Function();
/// The context shared between different units of the same library.
final class LibraryResolutionContext {
/// The declarations for [VariableFragment]s.
final Map<VariableFragment, VariableDeclaration> _variableNodes =
Map.identity();
}
/// Instances of the class `ResolverVisitor` are used to resolve the nodes
/// within a single compilation unit.
class ResolverVisitor extends ThrowingAstVisitor<void>
with
ErrorDetectionHelpers,
TypeAnalyzer<
AstNodeImpl,
StatementImpl,
ExpressionImpl,
PromotableElementImpl2,
DartPatternImpl,
void,
InterfaceTypeImpl,
InterfaceElementImpl2
>,
// TODO(paulberry): not yet used.
NullShortingMixin<
Null,
ExpressionImpl,
PromotableElementImpl2,
SharedTypeView
> {
/// Debug-only: if `true`, manipulations of [_rewriteStack] performed by
/// [popRewrite], [pushRewrite], and [replaceExpression] will be printed.
static const bool _debugRewriteStack = false;
/// The element for the library containing the compilation unit being visited.
final LibraryElementImpl definingLibrary;
/// The library fragment being visited.
final LibraryFragmentImpl libraryFragment;
/// The context shared between different units of the same library.
final LibraryResolutionContext libraryResolutionContext;
/// If the resolver visitor is visiting a switch statement and patterns
/// support is disabled, the tracker that determines whether the switch is
/// exhaustive.
SwitchExhaustiveness? legacySwitchExhaustiveness;
@override
final TypeAnalyzerOptions typeAnalyzerOptions;
@override
late final SharedTypeAnalyzerErrors errors = SharedTypeAnalyzerErrors(
errorReporter,
);
/// 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;
@override
final ErrorReporter errorReporter;
/// The analysis options used by this resolver.
final AnalysisOptions analysisOptions;
/// The class containing the AST nodes being visited,
/// or `null` if we are not in the scope of a class.
InterfaceElementImpl2? enclosingClass;
/// The element representing the extension containing the AST nodes being
/// visited, or `null` if we are not in the scope of an extension.
ExtensionElementImpl2? enclosingExtension;
/// The element representing the function containing the current node, or
/// `null` if the current node is not contained in a function.
ExecutableElementImpl2? enclosingFunction;
/// The manager for the inheritance mappings.
@override
final InheritanceManager3 inheritance;
/// The feature set that is enabled for the current unit.
final FeatureSet _featureSet;
/// Helper for checking that subtypes of a base or final type must be base,
/// final, or sealed.
late final BaseOrFinalTypeVerifier baseOrFinalTypeVerifier;
/// Helper for checking expression that should have the `bool` type.
late final BoolExpressionVerifier boolExpressionVerifier =
BoolExpressionVerifier(
resolver: this,
errorReporter: errorReporter,
nullableDereferenceVerifier: nullableDereferenceVerifier,
);
/// Helper for checking potentially nullable dereferences.
late final NullableDereferenceVerifier nullableDereferenceVerifier =
NullableDereferenceVerifier(
typeSystem: typeSystem,
errorReporter: errorReporter,
resolver: this,
);
/// Helper for extension method resolution.
late final ExtensionMemberResolver extensionResolver =
ExtensionMemberResolver(this);
/// Helper for resolving properties on types.
late final TypePropertyResolver typePropertyResolver = TypePropertyResolver(
this,
);
/// Helper for resolving [ListLiteral] and [SetOrMapLiteral].
late final TypedLiteralResolver _typedLiteralResolver = TypedLiteralResolver(
this,
typeSystem,
typeProvider,
analysisOptions,
);
late final AssignmentExpressionResolver _assignmentExpressionResolver =
AssignmentExpressionResolver(resolver: this);
late final BinaryExpressionResolver _binaryExpressionResolver;
late final ConstructorReferenceResolver _constructorReferenceResolver =
ConstructorReferenceResolver(this);
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;
/// Inference context information for the current function body, if the
/// current node is inside a function body.
BodyInferenceContext? _bodyContext;
/// If a class, or mixin, is being resolved, the type of the class.
/// Otherwise `null`.
TypeImpl? _thisType;
final FlowAnalysisHelper flowAnalysis;
/// 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];
late final FunctionReferenceResolver _functionReferenceResolver;
late final InstanceCreationExpressionResolver
_instanceCreationExpressionResolver = InstanceCreationExpressionResolver(
this,
);
late final SimpleIdentifierResolver _simpleIdentifierResolver =
SimpleIdentifierResolver(this);
late final PropertyElementResolver _propertyElementResolver =
PropertyElementResolver(this);
late final RecordLiteralResolver _recordLiteralResolver =
RecordLiteralResolver(resolver: this);
late final AnnotationResolver _annotationResolver = AnnotationResolver(this);
late final ListPatternResolver listPatternResolver = ListPatternResolver(
this,
);
final bool genericMetadataIsEnabled;
final bool inferenceUsingBoundsIsEnabled;
/// Stack for obtaining rewritten expressions. Prior to visiting an
/// expression, a caller may push the expression on this stack; if
/// [replaceExpression] is later called, it will update the top of the stack
/// to point to the rewritten expression.
///
/// The stack sometimes contains `null`s. These account for situations where
/// it's necessary to push a value onto the stack to balance a later pop, but
/// there is no suitable expression to push.
final List<ExpressionImpl?> _rewriteStack = [];
/// Debug-only expando mapping AST nodes to the nodes they were replaced with
/// by [replaceExpression]. This is used by [dispatchExpression] as a sanity
/// check to make sure the expression it pops off the [_rewriteStack] is
/// actually correct.
late final Expando<AstNode> _replacements = Expando();
/// 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.
///
// TODO(paulberry): make [featureSet] a required parameter (this will be a
// breaking change).
ResolverVisitor(
InheritanceManager3 inheritanceManager,
LibraryElementImpl definingLibrary,
LibraryResolutionContext libraryResolutionContext,
Source source,
TypeProvider typeProvider,
AnalysisErrorListener errorListener, {
required LibraryFragmentImpl libraryFragment,
required FeatureSet featureSet,
required AnalysisOptions analysisOptions,
required FlowAnalysisHelper flowAnalysisHelper,
required TypeAnalyzerOptions typeAnalyzerOptions,
}) : this._(
inheritanceManager,
definingLibrary,
libraryResolutionContext,
source,
definingLibrary.typeSystem,
typeProvider as TypeProviderImpl,
ErrorReporter(errorListener, source),
featureSet,
analysisOptions,
flowAnalysisHelper,
libraryFragment: libraryFragment,
typeAnalyzerOptions: typeAnalyzerOptions,
);
ResolverVisitor._(
this.inheritance,
this.definingLibrary,
this.libraryResolutionContext,
this.source,
this.typeSystem,
this.typeProvider,
this.errorReporter,
FeatureSet featureSet,
this.analysisOptions,
this.flowAnalysis, {
required this.libraryFragment,
required this.typeAnalyzerOptions,
}) : _featureSet = featureSet,
genericMetadataIsEnabled = definingLibrary.featureSet.isEnabled(
Feature.generic_metadata,
),
inferenceUsingBoundsIsEnabled = definingLibrary.featureSet.isEnabled(
Feature.inference_using_bounds,
),
baseOrFinalTypeVerifier = BaseOrFinalTypeVerifier(
definingLibrary: definingLibrary,
errorReporter: errorReporter,
) {
inferenceHelper = InvocationInferenceHelper(
resolver: this,
errorReporter: errorReporter,
typeSystem: typeSystem,
dataForTesting:
flowAnalysis.dataForTesting != null
? TypeConstraintGenerationDataForTesting()
: null,
);
_binaryExpressionResolver = BinaryExpressionResolver(resolver: this);
_functionExpressionInvocationResolver =
FunctionExpressionInvocationResolver(resolver: this);
_functionExpressionResolver = FunctionExpressionResolver(resolver: this);
_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);
typeAnalyzer = StaticTypeAnalyzer(this);
_functionReferenceResolver = FunctionReferenceResolver(this);
}
/// Inference context information for the current function body, if the
/// current node is inside a function body.
BodyInferenceContext? get bodyContext => _bodyContext;
@override
FlowAnalysis<
AstNodeImpl,
StatementImpl,
ExpressionImpl,
PromotableElementImpl2,
SharedTypeView
>
get flow => flowAnalysis.flow!;
bool get isConstructorTearoffsEnabled =>
_featureSet.isEnabled(Feature.constructor_tearoffs);
bool get isInferenceUpdate1Enabled =>
_featureSet.isEnabled(Feature.inference_update_1);
/// Return the object providing promoted or declared types of variables.
LocalVariableTypeProvider get localVariableTypeProvider {
return flowAnalysis.localVariableTypeProvider;
}
@override
shared.TypeAnalyzerOperations<
PromotableElementImpl2,
InterfaceTypeImpl,
InterfaceElementImpl2
>
get operations => flowAnalysis.typeOperations;
/// Gets the current depth of the [_rewriteStack]. This may be used in
/// assertions to verify that pushes and pops are properly balanced.
int get rewriteStackDepth => _rewriteStack.length;
@override
bool get strictCasts => analysisOptions.strictCasts;
/// 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`.
TypeImpl? get thisType {
return _thisType;
}
@override
SharedTypeView analyzeExpression(
ExpressionImpl node,
SharedTypeSchemaView schema, {
bool continueNullShorting = false,
}) {
inferenceLogWriter?.setExpressionVisitCodePath(
node,
ExpressionVisitCodePath.analyzeExpression,
);
return super.analyzeExpression(
node,
schema,
continueNullShorting: continueNullShorting,
);
}
List<SharedPatternField> buildSharedPatternFields(
List<PatternFieldImpl> fields, {
required bool mustBeNamed,
}) {
return fields.map((field) {
Token? nameToken;
var fieldName = field.name;
if (fieldName != null) {
nameToken = fieldName.name;
if (nameToken == null) {
var variablePattern = field.pattern.variablePattern;
if (variablePattern != null) {
variablePattern.fieldNameWithImplicitName = fieldName;
nameToken = variablePattern.name;
} else {
errorReporter.atNode(
field,
CompileTimeErrorCode.MISSING_NAMED_PATTERN_FIELD_NAME,
);
}
}
} else if (mustBeNamed) {
errorReporter.atNode(
field,
CompileTimeErrorCode.POSITIONAL_FIELD_IN_OBJECT_PATTERN,
);
}
return shared.RecordPatternField(
node: field,
name: nameToken?.lexeme,
pattern: field.pattern,
);
}).toList();
}
/// Verify that the arguments in the given [argumentList] can be assigned to
/// their corresponding parameters.
///
/// See [CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE].
void checkForArgumentTypesNotAssignableInList(
ArgumentListImpl argumentList,
List<WhyNotPromotedGetter> whyNotPromotedArguments,
) {
var arguments = argumentList.arguments;
for (int i = 0; i < arguments.length; i++) {
checkForArgumentTypeNotAssignableForArgument(
arguments[i],
whyNotPromoted:
flowAnalysis.flow == null ? null : whyNotPromotedArguments[i],
);
}
}
void checkForBodyMayCompleteNormally({
required FunctionBodyImpl body,
required SyntacticEntity errorNode,
}) {
var bodyContext = body.bodyContext;
if (bodyContext == null) {
return;
}
if (!flowAnalysis.flow!.isReachable) {
bodyContext.mayCompleteNormally = false;
return;
}
var returnType = bodyContext.contextType;
if (returnType == null) {
if (errorNode is BlockFunctionBody) {
_checkForFutureCatchErrorOnError(errorNode);
}
return;
}
if (body is BlockFunctionBody) {
if (body.isGenerator) {
return;
}
if (body.isAsynchronous) {
// Check whether the return type is legal. If not, return rather than
// reporting a second error.
// This is the same check as [ReturnTypeVerifier._isLegalReturnType].
// TODO(srawlins): When this check is moved into the resolution stage,
// use the result of that check to determine whether this check should
// be done.
var lowerBound = typeProvider.futureElement2.instantiateImpl(
typeArguments: fixedTypeList(NeverTypeImpl.instance),
nullabilitySuffix: NullabilitySuffix.none,
);
var imposedType = bodyContext.imposedType;
if (imposedType != null &&
!typeSystem.isSubtypeOf(lowerBound, imposedType)) {
// [imposedType] is an illegal return type for an asynchronous
// non-generator function; do not report an additional error here.
return;
}
}
DiagnosticCode diagnosticCode;
if (typeSystem.isPotentiallyNonNullable(returnType)) {
diagnosticCode = CompileTimeErrorCode.BODY_MIGHT_COMPLETE_NORMALLY;
} else {
var returnTypeBase = typeSystem.futureOrBase(returnType);
if (returnTypeBase is DynamicType ||
returnTypeBase is InvalidType ||
returnTypeBase is UnknownInferredType ||
returnTypeBase is VoidType ||
returnTypeBase.isDartCoreNull) {
return;
} else {
diagnosticCode = WarningCode.BODY_MIGHT_COMPLETE_NORMALLY_NULLABLE;
}
}
if (errorNode is ConstructorDeclaration) {
errorReporter.atConstructorDeclaration(
errorNode,
diagnosticCode,
arguments: [returnType],
);
} else if (errorNode is BlockFunctionBody) {
errorReporter.atToken(
errorNode.block.leftBracket,
diagnosticCode,
arguments: [returnType],
);
} else if (errorNode is Token) {
errorReporter.atToken(
errorNode,
diagnosticCode,
arguments: [returnType],
);
}
}
}
/// The client of the resolver should call this method after asking the
/// resolver to visit an AST node. This performs assertions to make sure that
/// temporary resolver state has been properly cleaned up.
void checkIdle() {
assert(_rewriteStack.isEmpty);
inferenceLogWriter?.assertIdle();
}
/// Reports an error if the [pattern] with the [requiredType] cannot
/// match the [DartPatternImpl.matchedValueType].
void checkPatternNeverMatchesValueType({
required SharedMatchContext context,
required DartPatternImpl pattern,
required TypeImpl requiredType,
required TypeImpl matchedValueType,
}) {
if (context.irrefutableContext == null) {
if (!typeSystem.canBeSubtypeOf(matchedValueType, requiredType)) {
AstNodeImpl? errorNode;
if (pattern is CastPatternImpl) {
errorNode = pattern.type;
} else if (pattern is DeclaredVariablePatternImpl) {
errorNode = pattern.type;
} else if (pattern is ObjectPatternImpl) {
errorNode = pattern.type;
} else if (pattern is WildcardPatternImpl) {
errorNode = pattern.type;
}
errorNode ??= pattern;
errorReporter.atNode(
errorNode,
WarningCode.PATTERN_NEVER_MATCHES_VALUE_TYPE,
arguments: [matchedValueType, requiredType],
);
}
}
}
void checkReadOfNotAssignedLocalVariable(
SimpleIdentifier node,
Element? element,
) {
if (flowAnalysis.flow == null) {
return;
}
if (!node.inGetterContext()) {
return;
}
if (element is PromotableElementImpl2) {
var assigned = flowAnalysis.isDefinitelyAssigned(node, element);
var unassigned = flowAnalysis.isDefinitelyUnassigned(node, element);
if (element.isLate) {
if (unassigned) {
errorReporter.atNode(
node,
CompileTimeErrorCode.DEFINITELY_UNASSIGNED_LATE_LOCAL_VARIABLE,
arguments: [node.name],
);
}
return;
}
if (!assigned) {
if (element.isFinal) {
errorReporter.atNode(
node,
CompileTimeErrorCode.READ_POTENTIALLY_UNASSIGNED_FINAL,
arguments: [node.name],
);
return;
}
if (typeSystem.isPotentiallyNonNullable(element.type)) {
errorReporter.atNode(
node,
CompileTimeErrorCode
.NOT_ASSIGNED_POTENTIALLY_NON_NULLABLE_LOCAL_VARIABLE,
arguments: [node.name],
);
return;
}
}
}
}
void checkUnreachableNode(AstNode node) {
nullSafetyDeadCodeVerifier.visitNode(node);
}
@override
List<DiagnosticMessage> computeWhyNotPromotedMessages(
SyntacticEntity errorEntity,
Map<SharedTypeView, NonPromotionReason>? whyNotPromoted,
) {
List<DiagnosticMessage> messages = [];
if (whyNotPromoted != null) {
for (var entry in whyNotPromoted.entries) {
var whyNotPromotedVisitor = _WhyNotPromotedVisitor(
source,
errorEntity,
flowAnalysis.dataForTesting,
);
if (typeSystem.isPotentiallyNullable(
// TODO(paulberry): make this type argument unnecessary by changing
// the parameter of `TypeSystemImpl.isPotentiallyNullable` to
// (covariant) `TypeImpl`.
entry.key.unwrapTypeView<TypeImpl>(),
)) {
continue;
}
messages = entry.value.accept(whyNotPromotedVisitor);
// `messages` will be passed to the ErrorReporter, which might add
// additional entries. So make sure that it's not a `const []`.
assert(_isModifiableList(messages));
if (messages.isNotEmpty) {
if (flowAnalysis.dataForTesting != null) {
var nonPromotionReasonText = entry.value.shortName;
var args = <String>[];
var propertyReference = whyNotPromotedVisitor.propertyReference;
if (propertyReference != null) {
var id = computeMemberId(propertyReference);
args.add('target: $id');
}
if (args.isNotEmpty) {
nonPromotionReasonText += '(${args.join(', ')})';
}
flowAnalysis.dataForTesting!.nonPromotionReasons[errorEntity] =
nonPromotionReasonText;
}
}
break;
}
}
return messages;
}
@override
void dispatchCollectionElement(
covariant CollectionElementImpl element,
covariant CollectionLiteralContext? context,
) {
element.resolveElement(this, context);
popRewrite();
}
@override
ExpressionTypeAnalysisResult dispatchExpression(
covariant ExpressionImpl expression,
SharedTypeSchemaView context,
) {
int? stackDepth;
assert(() {
stackDepth = rewriteStackDepth;
return true;
}());
// TODO(paulberry): implement null shorting
// Stack: ()
pushRewrite(expression);
// Stack: (Expression)
expression.resolveExpression(this, context.unwrapTypeSchemaView());
inferenceLogWriter?.assertExpressionWasRecorded(expression);
assert(rewriteStackDepth == stackDepth! + 1);
var replacementExpression = peekRewrite()!;
assert(
identical(_replacements[expression] ?? expression, replacementExpression),
);
var staticType = replacementExpression.staticType;
if (staticType == null) {
var shouldHaveType = true;
if (replacementExpression is ExtensionOverride) {
shouldHaveType = false;
} else if (replacementExpression is IdentifierImpl) {
var element = replacementExpression.element;
if (element is ExtensionElement ||
element is InterfaceElement ||
element is PrefixElement ||
element is TypeAliasElement) {
shouldHaveType = false;
}
}
if (shouldHaveType) {
assert(
false,
'No static type for: '
'(${replacementExpression.runtimeType}) $replacementExpression',
);
}
staticType = operations.unknownType.unwrapTypeSchemaView();
}
return ExpressionTypeAnalysisResult(type: SharedTypeView(staticType));
}
@override
PatternResult dispatchPattern(SharedMatchContext context, AstNodeImpl node) {
shared.PatternResult analysisResult;
if (node is DartPatternImpl) {
analysisResult = node.resolvePattern(this, context);
node.matchedValueType = analysisResult.matchedValueType.unwrapTypeView();
} else {
// This can occur inside conventional switch statements, since
// [SwitchCase] points directly to an [Expression] rather than to a
// [ConstantPattern]. So we mimic what
// [ConstantPatternImpl.resolvePattern] would do.
analysisResult = analyzeConstantPattern(
context,
node,
node as ExpressionImpl,
);
// Stack: (Expression)
popRewrite();
// Stack: ()
}
return analysisResult;
}
@override
SharedTypeSchemaView dispatchPatternSchema(covariant DartPatternImpl node) {
return SharedTypeSchemaView(node.computePatternSchema(this));
}
@override
void dispatchStatement(Statement statement) {
statement.accept(this);
}
@override
SharedTypeView downwardInferObjectPatternRequiredType({
required SharedTypeView matchedType,
required covariant ObjectPatternImpl pattern,
}) {
var typeNode = pattern.type;
if (typeNode.typeArguments == null) {
var typeNameElement = typeNode.element2;
if (typeNameElement is InterfaceElementImpl2) {
var typeParameters = typeNameElement.typeParameters2;
if (typeParameters.isNotEmpty) {
var typeArguments = _inferTypeArguments(
typeParameters: typeParameters,
errorNode: typeNode,
declaredType: typeNameElement.thisType,
contextType: matchedType.unwrapTypeView(),
nodeForTesting: pattern,
);
return SharedTypeView(
typeNode.type = typeNameElement.instantiateImpl(
typeArguments: typeArguments,
nullabilitySuffix: NullabilitySuffix.none,
),
);
}
} else if (typeNameElement is TypeAliasElementImpl2) {
var typeParameters = typeNameElement.typeParameters2;
if (typeParameters.isNotEmpty) {
var typeArguments = _inferTypeArguments(
typeParameters: typeParameters,
errorNode: typeNode,
declaredType: typeNameElement.aliasedType,
contextType: matchedType.unwrapTypeView(),
nodeForTesting: pattern,
);
return SharedTypeView(
typeNode.type = typeNameElement.instantiateImpl(
typeArguments: typeArguments,
nullabilitySuffix: NullabilitySuffix.none,
),
);
}
}
}
return SharedTypeView(typeNode.typeOrThrow);
}
@override
void finishExpressionCase(
covariant SwitchExpressionImpl node,
int caseIndex,
) {
var case_ = node.cases[caseIndex];
case_.expression = popRewrite()!;
nullSafetyDeadCodeVerifier.flowEnd(case_);
}
@override
void finishJoinedPatternVariable(
covariant JoinPatternVariableElementImpl2 variable, {
required JoinedPatternVariableLocation location,
required shared.JoinedPatternVariableInconsistency inconsistency,
required bool isFinal,
required SharedTypeView type,
}) {
variable.inconsistency = variable.inconsistency.maxWith(inconsistency);
variable.isFinal = isFinal;
variable.type = type.unwrapTypeView();
if (location == JoinedPatternVariableLocation.sharedCaseScope) {
for (var reference in variable.references) {
if (variable.inconsistency ==
shared.JoinedPatternVariableInconsistency.sharedCaseAbsent) {
errorReporter.atNode(
reference,
CompileTimeErrorCode
.PATTERN_VARIABLE_SHARED_CASE_SCOPE_NOT_ALL_CASES,
arguments: [variable.name3!],
);
} else if (variable.inconsistency ==
shared.JoinedPatternVariableInconsistency.sharedCaseHasLabel) {
errorReporter.atNode(
reference,
CompileTimeErrorCode.PATTERN_VARIABLE_SHARED_CASE_SCOPE_HAS_LABEL,
arguments: [variable.name3!],
);
} else if (variable.inconsistency ==
shared.JoinedPatternVariableInconsistency.differentFinalityOrType) {
errorReporter.atNode(
reference,
CompileTimeErrorCode
.PATTERN_VARIABLE_SHARED_CASE_SCOPE_DIFFERENT_FINALITY_OR_TYPE,
arguments: [variable.name3!],
);
}
}
}
}
@override
shared.MapPatternEntry<ExpressionImpl, DartPatternImpl>? getMapPatternEntry(
covariant MapPatternElementImpl element,
) {
if (element is MapPatternEntryImpl) {
return shared.MapPatternEntry(key: element.key, value: element.value);
}
return null;
}
@override
DartPatternImpl? getRestPatternElementPattern(
covariant RestPatternElementImpl element,
) {
return element.pattern;
}
@override
SwitchExpressionMemberInfo<
AstNodeImpl,
ExpressionImpl,
PromotableElementImpl2
>
getSwitchExpressionMemberInfo(
covariant SwitchExpressionImpl node,
int index,
) {
var case_ = node.cases[index];
var guardedPattern = case_.guardedPattern;
return SwitchExpressionMemberInfo(
head: CaseHeadOrDefaultInfo(
pattern: guardedPattern.pattern,
guard: guardedPattern.whenClause?.expression,
variables: guardedPattern.variables,
),
expression: case_.expression,
);
}
@override
SwitchStatementMemberInfo<
AstNodeImpl,
StatementImpl,
ExpressionImpl,
PromotableElementImpl2
>
getSwitchStatementMemberInfo(covariant SwitchStatementImpl node, int index) {
CaseHeadOrDefaultInfo<AstNodeImpl, ExpressionImpl, PromotableElementImpl2>
ofMember(SwitchMemberImpl member) {
if (member is SwitchCaseImpl) {
return CaseHeadOrDefaultInfo(pattern: member.expression, variables: {});
} else if (member is SwitchPatternCaseImpl) {
var guardedPattern = member.guardedPattern;
return CaseHeadOrDefaultInfo(
pattern: guardedPattern.pattern,
variables: guardedPattern.variables,
guard: guardedPattern.whenClause?.expression,
);
} else {
return CaseHeadOrDefaultInfo(pattern: null, variables: {});
}
}
var group = node.memberGroups[index];
return SwitchStatementMemberInfo(
heads: group.members.map(ofMember).toList(),
body: group.statements,
variables: group.variables,
hasLabels: group.hasLabels,
);
}
@override
void handle_ifElement_conditionEnd(covariant IfElementImpl node) {
// Stack: (Expression condition)
var condition = popRewrite()!;
var whyNotPromoted = flowAnalysis.flow?.whyNotPromoted(condition);
boolExpressionVerifier.checkForNonBoolCondition(
condition,
whyNotPromoted: whyNotPromoted,
);
}
@override
void handle_ifElement_elseEnd(
covariant IfElementImpl node,
covariant CollectionElementImpl ifFalse,
) {
nullSafetyDeadCodeVerifier.flowEnd(ifFalse);
}
@override
void handle_ifElement_thenEnd(
covariant IfElementImpl node,
covariant CollectionElementImpl ifTrue,
) {
nullSafetyDeadCodeVerifier.flowEnd(ifTrue);
}
@override
void handle_ifStatement_conditionEnd(Statement node) {
// Stack: (Expression condition)
var condition = popRewrite()!;
var whyNotPromoted = flowAnalysis.flow?.whyNotPromoted(condition);
boolExpressionVerifier.checkForNonBoolCondition(
condition,
whyNotPromoted: whyNotPromoted,
);
}
@override
void handle_ifStatement_elseEnd(Statement node, Statement ifFalse) {
nullSafetyDeadCodeVerifier.flowEnd(ifFalse);
}
@override
void handle_ifStatement_thenEnd(Statement node, Statement ifTrue) {
nullSafetyDeadCodeVerifier.flowEnd(ifTrue);
}
@override
void handle_logicalOrPattern_afterLhs(covariant LogicalOrPatternImpl node) {
checkUnreachableNode(node.rightOperand);
}
@override
void handleCase_afterCaseHeads(
AstNode node,
int caseIndex,
Iterable<PromotableElement> variables,
) {}
@override
void handleCaseHead(
covariant AstNodeImpl node, {
required int caseIndex,
required int subIndex,
}) {
// Stack: (Expression)
popRewrite(); // "when" expression
// Stack: ()
if (node is SwitchStatementImpl) {
var group = node.memberGroups[caseIndex];
legacySwitchExhaustiveness?.visitSwitchMember(group);
nullSafetyDeadCodeVerifier.flowEnd(group.members[subIndex]);
} else if (node is SwitchExpressionImpl) {
legacySwitchExhaustiveness?.visitSwitchExpressionCase(
node.cases[caseIndex],
);
}
}
@override
void handleDefault(
covariant SwitchStatementImpl node, {
required int caseIndex,
required int subIndex,
}) {
var group = node.memberGroups[caseIndex];
legacySwitchExhaustiveness?.visitSwitchMember(group);
nullSafetyDeadCodeVerifier.flowEnd(group.members[subIndex]);
}
@override
void handleListPatternRestElement(
DartPattern container,
covariant RestPatternElementImpl restElement,
) {}
@override
void handleMapPatternEntry(
DartPattern container,
covariant MapPatternEntryImpl entry,
SharedTypeView keyType,
) {
entry.key = popRewrite()!;
}
@override
void handleMapPatternRestElement(
DartPattern container,
covariant RestPatternElementImpl restElement,
) {}
@override
void handleMergedStatementCase(
covariant SwitchStatementImpl node, {
required int caseIndex,
required bool isTerminating,
}) {
nullSafetyDeadCodeVerifier.flowEnd(
node.memberGroups[caseIndex].members.last,
);
}
@override
void handleNoCollectionElement(AstNode node) {}
@override
void handleNoGuard(AstNode node, int caseIndex) {
// Stack: ()
// We can push `null` here because there is no actual expression associated
// with the lack of a guard, so there's nothing that will need rewriting.
pushRewrite(null);
// Stack: (Expression)
}
@override
void handleNoStatement(Statement node) {}
@override
void handleSwitchBeforeAlternative(
covariant AstNodeImpl node, {
required int caseIndex,
required int subIndex,
}) {
if (node is SwitchExpressionImpl) {
var case_ = node.cases[caseIndex];
checkUnreachableNode(case_);
} else if (node is SwitchStatementImpl) {
var member = node.memberGroups[caseIndex].members[subIndex];
checkUnreachableNode(member);
}
}
@override
void handleSwitchScrutinee(SharedTypeView type) {
if (!typeAnalyzerOptions.patternsEnabled) {
legacySwitchExhaustiveness = SwitchExhaustiveness(type.unwrapTypeView());
}
}
/// If generic function instantiation should be performed on `expression`,
/// inserts a [FunctionReference] node which wraps [expression].
///
/// If an [FunctionReference] is inserted, returns it; otherwise, returns
/// [expression].
ExpressionImpl insertGenericFunctionInstantiation(
Expression expression, {
required TypeImpl contextType,
}) {
expression as ExpressionImpl;
if (!isConstructorTearoffsEnabled) {
// Temporarily, only create [ImplicitCallReference] nodes under the
// 'constructor-tearoffs' feature.
// TODO(srawlins): When we are ready to make a breaking change release to
// the analyzer package, remove this exception.
return expression;
}
// Don't rewrite function declarations.
if (expression.parent is FunctionDeclaration) {
return expression;
}
var staticType = expression.staticType;
if (staticType is! FunctionTypeImpl || staticType.typeFormals.isEmpty) {
return expression;
}
var context = typeSystem.flatten(contextType);
if (context is! FunctionTypeImpl || context.typeFormals.isNotEmpty) {
return expression;
}
var typeArgumentTypes = typeSystem.inferFunctionTypeInstantiation(
context,
staticType,
errorReporter: errorReporter,
errorNode: expression,
// If the constructor-tearoffs feature is enabled, then so is
// generic-metadata.
genericMetadataIsEnabled: true,
inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled,
strictInference: analysisOptions.strictInference,
strictCasts: analysisOptions.strictCasts,
typeSystemOperations: flowAnalysis.typeOperations,
dataForTesting: inferenceHelper.dataForTesting,
nodeForTesting: expression,
);
if (typeArgumentTypes.isNotEmpty) {
staticType = staticType.instantiate(typeArgumentTypes);
}
var parent = expression.parent;
var genericFunctionInstantiation = FunctionReferenceImpl(
function: expression,
typeArguments: null,
);
replaceExpression(expression, genericFunctionInstantiation, parent: parent);
genericFunctionInstantiation.typeArgumentTypes = typeArgumentTypes;
genericFunctionInstantiation.setPseudoExpressionStaticType(staticType);
return genericFunctionInstantiation;
}
@override
bool isDotShorthand(ExpressionImpl node) {
if (node is DotShorthandMixin) {
return node.isDotShorthand;
}
return false;
}
@override
bool isLegacySwitchExhaustive(AstNode node, SharedTypeView expressionType) =>
legacySwitchExhaustiveness!.isExhaustive;
@override
bool isRestPatternElement(AstNode node) {
return node is RestPatternElementImpl;
}
@override
bool isVariablePattern(AstNode pattern) => pattern is DeclaredVariablePattern;
/// If we reached a null-shorting termination, and the [node] has null
/// shorting, make the type of the [node] nullable.
///
/// [node] should be the original expression node (before resolution). If the
/// resolution process rewrote [node] to some other expression, that
/// expression should be passed in as [rewrittenExpression].
void nullShortingTermination(
ExpressionImpl node, {
ExpressionImpl? rewrittenExpression,
}) {
// Verify that `rewrittenExpression` properly reflects any rewrites that
// were performed on `node`.
assert(identical(rewrittenExpression ?? node, _replacements[node] ?? node));
if (identical(_unfinishedNullShorts.last, node)) {
do {
_unfinishedNullShorts.removeLast();
flowAnalysis.flow!.nullAwareAccess_end();
nullSafetyDeadCodeVerifier.flowEnd(node);
} while (identical(_unfinishedNullShorts.last, node));
if (node is! CascadeExpression) {
// Make the static type of `node` (or whatever it was rewritten to)
// nullable.
rewrittenExpression ??= node;
rewrittenExpression.setPseudoExpressionStaticType(
typeSystem.makeNullable(rewrittenExpression.typeOrThrow),
);
}
}
}
/// 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.
}
/// Examines the top entry of [_rewriteStack] but does not pop it.
ExpressionImpl? peekRewrite() => _rewriteStack.last;
/// Pops the top entry off of [_rewriteStack].
ExpressionImpl? popRewrite() {
var expression = _rewriteStack.removeLast();
if (_debugRewriteStack) {
assert(_debugPrint('POP ${expression.runtimeType} $expression'));
}
return expression;
}
/// Set information about enclosing declarations.
void prepareEnclosingDeclarations({
InterfaceElementImpl2? enclosingClassElement,
ExecutableElementImpl2? enclosingExecutableElement,
}) {
enclosingClass = enclosingClassElement;
_setupThisType();
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 Directive ||
node is ExtensionDeclaration ||
node is FunctionDeclaration ||
node is TopLevelVariableDeclaration;
}
if (parent is ClassDeclarationImpl) {
enclosingClass = parent.declaredFragment!.element;
return true;
}
if (parent is ExtensionDeclarationImpl) {
enclosingExtension = parent.declaredFragment!.element;
return true;
}
if (parent is MixinDeclarationImpl) {
enclosingClass = parent.declaredFragment!.element;
return true;
}
return false;
}
/// Pushes an entry onto [_rewriteStack].
void pushRewrite(ExpressionImpl? expression) {
if (_debugRewriteStack) {
assert(_debugPrint('PUSH ${expression.runtimeType} $expression'));
}
_rewriteStack.add(expression);
}
/// Replaces the expression [oldNode] with [newNode], updating the node's
/// parent as appropriate.
///
/// If [newNode] is the parent of [oldNode] already (because [newNode] became
/// the parent of [oldNode] in its constructor), this action will loop
/// infinitely; pass [oldNode]'s previous parent as [parent] to avoid this.
void replaceExpression(
Expression oldNode,
ExpressionImpl newNode, {
AstNode? parent,
}) {
assert(() {
assert(_replacements[oldNode] == null);
_replacements[oldNode] = newNode;
return true;
}());
if (_rewriteStack.isNotEmpty && identical(peekRewrite(), oldNode)) {
if (_debugRewriteStack) {
assert(_debugPrint('REPLACE ${newNode.runtimeType} $newNode'));
}
_rewriteStack.last = newNode;
}
inferenceLogWriter?.recordExpressionRewrite(
oldExpression: oldNode,
newExpression: newNode,
);
NodeReplacer.replace(oldNode, newNode, parent: parent);
nullSafetyDeadCodeVerifier.maybeRewriteFirstDeadNode(oldNode, newNode);
}
PatternResult resolveAssignedVariablePattern({
required AssignedVariablePatternImpl node,
required SharedMatchContext context,
}) {
var element = node.element2;
if (element is! PromotableElementImpl2) {
return PatternResult(
matchedValueType: SharedTypeView(InvalidTypeImpl.instance),
);
}
if (element.isFinal) {
var flow = this.flow;
if (element.isLate) {
if (flow.isAssigned(element)) {
errorReporter.atToken(
node.name,
CompileTimeErrorCode.LATE_FINAL_LOCAL_ALREADY_ASSIGNED,
);
}
} else {
if (!flow.isUnassigned(element)) {
errorReporter.atToken(
node.name,
CompileTimeErrorCode.ASSIGNMENT_TO_FINAL_LOCAL,
arguments: [node.name.lexeme],
);
}
}
}
return analyzeAssignedVariablePattern(context, node, element);
}
/// Resolve LHS [node] of an assignment, an explicit [AssignmentExpression],
/// or implicit [PrefixExpression] or [PostfixExpression].
PropertyElementResolverResult resolveForWrite({
required ExpressionImpl node,
required bool hasRead,
}) {
inferenceLogWriter?.enterLValue(node);
if (node is IndexExpressionImpl) {
var target = node.target;
if (target != null) {
if (isDotShorthand(node)) {
// Recovery.
// It's a compile-time error to use postfix or prefix operators with
// dot shorthands. We provide an unknown type since this shouldn't be
// valid code, but we want to prevent any crashes.
pushDotShorthandContext(target, operations.unknownType);
}
analyzeExpression(target, operations.unknownType);
popRewrite();
}
if (node.isNullAware) {
_startNullAwareAccess(node, node.target);
nullSafetyDeadCodeVerifier.visitNode(node.index);
}
var result = _propertyElementResolver.resolveIndexExpression(
node: node,
hasRead: hasRead,
hasWrite: true,
);
analyzeExpression(
node.index,
SharedTypeSchemaView(result.indexContextType),
);
popRewrite();
var whyNotPromoted = flowAnalysis.flow?.whyNotPromoted(node.index);
checkIndexExpressionIndex(
node.index,
readElement:
hasRead ? result.readElement2 as ExecutableElement2OrMember? : null,
writeElement: result.writeElement2 as ExecutableElement2OrMember?,
whyNotPromoted: whyNotPromoted,
);
inferenceLogWriter?.exitLValue(node);
return result;
} else if (node is PrefixedIdentifierImpl) {
var prefix = node.prefix;
analyzeExpression(prefix, operations.unknownType);
popRewrite();
// TODO(scheglov): It would be nice to rewrite all such cases.
if (prefix.staticType is RecordType) {
var propertyAccess = PropertyAccessImpl(
target: prefix,
operator: node.period,
propertyName: node.identifier,
);
NodeReplacer.replace(node, propertyAccess);
inferenceLogWriter?.exitLValue(node);
return _propertyElementResolver.resolvePropertyAccess(
node: propertyAccess,
hasRead: hasRead,
hasWrite: true,
);
}
inferenceLogWriter?.exitLValue(node);
return _propertyElementResolver.resolvePrefixedIdentifier(
node: node,
hasRead: hasRead,
hasWrite: true,
);
} else if (node is PropertyAccessImpl) {
if (node.target case var target?) {
analyzeExpression(target, operations.unknownType);
popRewrite();
}
if (node.isNullAware) {
_startNullAwareAccess(node, node.target);
nullSafetyDeadCodeVerifier.visitNode(node.propertyName);
}
inferenceLogWriter?.exitLValue(node);
return _propertyElementResolver.resolvePropertyAccess(
node: node,
hasRead: hasRead,
hasWrite: true,
);
} else if (node is SimpleIdentifierImpl) {
var result = _propertyElementResolver.resolveSimpleIdentifier(
node: node,
hasRead: hasRead,
hasWrite: true,
);
if (hasRead && result.readElementRequested2 == null) {
errorReporter.atNode(
node,
CompileTimeErrorCode.UNDEFINED_IDENTIFIER,
arguments: [node.name],
);
}
inferenceLogWriter?.exitLValue(node);
return result;
} else {
inferenceLogWriter?.exitLValue(node, reanalyzeAsRValue: true);
analyzeExpression(
node,
SharedTypeSchemaView(UnknownInferredType.instance),
);
popRewrite();
return PropertyElementResolverResult();
}
}
PatternResult resolveMapPattern({
required MapPatternImpl node,
required SharedMatchContext context,
}) {
inferenceLogWriter?.enterPattern(node);
({SharedTypeView keyType, SharedTypeView valueType})? typeArguments;
var typeArgumentsList = node.typeArguments;
if (typeArgumentsList != null) {
typeArgumentsList.accept(this);
// Check that we have exactly two type arguments.
var length = typeArgumentsList.arguments.length;
if (length == 2) {
typeArguments = (
keyType: SharedTypeView(typeArgumentsList.arguments[0].typeOrThrow),
valueType: SharedTypeView(typeArgumentsList.arguments[1].typeOrThrow),
);
} else {
errorReporter.atNode(
typeArgumentsList,
CompileTimeErrorCode.EXPECTED_TWO_MAP_PATTERN_TYPE_ARGUMENTS,
arguments: [length],
);
}
}
var result = analyzeMapPattern(
context,
node,
typeArguments: typeArguments,
elements: node.elements,
);
node.requiredType = result.requiredType.unwrapTypeView();
checkPatternNeverMatchesValueType(
context: context,
pattern: node,
requiredType: result.requiredType.unwrapTypeView(),
matchedValueType: result.matchedValueType.unwrapTypeView(),
);
inferenceLogWriter?.exitPattern(node);
return result;
}
@override
(ExecutableElement?, SharedTypeView) resolveObjectPatternPropertyGet({
required covariant ObjectPatternImpl objectPattern,
required SharedTypeView receiverType,
required covariant SharedPatternField field,
}) {
var fieldNode = field.node;
var nameToken = fieldNode.name?.name;
nameToken ??= field.pattern.variablePattern?.name;
if (nameToken == null) {
return (null, SharedTypeView(typeProvider.dynamicType));
}
var result = typePropertyResolver.resolve(
receiver: null,
receiverType: receiverType.unwrapTypeView(),
name: nameToken.lexeme,
hasRead: true,
hasWrite: false,
propertyErrorEntity: objectPattern.type,
nameErrorEntity: nameToken,
);
if (result.needsGetterError) {
errorReporter.atToken(
nameToken,
CompileTimeErrorCode.UNDEFINED_GETTER,
arguments: [nameToken.lexeme, receiverType],
);
}
var getter = result.getter2;
if (getter != null) {
fieldNode.element2 = getter;
if (getter is PropertyAccessorElement2OrMember) {
return (getter, SharedTypeView(getter.returnType));
} else {
return (getter, SharedTypeView(getter.type));
}
}
var recordField = result.recordField;
if (recordField != null) {
return (null, SharedTypeView(recordField.type));
}
return (null, SharedTypeView(typeProvider.dynamicType));
}
@override
RelationalOperatorResolution? resolveRelationalPatternOperator(
covariant RelationalPatternImpl node,
SharedTypeView matchedType,
) {
var operatorLexeme = node.operator.lexeme;
RelationalOperatorKind kind;
String methodName;
if (operatorLexeme == '==') {
kind = RelationalOperatorKind.equals;
methodName = '==';
} else if (operatorLexeme == '!=') {
kind = RelationalOperatorKind.notEquals;
methodName = '==';
} else {
kind = RelationalOperatorKind.other;
methodName = operatorLexeme;
}
var result = typePropertyResolver.resolve(
receiver: null,
receiverType: matchedType.unwrapTypeView(),
name: methodName,
hasRead: true,
hasWrite: false,
propertyErrorEntity: node.operator,
nameErrorEntity: node,
parentNode: node,
);
if (result.needsGetterError) {
errorReporter.atToken(
node.operator,
CompileTimeErrorCode.UNDEFINED_OPERATOR,
arguments: [methodName, matchedType],
);
}
var element = result.getter2 as MethodElement2OrMember?;
node.element2 = element;
if (element == null) {
return null;
}
var parameterType = element.firstParameterType;
if (parameterType == null) {
return null;
}
return RelationalOperatorResolution(
kind: kind,
parameterType: SharedTypeView(parameterType),
returnType: SharedTypeView(element.returnType),
);
}
void setReadElement(
Expression node,
Element? element, {
required bool atDynamicTarget,
}) {
var readType =
atDynamicTarget ? DynamicTypeImpl.instance : InvalidTypeImpl.instance;
if (node is IndexExpression) {
if (element is MethodElement2OrMember) {
readType = element.returnType;
}
} else if (node is PrefixedIdentifier ||
node is PropertyAccess ||
node is SimpleIdentifier) {
if (element is GetterElement2OrMember) {
readType = element.returnType;
} else if (element is VariableElement) {
readType = localVariableTypeProvider.getType(
node as SimpleIdentifierImpl,
isRead: true,
);
}
}
var parent = node.parent;
if (parent is AssignmentExpressionImpl && parent.leftHandSide == node) {
parent.readElement2 = element;
parent.readType = readType;
} else if (parent is PostfixExpressionImpl &&
parent.operator.type.isIncrementOperator) {
parent.readElement2 = element;
parent.readType = readType;
} else if (parent is PrefixExpressionImpl &&
parent.operator.type.isIncrementOperator) {
parent.readElement2 = element;
parent.readType = readType;
}
}
@override
void setVariableType(PromotableElement variable, SharedTypeView type) {
if (variable is LocalVariableElementImpl2) {
variable.type = type.unwrapTypeView();
} else {
throw UnimplementedError('TODO(paulberry)');
}
}
void setWriteElement(
Expression node,
Element? element, {
required bool atDynamicTarget,
}) {
var writeType =
atDynamicTarget ? DynamicTypeImpl.instance : InvalidTypeImpl.instance;
if (node is AugmentedExpression) {
if (element is SetterElement2OrMember) {
if (element.formalParameters case [var valueParameter]) {
writeType = valueParameter.type;
}
}
} else if (node is IndexExpression) {
if (element is MethodElement2OrMember) {
var parameters = element.formalParameters;
if (parameters.length == 2) {
writeType = parameters[1].type;
}
}
} else if (node is PrefixedIdentifier ||
node is PropertyAccess ||
node is SimpleIdentifier) {
if (element is SetterElement2OrMember) {
if (element.isSynthetic) {
var variable = element.variable3;
if (variable != null) {
writeType = variable.type;
}
} else {
var parameters = element.formalParameters;
if (parameters.length == 1) {
writeType = parameters[0].type;
}
}
} else if (element is VariableElement2OrMember) {
writeType = element.type;
}
}
var parent = node.parent;
if (parent is AssignmentExpressionImpl && parent.leftHandSide == node) {
parent.writeElement2 = element;
parent.writeType = writeType;
} else if (parent is PostfixExpressionImpl &&
parent.operator.type.isIncrementOperator) {
parent.writeElement2 = element;
parent.writeType = writeType;
} else if (parent is PrefixExpressionImpl &&
parent.operator.type.isIncrementOperator) {
parent.writeElement2 = element;
parent.writeType = writeType;
}
}
/// Returns the result of an implicit `this.` lookup for the identifier [node]
/// in a getter context, or `null` if no match was found.
LexicalLookupResult? thisLookupGetter(SimpleIdentifier node) {
return ThisLookup.lookupGetter(this, node);
}
/// Returns the result of an implicit `this.` lookup for the identifier [node]
/// in a setter context, or `null` if no match was found.
LexicalLookupResult? thisLookupSetter(SimpleIdentifier node) {
return ThisLookup.lookupSetter(this, node);
}
@override
SharedTypeView variableTypeFromInitializerType(SharedTypeView type) {
TypeImpl unwrapped = type.unwrapTypeView();
if (unwrapped.isDartCoreNull) {
return SharedTypeView(DynamicTypeImpl.instance);
}
return SharedTypeView(typeSystem.demoteType(unwrapped));
}
@override
void visitAdjacentStrings(
covariant AdjacentStringsImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
for (var string in node.strings) {
analyzeExpression(string, operations.unknownType);
popRewrite();
}
typeAnalyzer.visitAdjacentStrings(node);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitAnnotation(covariant AnnotationImpl node) {
inferenceLogWriter?.enterAnnotation(node);
// Annotations can contain expressions, so we need flow analysis to be
// available to process those expressions.
var isTopLevel = flowAnalysis.flow == null;
if (isTopLevel) {
flowAnalysis.bodyOrInitializer_enter(node, null);
}
assert(flowAnalysis.flow != null);
var whyNotPromotedArguments =
<Map<SharedTypeView, NonPromotionReason> Function()>[];
_annotationResolver.resolve(node, whyNotPromotedArguments);
var arguments = node.arguments;
if (arguments != null) {
checkForArgumentTypesNotAssignableInList(
arguments,
whyNotPromotedArguments,
);
}
if (isTopLevel) {
flowAnalysis.bodyOrInitializer_exit();
}
inferenceLogWriter?.exitAnnotation(node);
}
@override
void visitAsExpression(
covariant AsExpressionImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
analyzeExpression(
node.expression,
SharedTypeSchemaView(UnknownInferredType.instance),
);
popRewrite();
checkUnreachableNode(node.type);
node.type.accept(this);
typeAnalyzer.visitAsExpression(node);
flowAnalysis.asExpression(node);
_insertImplicitCallReference(
insertGenericFunctionInstantiation(node, contextType: contextType),
contextType: contextType,
);
var expression = node.expression;
var staticType = node.staticType;
if (staticType != null && expression is SimpleIdentifier) {
var simpleIdentifier = expression as SimpleIdentifier;
var element = simpleIdentifier.element;
if (element is PromotableElementImpl2 &&
!expression.typeOrThrow.isDartCoreNull &&
typeSystem.isNullable(element.type) &&
typeSystem.isNonNullable(staticType) &&
flowAnalysis.isDefinitelyUnassigned(simpleIdentifier, element)) {
errorReporter.atNode(
simpleIdentifier,
WarningCode.CAST_FROM_NULLABLE_ALWAYS_FAILS,
arguments: [simpleIdentifier.name],
);
}
}
inferenceLogWriter?.exitExpression(node);
}
@override
void visitAssertInitializer(covariant AssertInitializerImpl node) {
flowAnalysis.flow?.assert_begin();
analyzeExpression(
node.condition,
SharedTypeSchemaView(typeProvider.boolType),
);
popRewrite();
boolExpressionVerifier.checkForNonBoolExpression(
node.condition,
diagnosticCode: CompileTimeErrorCode.NON_BOOL_EXPRESSION,
whyNotPromoted: flowAnalysis.flow?.whyNotPromoted(node.condition),
);
flowAnalysis.flow?.assert_afterCondition(node.condition);
if (node.message case var message?) {
analyzeExpression(message, operations.unknownType);
popRewrite();
}
flowAnalysis.flow?.assert_end();
}
@override
void visitAssertStatement(covariant AssertStatementImpl node) {
inferenceLogWriter?.enterStatement(node);
checkUnreachableNode(node);
flowAnalysis.flow?.assert_begin();
analyzeExpression(
node.condition,
SharedTypeSchemaView(typeProvider.boolType),
);
popRewrite();
boolExpressionVerifier.checkForNonBoolExpression(
node.condition,
diagnosticCode: CompileTimeErrorCode.NON_BOOL_EXPRESSION,
whyNotPromoted: flowAnalysis.flow?.whyNotPromoted(node.condition),
);
flowAnalysis.flow?.assert_afterCondition(node.condition);
if (node.message case var message?) {
analyzeExpression(message, operations.unknownType);
popRewrite();
}
flowAnalysis.flow?.assert_end();
inferenceLogWriter?.exitStatement(node);
}
@override
void visitAssignmentExpression(
AssignmentExpression node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
_assignmentExpressionResolver.resolve(
node as AssignmentExpressionImpl,
contextType: contextType,
);
_insertImplicitCallReference(
insertGenericFunctionInstantiation(node, contextType: contextType),
contextType: contextType,
);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitAugmentedExpression(
covariant AugmentedExpressionImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
super.visitAugmentedExpression(node);
}
@override
void visitAugmentedInvocation(
covariant AugmentedInvocationImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
super.visitAugmentedInvocation(node);
}
@override
void visitAwaitExpression(
covariant AwaitExpressionImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
analyzeExpression(
node.expression,
SharedTypeSchemaView(_createFutureOr(contextType)),
);
popRewrite();
typeAnalyzer.visitAwaitExpression(node);
_insertImplicitCallReference(
insertGenericFunctionInstantiation(node, contextType: contextType),
contextType: contextType,
);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitBinaryExpression(
BinaryExpression node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
_binaryExpressionResolver.resolve(
node as BinaryExpressionImpl,
contextType: contextType,
);
_insertImplicitCallReference(
insertGenericFunctionInstantiation(node, contextType: contextType),
contextType: contextType,
);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitBlock(Block node) {
inferenceLogWriter?.enterStatement(node);
checkUnreachableNode(node);
node.visitChildren(this);
inferenceLogWriter?.exitStatement(node);
}
@override
TypeImpl visitBlockFunctionBody(
covariant BlockFunctionBodyImpl node, {
TypeImpl? imposedType,
}) {
var oldBodyContext = _bodyContext;
try {
_bodyContext = BodyInferenceContext(
typeSystem: typeSystem,
node: node,
imposedType: imposedType,
);
checkUnreachableNode(node);
node.visitChildren(this);
return _finishFunctionBodyInference();
} finally {
_bodyContext = oldBodyContext;
}
}
@override
void visitBooleanLiteral(
covariant BooleanLiteralImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
flowAnalysis.flow?.booleanLiteral(node, node.value);
checkUnreachableNode(node);
node.visitChildren(this);
typeAnalyzer.visitBooleanLiteral(node);
inferenceLogWriter?.exitExpression(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);
flowAnalysis.breakStatement(node);
}
@override
void visitCascadeExpression(
covariant CascadeExpressionImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
analyzeExpression(node.target, SharedTypeSchemaView(contextType));
var targetType = node.target.staticType ?? typeProvider.dynamicType;
popRewrite();
flowAnalysis.flow!.cascadeExpression_afterTarget(
node.target,
SharedTypeView(targetType),
isNullAware: node.isNullAware,
);
if (node.isNullAware) {
flowAnalysis.flow!.nullAwareAccess_rightBegin(
node.target,
SharedTypeView(targetType),
);
_unfinishedNullShorts.add(node.nullShortingTermination);
}
for (var cascadeSection in node.cascadeSections) {
analyzeExpression(cascadeSection, operations.unknownType);
popRewrite();
}
typeAnalyzer.visitCascadeExpression(node);
nullShortingTermination(node);
flowAnalysis.flow!.cascadeExpression_end(node);
_insertImplicitCallReference(node, contextType: contextType);
nullSafetyDeadCodeVerifier.verifyCascadeExpression(node);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitCatchClause(CatchClause node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
void visitCatchClauseParameter(CatchClauseParameter node) {
node.visitChildren(this);
}
@override
void visitClassDeclaration(covariant ClassDeclarationImpl node) {
var declaredFragment = node.declaredFragment!;
var declaredElement = declaredFragment.element;
//
// Continue the class resolution.
//
var outerType = enclosingClass;
try {
enclosingClass = declaredElement;
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitClassDeclaration(node);
} finally {
enclosingClass = outerType;
}
baseOrFinalTypeVerifier.checkElement(
declaredElement,
node.implementsClause,
);
}
@override
void visitClassTypeAlias(covariant ClassTypeAliasImpl node) {
var declaredFragment = node.declaredFragment!;
var declaredElement = declaredFragment.element;
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitClassTypeAlias(node);
baseOrFinalTypeVerifier.checkElement(
declaredElement,
node.implementsClause,
);
}
@override
void visitComment(Comment node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
void visitCommentReference(CommentReference node) {
//
// We do not visit the expression because it needs to be visited in the
// context of the reference.
//
elementResolver.visitCommentReference(node);
}
@override
void visitCompilationUnit(CompilationUnit node) {
conditionallyStartInferenceLogging(dump: inferenceLoggingPredicate(source));
try {
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);
}
checkIdle();
} finally {
stopInferenceLogging();
}
}
@override
void visitConditionalExpression(
covariant ConditionalExpressionImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
ExpressionImpl condition = node.condition;
var flow = flowAnalysis.flow;
flow?.conditional_conditionBegin();
analyzeExpression(
node.condition,
SharedTypeSchemaView(typeProvider.boolType),
);
condition = popRewrite()!;
var whyNotPromoted = flowAnalysis.flow?.whyNotPromoted(condition);
boolExpressionVerifier.checkForNonBoolCondition(
condition,
whyNotPromoted: whyNotPromoted,
);
if (flow != null) {
flow.conditional_thenBegin(condition, node);
checkUnreachableNode(node.thenExpression);
}
analyzeExpression(node.thenExpression, SharedTypeSchemaView(contextType));
popRewrite();
nullSafetyDeadCodeVerifier.flowEnd(node.thenExpression);
ExpressionImpl elseExpression = node.elseExpression;
if (flow != null) {
flow.conditional_elseBegin(
node.thenExpression,
SharedTypeView(node.thenExpression.typeOrThrow),
);
checkUnreachableNode(elseExpression);
analyzeExpression(elseExpression, SharedTypeSchemaView(contextType));
} else {
analyzeExpression(elseExpression, SharedTypeSchemaView(contextType));
}
elseExpression = popRewrite()!;
typeAnalyzer.visitConditionalExpression(node, contextType: contextType);
if (flow != null) {
flow.conditional_end(
node,
SharedTypeView(node.typeOrThrow),
elseExpression,
SharedTypeView(elseExpression.typeOrThrow),
);
nullSafetyDeadCodeVerifier.flowEnd(elseExpression);
}
_insertImplicitCallReference(node, contextType: contextType);
inferenceLogWriter?.exitExpression(node);
}
@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(covariant ConstructorDeclarationImpl node) {
var fragment = node.declaredFragment!;
var element = fragment.element;
var returnType = element.type.returnType;
var outerFunction = enclosingFunction;
try {
enclosingFunction = element;
assert(_thisType == null);
_setupThisType();
checkUnreachableNode(node);
node.documentationComment?.accept(this);
node.metadata.accept(this);
node.returnType.accept(this);
node.parameters.accept(this);
flowAnalysis.bodyOrInitializer_enter(node, node.parameters);
flowAnalysis.executableDeclaration_enter(
node,
node.parameters,
isClosure: false,
);
node.initializers.accept(this);
node.redirectedConstructor?.accept(this);
node.body.resolve(this, returnType is DynamicType ? null : returnType);
elementResolver.visitConstructorDeclaration(node);
if (node.factoryKeyword != null) {
checkForBodyMayCompleteNormally(body: node.body, errorNode: node);
}
flowAnalysis.executableDeclaration_exit(node.body, false);
flowAnalysis.bodyOrInitializer_exit();
nullSafetyDeadCodeVerifier.flowEnd(node);
} finally {
enclosingFunction = outerFunction;
_thisType = null;
}
}
@override
void visitConstructorFieldInitializer(
covariant ConstructorFieldInitializerImpl 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 fieldName = node.fieldName;
var fieldElement = enclosingClass!.getField(fieldName.name);
fieldName.element = fieldElement;
var fieldType = fieldElement?.type ?? UnknownInferredType.instance;
var expression = node.expression;
analyzeExpression(expression, SharedTypeSchemaView(fieldType));
expression = popRewrite()!;
var whyNotPromoted = flowAnalysis.flow?.whyNotPromoted(expression);
if (fieldElement != null) {
var enclosingConstructor = enclosingFunction as ConstructorElementImpl2;
checkForFieldInitializerNotAssignable(
node,
fieldElement,
isConstConstructor: enclosingConstructor.isConst,
whyNotPromoted: whyNotPromoted,
);
}
}
@override
void visitConstructorName(ConstructorName node) {
node.type.accept(this);
elementResolver.visitConstructorName(node as ConstructorNameImpl);
}
@override
void visitConstructorReference(
covariant ConstructorReferenceImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
_constructorReferenceResolver.resolve(node, contextType: contextType);
_insertImplicitCallReference(node, contextType: contextType);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitConstructorSelector(ConstructorSelector node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@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);
flowAnalysis.continueStatement(node);
}
@override
void visitDeclaredIdentifier(DeclaredIdentifier node) {
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitDeclaredIdentifier(node);
}
@override
void visitDefaultFormalParameter(covariant DefaultFormalParameterImpl node) {
var fragment = node.declaredFragment!;
checkUnreachableNode(node);
node.parameter.accept(this);
var defaultValue = node.defaultValue;
if (defaultValue != null) {
analyzeExpression(
defaultValue,
SharedTypeSchemaView(fragment.element.type),
);
popRewrite();
}
if (fragment is DefaultParameterFragmentImpl && node.isOfLocalFunction) {
fragment.constantInitializer = defaultValue;
}
}
@override
void visitDoStatement(covariant DoStatementImpl node) {
inferenceLogWriter?.enterStatement(node);
checkUnreachableNode(node);
var condition = node.condition;
flowAnalysis.flow?.doStatement_bodyBegin(node);
node.body.accept(this);
flowAnalysis.flow?.doStatement_conditionBegin();
analyzeExpression(condition, SharedTypeSchemaView(typeProvider.boolType));
condition = popRewrite()!;
var whyNotPromoted = flowAnalysis.flow?.whyNotPromoted(condition);
boolExpressionVerifier.checkForNonBoolCondition(
condition,
whyNotPromoted: whyNotPromoted,
);
flowAnalysis.flow?.doStatement_end(condition);
inferenceLogWriter?.exitStatement(node);
}
@override
void visitDotShorthandConstructorInvocation(
covariant DotShorthandConstructorInvocationImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
// If [isDotShorthand] is set, cache the context type for resolution.
if (isDotShorthand(node)) {
pushDotShorthandContext(node, SharedTypeSchemaView(contextType));
}
_instanceCreationExpressionResolver.resolveDotShorthand(
node,
contextType: contextType,
);
if (isDotShorthand(node)) {
popDotShorthandContext();
}
inferenceLogWriter?.exitExpression(node);
}
@override
void visitDotShorthandInvocation(
covariant DotShorthandInvocationImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
// If [isDotShorthand] is set, cache the context type for resolution.
if (isDotShorthand(node)) {
pushDotShorthandContext(node, SharedTypeSchemaView(contextType));
}
checkUnreachableNode(node);
var whyNotPromotedArguments =
<Map<SharedTypeView, NonPromotionReason> Function()>[];
node.typeArguments?.accept(this);
var rewrittenExpression = elementResolver.visitDotShorthandInvocation(
node,
whyNotPromotedArguments: whyNotPromotedArguments,
contextType: contextType,
);
switch (rewrittenExpression) {
case null:
// In this case, we didn't rewrite anything. The [node] is a static
// method invocation.
break;
case FunctionExpressionInvocationImpl():
_resolveRewrittenFunctionExpressionInvocation(
rewrittenExpression,
whyNotPromotedArguments,
contextType: contextType,
);
case DotShorthandConstructorInvocationImpl():
_instanceCreationExpressionResolver.resolveDotShorthand(
rewrittenExpression,
contextType: contextType,
);
}
if (rewrittenExpression is FunctionExpressionInvocationImpl ||
rewrittenExpression == null) {
var replacement = insertGenericFunctionInstantiation(
node,
contextType: contextType,
);
checkForArgumentTypesNotAssignableInList(
node.argumentList,
whyNotPromotedArguments,
);
_insertImplicitCallReference(replacement, contextType: contextType);
}
if (isDotShorthand(node)) {
popDotShorthandContext();
}
inferenceLogWriter?.exitExpression(node);
}
@override
void visitDotShorthandPropertyAccess(
covariant DotShorthandPropertyAccessImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
// If [isDotShorthand] is set, cache the context type for resolution.
if (isDotShorthand(node)) {
pushDotShorthandContext(node, SharedTypeSchemaView(contextType));
}
checkUnreachableNode(node);
var result = _propertyElementResolver.resolveDotShorthand(
node,
contextType: contextType,
);
_resolvePropertyAccessRhs_common(
result,
node,
node.propertyName,
contextType,
);
if (isDotShorthand(node)) {
popDotShorthandContext();
}
inferenceLogWriter?.exitExpression(node);
}
@override
void visitDoubleLiteral(
DoubleLiteral node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
node.visitChildren(this);
typeAnalyzer.visitDoubleLiteral(node as DoubleLiteralImpl);
inferenceLogWriter?.exitExpression(node);
}
@override
TypeImpl visitEmptyFunctionBody(
EmptyFunctionBody node, {
TypeImpl? imposedType,
}) {
checkUnreachableNode(node);
node.visitChildren(this);
return imposedType ?? typeProvider.dynamicType;
}
@override
void visitEmptyStatement(EmptyStatement node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
void visitEnumConstantArguments(EnumConstantArguments node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
void visitEnumConstantDeclaration(
covariant EnumConstantDeclarationImpl node,
) {
node.documentationComment?.accept(this);
node.metadata.accept(this);
checkUnreachableNode(node);
var fragment = node.declaredFragment!;
var initializer = fragment.constantInitializer;
if (initializer is InstanceCreationExpressionImpl) {
var constructorName = initializer.constructorName;
var constructorElement = constructorName.element;
if (constructorElement != null) {
node.constructorElement2 = constructorElement;
if (constructorElement.isFactory) {
var constructorName = node.arguments?.constructorSelector?.name;
var errorTarget = constructorName ?? node.name;
errorReporter.atEntity(
errorTarget,
CompileTimeErrorCode.ENUM_CONSTANT_INVOKES_FACTORY_CONSTRUCTOR,
);
}
} else {
if (constructorName.type.element2 is EnumElementImpl2) {
var nameNode = node.arguments?.constructorSelector?.name;
if (nameNode != null) {
errorReporter.atNode(
nameNode,
CompileTimeErrorCode.UNDEFINED_ENUM_CONSTRUCTOR_NAMED,
arguments: [nameNode.name],
);
} else {
errorReporter.atToken(
node.name,
CompileTimeErrorCode.UNDEFINED_ENUM_CONSTRUCTOR_UNNAMED,
);
}
}
}
if (constructorElement != null) {
var arguments = node.arguments;
if (arguments != null) {
var argumentList = arguments.argumentList;
argumentList.correspondingStaticParameters2 =
ResolverVisitor.resolveArgumentsToParameters(
argumentList: argumentList,
formalParameters: constructorElement.formalParameters,
errorReporter: errorReporter,
);
} else if (definingLibrary.featureSet.isEnabled(
Feature.enhanced_enums,
)) {
var requiredParameterCount =
constructorElement.formalParameters
.where((e) => e.isRequiredPositional)
.length;
if (requiredParameterCount != 0) {
_reportNotEnoughPositionalArguments(
token: node.name,
requiredParameterCount: requiredParameterCount,
actualArgumentCount: 0,
nameNode: node,
errorReporter: errorReporter,
);
}
}
}
}
var arguments = node.arguments;
if (arguments != null) {
var argumentList = arguments.argumentList;
for (var argument in argumentList.arguments) {
analyzeExpression(
argument,
SharedTypeSchemaView(
argument.correspondingParameter?.type ??
UnknownInferredType.instance,
),
);
popRewrite();
}
arguments.typeArguments?.accept(this);
var whyNotPromotedArguments =
<Map<SharedTypeView, NonPromotionReason> Function()>[];
checkForArgumentTypesNotAssignableInList(
argumentList,
whyNotPromotedArguments,
);
}
elementResolver.visitEnumConstantDeclaration(node);
}
@override
void visitEnumDeclaration(covariant EnumDeclarationImpl node) {
//
// Continue the enum resolution.
//
var outerType = enclosingClass;
try {
enclosingClass = node.declaredFragment!.element;
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitEnumDeclaration(node);
} finally {
enclosingClass = outerType;
}
}
@override
void visitExportDirective(covariant ExportDirectiveImpl node) {
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitExportDirective(node);
}
@override
TypeImpl visitExpressionFunctionBody(
covariant ExpressionFunctionBodyImpl node, {
TypeImpl? imposedType,
}) {
var oldBodyContext = _bodyContext;
try {
var bodyContext =
_bodyContext = BodyInferenceContext(
typeSystem: typeSystem,
node: node,
imposedType: imposedType,
);
checkUnreachableNode(node);
analyzeExpression(
node.expression,
SharedTypeSchemaView(
bodyContext.contextType ?? UnknownInferredType.instance,
),
);
popRewrite();
flowAnalysis.flow?.handleExit();
bodyContext.addReturnExpression(node.expression);
return _finishFunctionBodyInference();
} finally {
_bodyContext = oldBodyContext;
}
}
@override
void visitExpressionStatement(covariant ExpressionStatementImpl node) {
inferenceLogWriter?.enterStatement(node);
checkUnreachableNode(node);
analyzeExpression(node.expression, operations.unknownType);
popRewrite();
inferenceLogWriter?.exitStatement(node);
}
@override
void visitExtendsClause(ExtendsClause node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
void visitExtensionDeclaration(covariant ExtensionDeclarationImpl node) {
var outerExtension = enclosingExtension;
try {
enclosingExtension = node.declaredFragment!.element;
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitExtensionDeclaration(node);
} finally {
enclosingExtension = outerExtension;
}
}
@override
void visitExtensionOnClause(ExtensionOnClause node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
void visitExtensionOverride(
covariant ExtensionOverrideImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExtensionOverride(node, contextType);
var whyNotPromotedArguments =
<Map<SharedTypeView, NonPromotionReason> Function()>[];
node.typeArguments?.accept(this);
var receiverContextType = ExtensionMemberResolver(
this,
).computeOverrideReceiverContextType(node);
InvocationInferrer<ExtensionOverrideImpl>(
resolver: this,
node: node,
argumentList: node.argumentList,
contextType: UnknownInferredType.instance,
whyNotPromotedArguments: whyNotPromotedArguments,
).resolveInvocation(
rawType:
receiverContextType == null
? null
: FunctionTypeImpl.v2(
typeParameters: const [],
formalParameters: [
FormalParameterElementImpl.synthetic(
null,
receiverContextType,
ParameterKind.REQUIRED,
),
],
returnType: DynamicTypeImpl.instance,
nullabilitySuffix: NullabilitySuffix.none,
),
);
extensionResolver.resolveOverride(node, whyNotPromotedArguments);
inferenceLogWriter?.exitExtensionOverride(node);
}
@override
void visitExtensionTypeDeclaration(
covariant ExtensionTypeDeclarationImpl node,
) {
var outerType = enclosingClass;
try {
enclosingClass = node.declaredFragment!.element;
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitExtensionTypeDeclaration(node);
} finally {
enclosingClass = outerType;
}
}
@override
void visitFieldDeclaration(FieldDeclaration node) {
try {
assert(_thisType == null);
_setupThisType();
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitFieldDeclaration(node);
} finally {
_thisType = null;
}
}
@override
void visitFieldFormalParameter(FieldFormalParameter node) {
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitFieldFormalParameter(node);
}
@override
void visitForElement(
covariant ForElementImpl node, {
CollectionLiteralContext? context,
}) {
inferenceLogWriter?.enterElement(node);
_forResolver.resolveElement(node, context);
inferenceLogWriter?.exitElement(node);
}
@override
void visitFormalParameterList(covariant FormalParameterListImpl node) {
// Formal parameter lists can contain default values, which in turn contain
// expressions, so we need flow analysis to be available to process those
// expressions.
var isTopLevel = flowAnalysis.flow == null;
if (isTopLevel) {
flowAnalysis.bodyOrInitializer_enter(node, null);
}
checkUnreachableNode(node);
node.visitChildren(this);
if (isTopLevel) {
flowAnalysis.bodyOrInitializer_exit();
}
}
@override
void visitForStatement(ForStatement node) {
inferenceLogWriter?.enterStatement(node);
checkUnreachableNode(node);
_forResolver.resolveStatement(node as ForStatementImpl);
nullSafetyDeadCodeVerifier.flowEnd(node.body);
inferenceLogWriter?.exitStatement(node);
}
@override
void visitFunctionDeclaration(covariant FunctionDeclarationImpl node) {
bool isLocal = node.parent is FunctionDeclarationStatement;
var fragment = node.declaredFragment!;
var element = fragment.element;
var functionType = element.type;
var outerFunction = enclosingFunction;
try {
enclosingFunction = element;
checkUnreachableNode(node);
node.documentationComment?.accept(this);
node.metadata.accept(this);
node.returnType?.accept(this);
if (isLocal) {
flowAnalysis.flow!.functionExpression_begin(node);
} else {
flowAnalysis.bodyOrInitializer_enter(
node,
node.functionExpression.parameters,
);
}
flowAnalysis.executableDeclaration_enter(
node,
node.functionExpression.parameters,
isClosure: isLocal,
);
analyzeExpression(
node.functionExpression,
SharedTypeSchemaView(functionType),
);
popRewrite();
elementResolver.visitFunctionDeclaration(node);
if (!node.isSetter) {
checkForBodyMayCompleteNormally(
body: node.functionExpression.body,
errorNode: node.name,
);
}
flowAnalysis.executableDeclaration_exit(
node.functionExpression.body,
isLocal,
);
if (isLocal) {
flowAnalysis.flow!.functionExpression_end();
} else {
flowAnalysis.bodyOrInitializer_exit();
}
nullSafetyDeadCodeVerifier.flowEnd(node);
} finally {
enclosingFunction = outerFunction;
}
}
@override
void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
inferenceLogWriter?.enterStatement(node);
checkUnreachableNode(node);
node.visitChildren(this);
inferenceLogWriter?.exitStatement(node);
}
@override
void visitFunctionExpression(
covariant FunctionExpressionImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
var outerFunction = enclosingFunction;
enclosingFunction = node.declaredFragment!.element;
_functionExpressionResolver.resolve(node, contextType: contextType);
insertGenericFunctionInstantiation(node, contextType: contextType);
enclosingFunction = outerFunction;
inferenceLogWriter?.exitExpression(node);
}
@override
void visitFunctionExpressionInvocation(
covariant FunctionExpressionInvocationImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
analyzeExpression(
node.function,
SharedTypeSchemaView(UnknownInferredType.instance),
);
node.function = popRewrite()!;
var whyNotPromotedArguments =
<Map<SharedTypeView, NonPromotionReason> Function()>[];
_functionExpressionInvocationResolver.resolve(
node,
whyNotPromotedArguments,
contextType: contextType,
);
nullShortingTermination(node);
var replacement = insertGenericFunctionInstantiation(
node,
contextType: contextType,
);
checkForArgumentTypesNotAssignableInList(
node.argumentList,
whyNotPromotedArguments,
);
_insertImplicitCallReference(replacement, contextType: contextType);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitFunctionReference(
covariant FunctionReferenceImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
// If [isDotShorthand] is set, cache the context type for resolution.
if (isDotShorthand(node)) {
pushDotShorthandContext(node, SharedTypeSchemaView(contextType));
}
_functionReferenceResolver.resolve(node);
if (isDotShorthand(node)) {
popDotShorthandContext();
}
inferenceLogWriter?.exitExpression(node);
}
@override
void visitFunctionTypeAlias(FunctionTypeAlias node) {
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitFunctionTypeAlias(node);
}
@override
void visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitFunctionTypedFormalParameter(node);
}
@override
void visitGenericFunctionType(GenericFunctionType node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
void visitGenericTypeAlias(GenericTypeAlias node) {
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitGenericTypeAlias(node);
}
@override
void visitHideCombinator(HideCombinator node) {}
@override
void visitIfElement(
covariant IfElementImpl node, {
CollectionLiteralContext? context,
}) {
inferenceLogWriter?.enterElement(node);
var caseClause = node.caseClause;
if (caseClause != null) {
var guardedPattern = caseClause.guardedPattern;
analyzeIfCaseElement(
node: node,
expression: node.expression,
pattern: guardedPattern.pattern,
variables: guardedPattern.variables,
guard: guardedPattern.whenClause?.expression,
ifTrue: node.thenElement,
ifFalse: node.elseElement,
context: context,
);
// Stack: (Expression, Guard)
popRewrite(); // guard
popRewrite()!; // expression
} else {
analyzeIfElement(
node: node,
condition: node.expression,
ifTrue: node.thenElement,
ifFalse: node.elseElement,
context: context,
);
}
inferenceLogWriter?.exitElement(node);
}
@override
void visitIfStatement(covariant IfStatementImpl node) {
inferenceLogWriter?.enterStatement(node);
checkUnreachableNode(node);
var caseClause = node.caseClause;
if (caseClause != null) {
var guardedPattern = caseClause.guardedPattern;
analyzeIfCaseStatement(
node,
node.expression,
guardedPattern.pattern,
guardedPattern.whenClause?.expression,
node.thenStatement,
node.elseStatement,
guardedPattern.variables,
);
// Stack: (Expression, Guard)
popRewrite(); // guard
popRewrite()!; // expression
} else {
analyzeIfStatement(
node,
node.expression,
node.thenStatement,
node.elseStatement,
);
}
inferenceLogWriter?.exitStatement(node);
}
@override
void visitImplementsClause(ImplementsClause node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
void visitImplicitCallReference(
covariant ImplicitCallReferenceImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
checkUnreachableNode(node);
analyzeExpression(
node.expression,
SharedTypeSchemaView(UnknownInferredType.instance),
);
popRewrite();
node.typeArguments?.accept(this);
}
@override
void visitImportDirective(ImportDirective node) {
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitImportDirective(node as ImportDirectiveImpl);
}
@override
void visitIndexExpression(
covariant IndexExpressionImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
// If [isDotShorthand] is set, cache the context type for resolution.
if (isDotShorthand(node)) {
pushDotShorthandContext(node, SharedTypeSchemaView(contextType));
}
checkUnreachableNode(node);
var target = node.target;
if (target != null) {
analyzeExpression(
target,
SharedTypeSchemaView(UnknownInferredType.instance),
);
popRewrite();
}
var targetType = node.realTarget.staticType;
if (node.isNullAware) {
_startNullAwareAccess(node, node.target);
nullSafetyDeadCodeVerifier.visitNode(node.index);
}
var result = _propertyElementResolver.resolveIndexExpression(
node: node,
hasRead: true,
hasWrite: false,
);
var element = result.readElement2;
node.element = element as MethodElement?;
analyzeExpression(
node.index,
SharedTypeSchemaView(result.indexContextType),
);
popRewrite();
var whyNotPromoted = flowAnalysis.flow?.whyNotPromoted(node.index);
checkIndexExpressionIndex(
node.index,
readElement: result.readElement2 as ExecutableElement2OrMember?,
writeElement: null,
whyNotPromoted: whyNotPromoted,
);
DartType type;
if (identical(targetType, NeverTypeImpl.instance)) {
type = NeverTypeImpl.instance;
} else if (element is MethodElement) {
type = element.returnType;
} else if (targetType is DynamicType) {
type = DynamicTypeImpl.instance;
} else {
type = InvalidTypeImpl.instance;
}
node.recordStaticType(type, resolver: this);
var replacement = insertGenericFunctionInstantiation(
node,
contextType: contextType,
);
nullShortingTermination(node, rewrittenExpression: replacement);
_insertImplicitCallReference(replacement, contextType: contextType);
nullSafetyDeadCodeVerifier.verifyIndexExpression(node);
if (isDotShorthand(node)) {
popDotShorthandContext();
}
inferenceLogWriter?.exitExpression(node);
}
@override
void visitInstanceCreationExpression(
covariant InstanceCreationExpressionImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
_instanceCreationExpressionResolver.resolve(node, contextType: contextType);
_insertImplicitCallReference(node, contextType: contextType);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitIntegerLiteral(
IntegerLiteral node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
node.visitChildren(this);
typeAnalyzer.visitIntegerLiteral(
node as IntegerLiteralImpl,
contextType: contextType,
);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitInterpolationExpression(
covariant InterpolationExpressionImpl node,
) {
checkUnreachableNode(node);
analyzeExpression(node.expression, operations.unknownType);
popRewrite();
}
@override
void visitInterpolationString(InterpolationString node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
void visitIsExpression(
covariant IsExpressionImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
analyzeExpression(
node.expression,
SharedTypeSchemaView(UnknownInferredType.instance),
);
popRewrite();
checkUnreachableNode(node.type);
node.type.accept(this);
typeAnalyzer.visitIsExpression(node);
flowAnalysis.isExpression(node);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitLabel(Label node) {}
@override
void visitLabeledStatement(covariant LabeledStatementImpl node) {
inferenceLogWriter?.enterStatement(node);
flowAnalysis.labeledStatement_enter(node);
checkUnreachableNode(node);
node.visitChildren(this);
flowAnalysis.labeledStatement_exit(node);
inferenceLogWriter?.exitStatement(node);
}
@override
void visitLibraryDirective(LibraryDirective node) {
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitLibraryDirective(node);
}
@override
void visitLibraryIdentifier(
LibraryIdentifier node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {}
@override
void visitListLiteral(
covariant ListLiteralImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
_typedLiteralResolver.resolveListLiteral(node, contextType: contextType);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitMapLiteralEntry(
covariant MapLiteralEntryImpl node, {
CollectionLiteralContext? context,
}) {
inferenceLogWriter?.enterElement(node);
checkUnreachableNode(node);
// If the key is null-aware, the context of the expression under `?` should
// be changed to the nullable version of the downwards context.
var keyTypeContext = context?.keyType;
if (keyTypeContext != null && node.keyQuestion != null) {
keyTypeContext = typeSystem.makeNullable(keyTypeContext);
}
var keyType = analyzeExpression(
node.key,
SharedTypeSchemaView(keyTypeContext ?? UnknownInferredType.instance),
);
popRewrite();
flowAnalysis.flow?.nullAwareMapEntry_valueBegin(
node.key,
keyType,
isKeyNullAware: node.keyQuestion != null,
);
// If the value is null-aware, the context of the expression under `?`
// should be changed to the nullable version of the downwards context.
var valueTypeContext = context?.valueType;
if (valueTypeContext != null && node.valueQuestion != null) {
valueTypeContext = typeSystem.makeNullable(valueTypeContext);
}
analyzeExpression(
node.value,
SharedTypeSchemaView(valueTypeContext ?? UnknownInferredType.instance),
);
popRewrite();
flowAnalysis.flow?.nullAwareMapEntry_end(
isKeyNullAware: node.keyQuestion != null,
);
inferenceLogWriter?.exitElement(node);
}
@override
void visitMethodDeclaration(covariant MethodDeclarationImpl node) {
var fragment = node.declaredFragment!;
var element = fragment.element;
var returnType = element.returnType;
var outerFunction = enclosingFunction;
try {
enclosingFunction = element;
assert(_thisType == null);
_setupThisType();
checkUnreachableNode(node);
node.documentationComment?.accept(this);
node.metadata.accept(this);
node.returnType?.accept(this);
node.typeParameters?.accept(this);
node.parameters?.accept(this);
flowAnalysis.bodyOrInitializer_enter(node, node.parameters);
flowAnalysis.executableDeclaration_enter(
node,
node.parameters,
isClosure: false,
);
node.body.resolve(this, returnType is DynamicType ? null : returnType);
elementResolver.visitMethodDeclaration(node);
if (!node.isSetter) {
checkForBodyMayCompleteNormally(body: node.body, errorNode: node.name);
}
flowAnalysis.executableDeclaration_exit(node.body, false);
flowAnalysis.bodyOrInitializer_exit();
nullSafetyDeadCodeVerifier.flowEnd(node);
} finally {
enclosingFunction = outerFunction;
_thisType = null;
}
}
@override
void visitMethodInvocation(
covariant MethodInvocationImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
// If [isDotShorthand] is set, cache the context type for resolution.
if (isDotShorthand(node)) {
pushDotShorthandContext(node, SharedTypeSchemaView(contextType));
}
checkUnreachableNode(node);
var whyNotPromotedArguments =
<Map<SharedTypeView, NonPromotionReason> Function()>[];
var target = node.target;
if (target != null) {
analyzeExpression(target, operations.unknownType);
target = popRewrite();
}
if (node.isNullAware) {
_startNullAwareAccess(node, target);
nullSafetyDeadCodeVerifier.visitNode(node.methodName);
}
node.typeArguments?.accept(this);
var functionRewrite = elementResolver.visitMethodInvocation(
node,
whyNotPromotedArguments: whyNotPromotedArguments,
contextType: contextType,
);
if (functionRewrite != null) {
_resolveRewrittenFunctionExpressionInvocation(
functionRewrite,
whyNotPromotedArguments,
contextType: contextType,
);
}
nullShortingTermination(node, rewrittenExpression: functionRewrite);
var replacement = insertGenericFunctionInstantiation(
node,
contextType: contextType,
);
checkForArgumentTypesNotAssignableInList(
node.argumentList,
whyNotPromotedArguments,
);
_insertImplicitCallReference(replacement, contextType: contextType);
nullSafetyDeadCodeVerifier.verifyMethodInvocation(node);
if (isDotShorthand(node)) {
popDotShorthandContext();
}
inferenceLogWriter?.exitExpression(node);
}
@override
void visitMixinDeclaration(covariant MixinDeclarationImpl node) {
var declaredFragment = node.declaredFragment!;
var declaredElement = declaredFragment.element;
//
// Continue the class resolution.
//
var outerType = enclosingClass;
try {
enclosingClass = node.declaredFragment!.element;
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitMixinDeclaration(node);
} finally {
enclosingClass = outerType;
}
baseOrFinalTypeVerifier.checkElement(
declaredElement,
node.implementsClause,
);
}
@override
void visitMixinOnClause(MixinOnClause node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
void visitNamedExpression(
covariant NamedExpressionImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
node.name.accept(this);
analyzeExpression(node.expression, SharedTypeSchemaView(contextType));
popRewrite();
typeAnalyzer.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);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitNamedType(NamedType node) {
// All TypeName(s) are already resolved, so we don't resolve it here.
// But there might be type arguments with Expression(s), such as default
// values for formal parameters of GenericFunctionType(s). These are
// invalid, but if they exist, they should be resolved.
node.typeArguments?.accept(this);
}
@override
void visitNativeClause(NativeClause node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
TypeImpl visitNativeFunctionBody(
covariant NativeFunctionBodyImpl node, {
TypeImpl? imposedType,
}) {
checkUnreachableNode(node);
if (node.stringLiteral case var stringLiteral?) {
analyzeExpression(stringLiteral, operations.unknownType);
popRewrite();
}
return imposedType ?? typeProvider.dynamicType;
}
@override
void visitNullAwareElement(
covariant NullAwareElementImpl node, {
CollectionLiteralContext? context,
}) {
inferenceLogWriter?.enterElement(node);
var elementType = context?.elementType;
if (elementType != null) {
elementType = typeSystem.makeNullable(elementType);
}
analyzeExpression(
node.value,
SharedTypeSchemaView(elementType ?? UnknownInferredType.instance),
);
popRewrite();
inferenceLogWriter?.exitElement(node);
}
@override
void visitNullLiteral(
NullLiteral node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
node.visitChildren(this);
typeAnalyzer.visitNullLiteral(node as NullLiteralImpl);
flowAnalysis.flow?.nullLiteral(node, SharedTypeView(node.typeOrThrow));
checkUnreachableNode(node);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitParenthesizedExpression(
covariant ParenthesizedExpressionImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
analyzeExpression(node.expression, SharedTypeSchemaView(contextType));
popRewrite();
typeAnalyzer.visitParenthesizedExpression(node);
flowAnalysis.flow?.parenthesizedExpression(node, node.expression);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitPartDirective(PartDirective node) {
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitPartDirective(node);
}
@override
void visitPartOfDirective(PartOfDirective node) {
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitPartOfDirective(node);
}
@override
void visitPatternAssignment(
covariant PatternAssignmentImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
var analysisResult = analyzePatternAssignment(
node,
node.pattern,
node.expression,
);
node.patternTypeSchema =
analysisResult.patternSchema.unwrapTypeSchemaView();
node.recordStaticType(
// TODO(paulberry): make this type argument unnecessary by changing the
// parameter of `ExpressionImpl.recordStaticType` to `TypeImpl`.
analysisResult.type.unwrapTypeView<TypeImpl>(),
resolver: this,
);
popRewrite(); // expression
inferenceLogWriter?.exitExpression(node);
}
@override
void visitPatternVariableDeclaration(
covariant PatternVariableDeclarationImpl node,
) {
var patternSchema =
analyzePatternVariableDeclaration(
node,
node.pattern,
node.expression,
isFinal: node.keyword.keyword == Keyword.FINAL,
).patternSchema;
node.patternTypeSchema = patternSchema.unwrapTypeSchemaView();
popRewrite(); // expression
}
@override
void visitPatternVariableDeclarationStatement(
PatternVariableDeclarationStatement node,
) {
inferenceLogWriter?.enterStatement(node);
checkUnreachableNode(node);
node.declaration.accept(this);
inferenceLogWriter?.exitStatement(node);
}
@override
void visitPostfixExpression(
covariant PostfixExpressionImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
// If [isDotShorthand] is set, cache the context type for resolution.
if (isDotShorthand(node)) {
pushDotShorthandContext(node, SharedTypeSchemaView(contextType));
}
checkUnreachableNode(node);
_postfixExpressionResolver.resolve(node, contextType: contextType);
_insertImplicitCallReference(
insertGenericFunctionInstantiation(node, contextType: contextType),
contextType: contextType,
);
if (isDotShorthand(node)) {
popDotShorthandContext();
}
inferenceLogWriter?.exitExpression(node);
}
@override
void visitPrefixedIdentifier(
covariant PrefixedIdentifierImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
var rewrittenPropertyAccess = _prefixedIdentifierResolver.resolve(
node,
contextType: contextType,
);
if (rewrittenPropertyAccess != null) {
_resolvePropertyAccessRhs(rewrittenPropertyAccess, contextType);
// We did record that `node` was replaced with `rewrittenPropertyAccess`.
// But if `rewrittenPropertyAccess` was itself rewritten, replace the
// rewrite result of `node`.
assert(() {
var rewrite = _replacements[rewrittenPropertyAccess];
if (rewrite != null) {
_replacements[node] = rewrite;
}
return true;
}());
inferenceLogWriter?.exitExpression(node);
return;
}
_insertImplicitCallReference(
insertGenericFunctionInstantiation(node, contextType: contextType),
contextType: contextType,
);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitPrefixExpression(
PrefixExpression node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
_prefixExpressionResolver.resolve(
node as PrefixExpressionImpl,
contextType: contextType,
);
_insertImplicitCallReference(
insertGenericFunctionInstantiation(node, contextType: contextType),
contextType: contextType,
);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitPropertyAccess(
covariant PropertyAccessImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
// If [isDotShorthand] is set, cache the context type for resolution.
if (isDotShorthand(node)) {
pushDotShorthandContext(node, SharedTypeSchemaView(contextType));
}
checkUnreachableNode(node);
var target = node.target;
if (target != null) {
analyzeExpression(
target,
SharedTypeSchemaView(UnknownInferredType.instance),
);
popRewrite();
}
checkUnreachableNode(node.propertyName);
_resolvePropertyAccessRhs(node, contextType);
if (isDotShorthand(node)) {
popDotShorthandContext();
}
inferenceLogWriter?.exitExpression(node);
}
@override
void visitRecordLiteral(
covariant RecordLiteralImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
_recordLiteralResolver.resolve(node, contextType: contextType);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitRecordTypeAnnotation(covariant RecordTypeAnnotationImpl node) {
// All RecordTypeAnnotation(s) are already resolved, so we don't resolve
// it here. But there might be types with Expression(s), such as default
// values for formal parameters of GenericFunctionType(s). These are
// invalid, but if they exist, they should be resolved.
node.visitChildren(this);
}
@override
void visitRecordTypeAnnotationNamedField(
RecordTypeAnnotationNamedField node,
) {
node.visitChildren(this);
elementResolver.visitRecordTypeAnnotationNamedField(node);
}
@override
void visitRecordTypeAnnotationNamedFields(
RecordTypeAnnotationNamedFields node,
) {
node.visitChildren(this);
}
@override
void visitRecordTypeAnnotationPositionalField(
RecordTypeAnnotationPositionalField node,
) {
node.visitChildren(this);
elementResolver.visitRecordTypeAnnotationPositionalField(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 whyNotPromotedArguments =
<Map<SharedTypeView, NonPromotionReason> Function()>[];
elementResolver.visitRedirectingConstructorInvocation(
node as RedirectingConstructorInvocationImpl,
);
InvocationInferrer<RedirectingConstructorInvocationImpl>(
resolver: this,
node: node,
argumentList: node.argumentList,
contextType: UnknownInferredType.instance,
whyNotPromotedArguments: whyNotPromotedArguments,
).resolveInvocation(rawType: node.element?.type);
checkForArgumentTypesNotAssignableInList(
node.argumentList,
whyNotPromotedArguments,
);
}
@override
void visitRepresentationConstructorName(RepresentationConstructorName node) {}
@override
void visitRepresentationDeclaration(RepresentationDeclaration node) {
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitRepresentationDeclaration(node);
}
@override
void visitRethrowExpression(
RethrowExpression node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
node.visitChildren(this);
typeAnalyzer.visitRethrowExpression(node as RethrowExpressionImpl);
flowAnalysis.flow?.handleExit();
inferenceLogWriter?.exitExpression(node);
}
@override
void visitReturnStatement(covariant ReturnStatementImpl node) {
inferenceLogWriter?.enterStatement(node);
checkUnreachableNode(node);
var expression = node.expression;
if (expression != null) {
analyzeExpression(
expression,
SharedTypeSchemaView(
bodyContext?.contextType ?? UnknownInferredType.instance,
),
);
// Pick up the expression again in case it was rewritten.
expression = popRewrite();
}
bodyContext?.addReturnExpression(expression);
flowAnalysis.flow?.handleExit();
inferenceLogWriter?.exitStatement(node);
}
@override
void visitSetOrMapLiteral(
SetOrMapLiteral node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
_typedLiteralResolver.resolveSetOrMapLiteral(
node,
contextType: contextType,
);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitShowCombinator(ShowCombinator node) {}
@override
void visitSimpleFormalParameter(SimpleFormalParameter node) {
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitSimpleFormalParameter(node);
}
@override
void visitSimpleIdentifier(
covariant SimpleIdentifierImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
_simpleIdentifierResolver.resolve(node, contextType: contextType);
_insertImplicitCallReference(
insertGenericFunctionInstantiation(node, contextType: contextType),
contextType: contextType,
);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitSimpleStringLiteral(
SimpleStringLiteral node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
node.visitChildren(this);
typeAnalyzer.visitSimpleStringLiteral(node as SimpleStringLiteralImpl);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitSpreadElement(
covariant SpreadElementImpl node, {
CollectionLiteralContext? context,
}) {
inferenceLogWriter?.enterElement(node);
var iterableType = context?.iterableType;
if (iterableType != null && node.isNullAware) {
iterableType = typeSystem.makeNullable(iterableType);
}
checkUnreachableNode(node);
analyzeExpression(
node.expression,
SharedTypeSchemaView(iterableType ?? UnknownInferredType.instance),
);
popRewrite();
if (!node.isNullAware) {
nullableDereferenceVerifier.expression(
CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE_IN_SPREAD,
node.expression,
);
}
inferenceLogWriter?.exitElement(node);
}
@override
void visitStringInterpolation(
StringInterpolation node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
node.visitChildren(this);
typeAnalyzer.visitStringInterpolation(node as StringInterpolationImpl);
inferenceLogWriter?.exitExpression(node);
}
@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 whyNotPromotedArguments =
<Map<SharedTypeView, NonPromotionReason> Function()>[];
elementResolver.visitSuperConstructorInvocation(
node as SuperConstructorInvocationImpl,
);
InvocationInferrer<SuperConstructorInvocationImpl>(
resolver: this,
node: node,
argumentList: node.argumentList,
contextType: UnknownInferredType.instance,
whyNotPromotedArguments: whyNotPromotedArguments,
).resolveInvocation(rawType: node.element?.type);
checkForArgumentTypesNotAssignableInList(
node.argumentList,
whyNotPromotedArguments,
);
}
@override
void visitSuperExpression(
SuperExpression node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitSuperExpression(node);
typeAnalyzer.visitSuperExpression(node as SuperExpressionImpl);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitSuperFormalParameter(SuperFormalParameter node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
void visitSwitchExpression(
covariant SwitchExpressionImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
var previousExhaustiveness = legacySwitchExhaustiveness;
var staticType =
analyzeSwitchExpression(
node,
node.expression,
node.cases.length,
SharedTypeSchemaView(contextType),
).type.unwrapTypeView<TypeImpl>();
node.recordStaticType(staticType, resolver: this);
popRewrite();
legacySwitchExhaustiveness = previousExhaustiveness;
inferenceLogWriter?.exitExpression(node);
}
@override
void visitSwitchStatement(covariant SwitchStatementImpl node) {
inferenceLogWriter?.enterStatement(node);
// Stack: ()
checkUnreachableNode(node);
var previousExhaustiveness = legacySwitchExhaustiveness;
analyzeSwitchStatement(node, node.expression, node.memberGroups.length);
// Stack: (Expression)
popRewrite();
// Stack: ()
legacySwitchExhaustiveness = previousExhaustiveness;
inferenceLogWriter?.exitStatement(node);
}
@override
void visitSymbolLiteral(
SymbolLiteral node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
node.visitChildren(this);
typeAnalyzer.visitSymbolLiteral(node as SymbolLiteralImpl);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitThisExpression(
ThisExpression node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
node.visitChildren(this);
typeAnalyzer.visitThisExpression(node as ThisExpressionImpl);
_insertImplicitCallReference(node, contextType: contextType);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitThrowExpression(
covariant ThrowExpressionImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
analyzeExpression(
node.expression,
SharedTypeSchemaView(typeProvider.objectType),
);
popRewrite();
typeAnalyzer.visitThrowExpression(node);
flowAnalysis.flow?.handleExit();
inferenceLogWriter?.exitExpression(node);
}
@override
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitTopLevelVariableDeclaration(node);
}
@override
void visitTryStatement(covariant TryStatementImpl node) {
inferenceLogWriter?.enterStatement(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);
nullSafetyDeadCodeVerifier.flowEnd(node.body);
nullSafetyDeadCodeVerifier.tryStatementEnter(node);
if (catchClauses.isNotEmpty) {
flow.tryCatchStatement_bodyEnd(body);
var catchLength = catchClauses.length;
for (var i = 0; i < catchLength; ++i) {
var catchClause = catchClauses[i];
nullSafetyDeadCodeVerifier.verifyCatchClause(catchClause);
// TODO(paulberry): try to remove these casts by changing `node` to a
// `TryStatementImpl`
flow.tryCatchStatement_catchBegin(
catchClause.exceptionParameter?.declaredElement2
as PromotableElementImpl2?,
catchClause.stackTraceParameter?.declaredElement2
as PromotableElementImpl2?,
);
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();
}
inferenceLogWriter?.exitStatement(node);
}
@override
void visitTypeArgumentList(TypeArgumentList node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
void visitTypeLiteral(
covariant TypeLiteralImpl node, {
TypeImpl contextType = UnknownInferredType.instance,
}) {
inferenceLogWriter?.enterExpression(node, contextType);
checkUnreachableNode(node);
node.visitChildren(this);
node.recordStaticType(typeProvider.typeType, resolver: this);
inferenceLogWriter?.exitExpression(node);
}
@override
void visitTypeParameter(TypeParameter node) {
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitTypeParameter(node);
}
@override
void visitTypeParameterList(TypeParameterList node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
void visitVariableDeclaration(covariant VariableDeclarationImpl node) {
var fragment = node.declaredFragment!;
libraryResolutionContext._variableNodes[fragment] = node;
_variableDeclarationResolver.resolve(node);
var initializer = node.initializer;
if (initializer != null) {
var parent = node.parent as VariableDeclarationList;
var declaredType = parent.type;
var initializerStaticType = initializer.typeOrThrow;
flowAnalysis.flow?.initialize(
node.declaredElement2 as PromotableElementImpl2,
SharedTypeView(initializerStaticType),
initializer,
isFinal: parent.isFinal,
isLate: parent.isLate,
isImplicitlyTyped: declaredType == null,
);
}
_checkTopLevelCycle(node);
}
@override
void visitVariableDeclarationList(VariableDeclarationList node) {
flowAnalysis.variableDeclarationList(node);
checkUnreachableNode(node);
node.visitChildren(this);
elementResolver.visitVariableDeclarationList(node);
}
@override
void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
inferenceLogWriter?.enterStatement(node);
checkUnreachableNode(node);
node.visitChildren(this);
inferenceLogWriter?.exitStatement(node);
}
@override
void visitWhileStatement(covariant WhileStatementImpl node) {
inferenceLogWriter?.enterStatement(node);
checkUnreachableNode(node);
ExpressionImpl condition = node.condition;
flowAnalysis.flow?.whileStatement_conditionBegin(node);
analyzeExpression(condition, SharedTypeSchemaView(typeProvider.boolType));
condition = popRewrite()!;
var whyNotPromoted = flowAnalysis.flow?.whyNotPromoted(condition);
boolExpressionVerifier.checkForNonBoolCondition(
node.condition,
whyNotPromoted: whyNotPromoted,
);
flowAnalysis.flow?.whileStatement_bodyBegin(node, condition);
node.body.accept(this);
flowAnalysis.flow?.whileStatement_end();
nullSafetyDeadCodeVerifier.flowEnd(node.body);
// TODO(brianwilkerson): If the loop can only be exited because the condition
// is false, then propagateFalseState(condition);
inferenceLogWriter?.exitStatement(node);
}
@override
void visitWithClause(WithClause node) {
checkUnreachableNode(node);
node.visitChildren(this);
}
@override
void visitYieldStatement(covariant YieldStatementImpl node) {
inferenceLogWriter?.enterStatement(node);
checkUnreachableNode(node);
_yieldStatementResolver.resolve(node);
inferenceLogWriter?.exitStatement(node);
}
/// Check whether [errorNode] is an `onError` callback in a
/// [Future.catchError] call, which might return an implicit `null`.
void _checkForFutureCatchErrorOnError(BlockFunctionBody errorNode) {
// Check for "body might complete normally" in a `Future.catchError`'s
//`onError` callback.
var parent = errorNode.parent?.parent;
if (parent is! ArgumentList) {
return;
}
var invocation = parent.parent;
if (invocation is! MethodInvocation) {
return;
}
var targetType = invocation.realTarget?.staticType;
if (invocation.methodName.name == 'catchError' &&
targetType is InterfaceTypeImpl) {
var instanceOfFuture = targetType.asInstanceOf2(
typeProvider.futureElement2,
);
if (instanceOfFuture != null) {
var targetFutureType = instanceOfFuture.typeArguments.first;
var expectedReturnType = typeProvider.futureOrType(targetFutureType);
var returnTypeBase = typeSystem.futureOrBase(expectedReturnType);
if (returnTypeBase is DynamicType ||
returnTypeBase is UnknownInferredType ||
returnTypeBase is VoidType ||
returnTypeBase.isDartCoreNull) {
return;
}
errorReporter.atToken(
errorNode.block.leftBracket,
WarningCode.BODY_MIGHT_COMPLETE_NORMALLY_CATCH_ERROR,
arguments: [returnTypeBase],
);
}
}
}
void _checkTopLevelCycle(VariableDeclaration node) {
var fragment = node.declaredFragment;
if (fragment is! PropertyInducingElementImpl) {
return;
}
// Errors on const are reported separately with
// [CompileTimeErrorCode.RECURSIVE_COMPILE_TIME_CONSTANT].
if (fragment.isConst) {
return;
}
var error = fragment.typeInferenceError;
if (error == null) {
return;
}
if (error.kind == TopLevelInferenceErrorKind.dependencyCycle) {
var argumentsText = error.arguments.join(', ');
errorReporter.atToken(
node.name,
CompileTimeErrorCode.TOP_LEVEL_CYCLE,
arguments: [node.name.lexeme, argumentsText],
);
}
}
/// Creates a union of `T | Future<T>`, unless `T` is already a
/// future-union, in which case it simply returns `T`.
TypeImpl _createFutureOr(TypeImpl type) {
if (type.isDartAsyncFutureOr) {
return type;
}
return typeProvider.futureOrType(type);
}
/// Helper function used to print information to the console in debug mode.
/// This method returns `true` so that it can be conveniently called inside of
/// an `assert` statement.
bool _debugPrint(String s) {
print(s);
return true;
}
TypeImpl _finishFunctionBodyInference() {
var flow = flowAnalysis.flow;
return _bodyContext!.computeInferredReturnType(
endOfBlockIsReachable: flow == null || flow.isReachable,
);
}
/// Infers type arguments corresponding to [typeParameters] used it the
/// [declaredType], so that thr resulting type is a subtype of [contextType].
List<TypeImpl> _inferTypeArguments({
required List<TypeParameterElementImpl2> typeParameters,
required AstNode errorNode,
required TypeImpl declaredType,
required TypeImpl contextType,
required AstNodeImpl? nodeForTesting,
}) {
inferenceLogWriter?.enterGenericInference(typeParameters, declaredType);
var inferrer = GenericInferrer(
typeSystem,
typeParameters,
errorEntity: errorNode,
genericMetadataIsEnabled: genericMetadataIsEnabled,
inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled,
strictInference: analysisOptions.strictInference,
typeSystemOperations: flowAnalysis.typeOperations,
dataForTesting: inferenceHelper.dataForTesting,
);
inferrer.constrainReturnType(
declaredType,
contextType,
nodeForTesting: nodeForTesting,
);
return inferrer.chooseFinalTypes();
}
/// If `expression` should be treated as `expression.call`, inserts an
/// [ImplicitCallReference] node which wraps [expression].
void _insertImplicitCallReference(
ExpressionImpl expression, {
required TypeImpl contextType,
}) {
var parent = expression.parent;
if (_shouldSkipImplicitCallReferenceDueToForm(expression, parent)) {
return;
}
var staticType = expression.staticType;
if (staticType == null) {
return;
}
TypeImpl context;
if (parent is AssignmentExpressionImpl) {
if (parent.writeType == null) return;
context = parent.writeType!;
} else {
context = contextType;
}
var callMethod = getImplicitCallMethod(staticType, context, expression);
if (callMethod == null) {
return;
}
// `expression` is to be treated as `expression.call`.
context = typeSystem.flatten(context);
var callMethodType = callMethod.type;
List<DartType> typeArgumentTypes;
if (isConstructorTearoffsEnabled &&
callMethodType.typeFormals.isNotEmpty &&
context is FunctionTypeImpl) {
typeArgumentTypes = typeSystem.inferFunctionTypeInstantiation(
context,
callMethodType,
errorReporter: errorReporter,
errorNode: expression,
// If the constructor-tearoffs feature is enabled, then so is
// generic-metadata.
genericMetadataIsEnabled: true,
inferenceUsingBoundsIsEnabled: inferenceUsingBoundsIsEnabled,
strictInference: analysisOptions.strictInference,
strictCasts: analysisOptions.strictCasts,
typeSystemOperations: flowAnalysis.typeOperations,
dataForTesting: inferenceHelper.dataForTesting,
nodeForTesting: expression,
);
if (typeArgumentTypes.isNotEmpty) {
callMethodType = callMethodType.instantiate(typeArgumentTypes);
}
} else {
typeArgumentTypes = [];
}
var callReference = ImplicitCallReferenceImpl(
expression: expression,
element: callMethod,
typeArguments: null,
typeArgumentTypes: typeArgumentTypes,
);
replaceExpression(expression, callReference, parent: parent);
callReference.setPseudoExpressionStaticType(callMethodType);
}
void _resolvePropertyAccessRhs(
PropertyAccessImpl node,
TypeImpl contextType,
) {
if (node.isNullAware) {
_startNullAwareAccess(node, node.target);
nullSafetyDeadCodeVerifier.visitNode(node.propertyName);
}
var result = _propertyElementResolver.resolvePropertyAccess(
node: node,
hasRead: true,
hasWrite: false,
);
_resolvePropertyAccessRhs_common(
result,
node,
node.propertyName,
contextType,
);
nullSafetyDeadCodeVerifier.verifyPropertyAccess(node);
}
/// Common logic for resolving dot shorthands property accesses and
/// [_resolvePropertyAccessRhs].
void _resolvePropertyAccessRhs_common(
PropertyElementResolverResult resolverResult,
ExpressionImpl node,
SimpleIdentifierImpl propertyName,
TypeImpl contextType,
) {
var element = resolverResult.readElement2;
propertyName.element = element;
DartType type;
if (element is MethodElement) {
type = element.type;
} else if (element is ConstructorElementMixin2) {
type = element.type;
} else if (element is GetterElement) {
type = resolverResult.getType!;
} else if (resolverResult.functionTypeCallType != null) {
type = resolverResult.functionTypeCallType!;
} else if (resolverResult.recordField != null) {
type = resolverResult.recordField!.type;
} else if (resolverResult.atDynamicTarget) {
type = DynamicTypeImpl.instance;
} else {
type = InvalidTypeImpl.instance;
}
if (!isConstructorTearoffsEnabled) {
// Only perform a generic function instantiation on a [PrefixedIdentifier]
// in pre-constructor-tearoffs code. In constructor-tearoffs-enabled code,
// generic function instantiation is performed at assignability check
// sites.
// TODO(srawlins): Switch all resolution to use the latter method, in a
// breaking change release.
type = inferenceHelper.inferTearOff(
node,
propertyName,
type,
contextType: contextType,
);
}
propertyName.setPseudoExpressionStaticType(type);
node.recordStaticType(type, resolver: this);
var replacement = insertGenericFunctionInstantiation(
node,
contextType: contextType,
);
nullShortingTermination(node, rewrittenExpression: replacement);
_insertImplicitCallReference(replacement, contextType: contextType);
}
/// 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(
FunctionExpressionInvocationImpl node,
List<WhyNotPromotedGetter> whyNotPromotedArguments, {
required TypeImpl contextType,
}) {
var function = node.function;
if (function is PropertyAccessImpl && function.isNullAware) {
_startNullAwareAccess(function, function.target);
nullSafetyDeadCodeVerifier.visitNode(node.argumentList);
}
_functionExpressionInvocationResolver.resolve(
node,
whyNotPromotedArguments,
contextType: contextType,
);
nullShortingTermination(node);
}
void _setupThisType() {
var enclosingClass = this.enclosingClass;
if (enclosingClass != null) {
_thisType = enclosingClass.thisType;
} else {
var enclosingExtension = this.enclosingExtension;
if (enclosingExtension != null) {
_thisType = enclosingExtension.extendedType;
}
}
}
bool _shouldSkipImplicitCallReferenceDueToForm(
Expression expression,
AstNode? parent,
) {
while (parent is ParenthesizedExpression) {
expression = parent;
parent = expression.parent;
}
if (parent is CascadeExpression && parent.target == expression) {
// Do not perform an "implicit tear-off conversion" here. It should only
// be performed on [parent]. See
// https://github.com/dart-lang/language/issues/1873.
return true;
}
if (parent is ConditionalExpression &&
(parent.thenExpression == expression ||
parent.elseExpression == expression)) {
// Do not perform an "implicit tear-off conversion" on the branches of a
// conditional expression.
return true;
}
if (parent is BinaryExpression &&
parent.operator.type == TokenType.QUESTION_QUESTION) {
// Do not perform an "implicit tear-off conversion" on the branches of a
// `??` operator.
return true;
}
return false;
}
void _startNullAwareAccess(
NullShortableExpression node,
ExpressionImpl? target,
) {
var flow = flowAnalysis.flow;
if (flow != null) {
switch (target) {
case null:
// This means the property access target is the target of a cascade.
// For this case, `node.isNullAware=true` means that the cascade is
// null aware, but that has already been taken care of in
// `visitCascadeExpression`. So there is nothing further to do.
break;
case SimpleIdentifier(element: InterfaceElement()):
// `?.` to access static methods is equivalent to `.`, so do nothing.
break;
case ExtensionOverride(
argumentList: ArgumentListImpl(arguments: [var expression]),
):
case var expression:
flow.nullAwareAccess_rightBegin(
expression,
SharedTypeView(expression.staticType ?? typeProvider.dynamicType),
);
_unfinishedNullShorts.add(node.nullShortingTermination);
}
}
}
/// 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.
///
/// Returns the parameters that correspond to the arguments. If no parameter
/// matched an argument, that position will be `null` in the list.
static List<FormalParameterElementMixin?> resolveArgumentsToParameters({
required ArgumentList argumentList,
required List<FormalParameterElement> formalParameters,
ErrorReporter? errorReporter,
ConstructorDeclaration? enclosingConstructor,
}) {
int requiredParameterCount = 0;
int unnamedParameterCount = 0;
var unnamedParameters = <FormalParameterElementMixin>[];
Map<String, FormalParameterElementMixin>? namedParameters;
int length = formalParameters.length;
for (int i = 0; i < length; i++) {
var parameter = formalParameters[i] as FormalParameterElementMixin;
if (parameter.isRequiredPositional) {
unnamedParameters.add(parameter);
unnamedParameterCount++;
requiredParameterCount++;
} else if (parameter.isOptionalPositional) {
unnamedParameters.add(parameter);
unnamedParameterCount++;
} else {
namedParameters ??= {};
namedParameters[parameter.name3 ?? ''] = parameter;
}
}
int unnamedIndex = 0;
NodeList<Expression> arguments = argumentList.arguments;
int argumentCount = arguments.length;
var resolvedParameters = List<FormalParameterElementMixin?>.filled(
argumentCount,
null,
);
int positionalArgumentCount = 0;
bool noBlankArguments = true;
Expression? firstUnresolvedArgument;
Expression? lastPositionalArgument;
for (int i = 0; i < argumentCount; i++) {
Expression argument = arguments[i];
if (argument is! NamedExpression) {
if (argument is SimpleIdentifier && argument.name.isEmpty) {
noBlankArguments = false;
}
positionalArgumentCount++;
if (unnamedIndex < unnamedParameterCount) {
resolvedParameters[i] = unnamedParameters[unnamedIndex++];
} else {
firstUnresolvedArgument ??= argument;
}
lastPositionalArgument = argument;
}
}
Set<String>? usedNames;
if (enclosingConstructor != null) {
var result = verifySuperFormalParameters(
constructor: enclosingConstructor,
hasExplicitPositionalArguments: positionalArgumentCount != 0,
errorReporter: errorReporter,
);
positionalArgumentCount += result.positionalArgumentCount;
if (result.namedArgumentNames.isNotEmpty) {
usedNames = result.namedArgumentNames.toSet();
}
}
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) {
errorReporter?.atNode(
nameNode,
CompileTimeErrorCode.UNDEFINED_NAMED_PARAMETER,
arguments: [name],
);
} else {
resolvedParameters[i] = element;
nameNode.element = element;
}
usedNames ??= <String>{};
if (!usedNames.add(name)) {
errorReporter?.atNode(
nameNode,
CompileTimeErrorCode.DUPLICATE_NAMED_ARGUMENT,
arguments: [name],
);
}
}
}
if (positionalArgumentCount < requiredParameterCount && noBlankArguments) {
var parent = argumentList.parent;
if (errorReporter != null && parent != null) {
var token =
lastPositionalArgument?.endToken.next ??
argumentList.leftParenthesis.next ??
argumentList.rightParenthesis;
_reportNotEnoughPositionalArguments(
token: token,
requiredParameterCount: requiredParameterCount,
actualArgumentCount: positionalArgumentCount,
nameNode: parent,
errorReporter: errorReporter,
);
}
} else if (positionalArgumentCount > unnamedParameterCount &&
noBlankArguments) {
DiagnosticCode diagnosticCode;
int namedParameterCount = namedParameters?.length ?? 0;
int namedArgumentCount = usedNames?.length ?? 0;
if (namedParameterCount > namedArgumentCount) {
diagnosticCode =
CompileTimeErrorCode.EXTRA_POSITIONAL_ARGUMENTS_COULD_BE_NAMED;
} else {
diagnosticCode = CompileTimeErrorCode.EXTRA_POSITIONAL_ARGUMENTS;
}
if (firstUnresolvedArgument != null) {
errorReporter?.atNode(
firstUnresolvedArgument,
diagnosticCode,
arguments: [unnamedParameterCount, positionalArgumentCount],
);
}
}
return resolvedParameters;
}
/// Debug-only: verifies that [list] is a modifiable list by setting its
/// length to itself.
///
/// For a normal list this is a no-op; for an unmodifiable (i.e. const) list,
/// this will cause an exception to be thrown.
static bool _isModifiableList(List<Object?> list) {
try {
list.length = list.length;
} catch (_) {
return false;
}
return true;
}
/// Reports [CompileTimeErrorCode.NOT_ENOUGH_POSITIONAL_ARGUMENTS_SINGULAR] or
/// [CompileTimeErrorCode.NOT_ENOUGH_POSITIONAL_ARGUMENTS_PLURAL] at the
/// specified [token], considering the name of the [nameNode].
static void _reportNotEnoughPositionalArguments({
required Token token,
required int requiredParameterCount,
required int actualArgumentCount,
required AstNode nameNode,
required ErrorReporter errorReporter,
}) {
String? name;
if (nameNode is InstanceCreationExpression) {
var constructorName = nameNode.constructorName;
name =
constructorName.name?.name ??
'${constructorName.type.name.lexeme}.new';
} else if (nameNode is RedirectingConstructorInvocation) {
name = nameNode.constructorName?.name;
if (name == null) {
var element = nameNode.element;
if (element != null) {
name = '${element.returnType.getDisplayString()}.new';
}
}
} else if (nameNode is SuperConstructorInvocation) {
name = nameNode.constructorName?.name;
if (name == null) {
var element = nameNode.element;
if (element != null) {
name = '${element.returnType.getDisplayString()}.new';
}
}
} else if (nameNode is MethodInvocation) {
name = nameNode.methodName.name;
} else if (nameNode is FunctionExpressionInvocation) {
var function = nameNode.function;
if (function is SimpleIdentifier) {
name = function.name;
}
} else if (nameNode is EnumConstantArguments) {
var parent = nameNode.parent;
if (parent is EnumConstantDeclaration) {
var declaredElement = parent.declaredFragment!.element;
name = declaredElement.type.getDisplayString();
}
} else if (nameNode is EnumConstantDeclaration) {
var declaredElement = nameNode.declaredFragment!.element;
name = declaredElement.type.getDisplayString();
} else if (nameNode is Annotation) {
var nameNodeName = nameNode.name;
name =
nameNodeName is PrefixedIdentifier
? nameNodeName.identifier.name
: '${nameNodeName.name}.new';
} else if (nameNode is DotShorthandConstructorInvocation) {
name = nameNode.constructorName.name;
} else if (nameNode is DotShorthandInvocation) {
name = nameNode.memberName.name;
} else {
throw UnimplementedError('(${nameNode.runtimeType}) $nameNode');
}
var isPlural = requiredParameterCount > 1;
var arguments = <Object>[];
if (isPlural) {
arguments.add(requiredParameterCount);
arguments.add(actualArgumentCount);
}
DiagnosticCode diagnosticCode;
if (name == null) {
diagnosticCode =
isPlural
? CompileTimeErrorCode.NOT_ENOUGH_POSITIONAL_ARGUMENTS_PLURAL
: CompileTimeErrorCode.NOT_ENOUGH_POSITIONAL_ARGUMENTS_SINGULAR;
} else {
diagnosticCode =
isPlural
? CompileTimeErrorCode.NOT_ENOUGH_POSITIONAL_ARGUMENTS_NAME_PLURAL
: CompileTimeErrorCode
.NOT_ENOUGH_POSITIONAL_ARGUMENTS_NAME_SINGULAR;
arguments.add(name);
}
errorReporter.atToken(token, diagnosticCode, arguments: arguments);
}
}
/// Instances of the class `ScopeResolverVisitor` are used to resolve
/// [SimpleIdentifier]s to declarations using scoping rules.
///
// TODO(paulberry): migrate the responsibility for all scope resolution into
// this visitor.
class ScopeResolverVisitor extends UnifyingAstVisitor<void> {
/// 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 of libraries imported by `@docImport`s.
final DocumentationCommentScope _docImportScope;
/// 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 container with information about local variables.
final LocalVariableInfo _localVariableInfo = LocalVariableInfo();
/// If the current function is contained within a closure (a local function or
/// function expression inside another executable declaration), the element
/// representing the closure; otherwise `null`.
LocalFunctionElement? _enclosingClosure;
/// Initialize a newly created visitor to resolve the nodes in an AST node.
///
/// [errorReporter] is the error reporter 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.
ScopeResolverVisitor(
this.errorReporter, {
required this.nameScope,
List<LibraryElement> docImportLibraries = const [],
}) : _docImportScope = DocumentationCommentScope(
nameScope,
docImportLibraries,
);
/// Return the implicit label scope in which the current node is being
/// resolved.
ImplicitLabelScope get implicitLabelScope => _implicitLabelScope;
@override
void visitAssignedVariablePattern(AssignedVariablePattern node) {
var element = node.element2;
if (element is PromotableElement) {
_localVariableInfo.potentiallyMutatedInScope.add(element);
}
}
@override
void visitBlock(covariant BlockImpl 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 visitBreakStatement(covariant BreakStatementImpl node) {
node.target = _lookupBreakOrContinueTarget(node, node.label, false);
}
@override
void visitCatchClause(CatchClause node) {
var exception = node.exceptionParameter;
if (exception != null) {
Scope outerScope = nameScope;
try {
nameScope = LocalScope(nameScope);
_define(exception.declaredElement2!);
var stackTrace = node.stackTraceParameter;
if (stackTrace != null) {
_define(stackTrace.declaredElement2!);
}
super.visitCatchClause(node);
} finally {
nameScope = outerScope;
}
} else {
super.visitCatchClause(node);
}
}
@override
void visitClassDeclaration(covariant ClassDeclarationImpl node) {
Scope outerScope = nameScope;
try {
var element = node.declaredFragment!.element;
node.metadata.accept(this);
nameScope = TypeParameterScope(nameScope, element.typeParameters2);
node.nameScope = nameScope;
node.typeParameters?.accept(this);
node.extendsClause?.accept(this);
node.withClause?.accept(this);
node.implementsClause?.accept(this);
node.nativeClause?.accept(this);
nameScope = InstanceScope(nameScope, element);
_visitDocumentationComment(node.documentationComment);
node.members.accept(this);
} finally {
nameScope = outerScope;
}
}
@override
void visitClassTypeAlias(covariant ClassTypeAliasImpl node) {
node.metadata.accept(this);
Scope outerScope = nameScope;
try {
var element = node.declaredFragment!.element;
nameScope = InstanceScope(
TypeParameterScope(nameScope, element.typeParameters2),
element,
);
_visitDocumentationComment(node.documentationComment);
node.typeParameters?.accept(this);
node.superclass.accept(this);
node.withClause.accept(this);
node.implementsClause?.accept(this);
} finally {
nameScope = outerScope;
}
}
@override
void visitCompilationUnit(covariant CompilationUnitImpl node) {
node.nameScope = nameScope;
super.visitCompilationUnit(node);
}
@override
void visitConstructorDeclaration(covariant ConstructorDeclarationImpl node) {
node.body.localVariableInfo = _localVariableInfo;
Scope outerScope = nameScope;
try {
var element = node.declaredFragment!.element;
node.metadata.accept(this);
node.returnType.accept(this);
node.parameters.accept(this);
try {
nameScope = ConstructorInitializerScope(nameScope, element);
node.initializers.accept(this);
_visitDocumentationComment(node.documentationComment);
} finally {
nameScope = outerScope;
}
node.redirectedConstructor?.accept(this);
nameScope = FormalParameterScope(nameScope, element.formalParameters);
node.body.accept(this);
} finally {
nameScope = outerScope;
}
}
@override
void visitContinueStatement(covariant ContinueStatementImpl node) {
node.target = _lookupBreakOrContinueTarget(node, node.label, true);
}
@override
void visitDeclaredIdentifier(DeclaredIdentifier node) {
_define(node.declaredFragment!.element);
super.visitDeclaredIdentifier(node);
}
@override
void visitDoStatement(DoStatement node) {
ImplicitLabelScope outerImplicitScope = _implicitLabelScope;
try {
_implicitLabelScope = _implicitLabelScope.nest(node);
_visitStatementInScope(node.body);
node.condition.accept(this);
} finally {
_implicitLabelScope = outerImplicitScope;
}
}
@override
void visitEnumConstantDeclaration(
covariant EnumConstantDeclarationImpl node,
) {
node.metadata.accept(this);
_visitDocumentationComment(node.documentationComment);
node.arguments?.accept(this);
}
@override
void visitEnumDeclaration(covariant EnumDeclarationImpl node) {
Scope outerScope = nameScope;
try {
var element = node.declaredFragment!.element;
node.metadata.accept(this);
nameScope = TypeParameterScope(nameScope, element.typeParameters2);
node.nameScope = nameScope;
node.typeParameters?.accept(this);
node.withClause?.accept(this);
node.implementsClause?.accept(this);
nameScope = InstanceScope(nameScope, element);
_visitDocumentationComment(node.documentationComment);
node.constants.accept(this);
node.members.accept(this);
} finally {
nameScope = outerScope;
}
}
@override
void visitExpressionFunctionBody(covariant ExpressionFunctionBodyImpl node) {
node.nameScope = nameScope;
super.visitExpressionFunctionBody(node);
}
@override
void visitExtensionDeclaration(covariant ExtensionDeclarationImpl node) {
var outerScope = nameScope;
try {
var element = node.declaredFragment!.element;
node.metadata.accept(this);
nameScope = TypeParameterScope(nameScope, element.typeParameters2);
node.nameScope = nameScope;
node.typeParameters?.accept(this);
node.onClause?.accept(this);
nameScope = ExtensionScope(nameScope, element);
_visitDocumentationComment(node.documentationComment);
node.members.accept(this);
} finally {
nameScope = outerScope;
}
}
@override
void visitExtensionTypeDeclaration(
covariant ExtensionTypeDeclarationImpl node,
) {
Scope outerScope = nameScope;
try {
var element = node.declaredFragment!.element;
node.metadata.accept(this);
nameScope = TypeParameterScope(nameScope, element.typeParameters2);
node.nameScope = nameScope;
node.typeParameters?.accept(this);
node.representation.accept(this);
node.implementsClause?.accept(this);
nameScope = InstanceScope(nameScope, element);
_visitDocumentationComment(node.documentationComment);
node.members.accept(this);
} finally {
nameScope = outerScope;
}
}
@override
void visitFieldDeclaration(covariant FieldDeclarationImpl node) {
node.metadata.accept(this);
_visitDocumentationComment(node.documentationComment);
node.fields.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 visitForEachPartsWithPattern(
covariant ForEachPartsWithPatternImpl node,
) {
// We visit the iterator before the pattern because the pattern variables
// cannot be in scope while visiting the iterator.
node.iterable.accept(this);
for (var variable in node.variables) {
_define(variable.asElement2);
}
node.pattern.accept(this);
}
@override
void visitForElement(covariant ForElementImpl node) {
Scope outerNameScope = nameScope;
try {
nameScope = LocalScope(nameScope);
node.nameScope = nameScope;
node.forLoopParts.accept(this);
node.body.accept(this);
} finally {
nameScope = outerNameScope;
}
}
@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) {
var element = parent.declaredFragment!.element;
nameScope = FormalParameterScope(nameScope, element.formalParameters);
} else if (parent is FunctionTypeAlias) {
var aliasedElement = parent.declaredFragment!.element.aliasedElement2;
var functionElement = aliasedElement as GenericFunctionTypeElement;
nameScope = FormalParameterScope(
nameScope,
functionElement.formalParameters,
);
} else if (parent is MethodDeclaration) {
var element = parent.declaredFragment!.element;
nameScope = FormalParameterScope(nameScope, element.formalParameters);
}
}
@override
void visitForStatement(covariant ForStatementImpl node) {
Scope outerNameScope = nameScope;
ImplicitLabelScope outerImplicitScope = _implicitLabelScope;
try {
nameScope = LocalScope(nameScope);
_implicitLabelScope = _implicitLabelScope.nest(node);
node.nameScope = nameScope;
node.forLoopParts.accept(this);
_visitStatementInScope(node.body);
} finally {
nameScope = outerNameScope;
_implicitLabelScope = outerImplicitScope;
}
}
@override
void visitFunctionDeclaration(covariant FunctionDeclarationImpl node) {
node.functionExpression.body.localVariableInfo = _localVariableInfo;
var outerClosure = _enclosingClosure;
Scope outerScope = nameScope;
try {
var element = node.declaredFragment!.element;
_enclosingClosure = element.ifTypeOrNull<LocalFunctionElement>();
node.metadata.accept(this);
nameScope = TypeParameterScope(nameScope, element.typeParameters2);
node.nameScope = nameScope;
node.returnType?.accept(this);
node.functionExpression.accept(this);
} finally {
nameScope = outerScope;
_enclosingClosure = outerClosure;
}
}
@override
void visitFunctionExpression(FunctionExpression node) {
var outerClosure = _enclosingClosure;
Scope outerScope = nameScope;
try {
var element = node.declaredFragment!.element;
if (node.parent is! FunctionDeclaration) {
(node.body as FunctionBodyImpl).localVariableInfo = _localVariableInfo;
_enclosingClosure = element as LocalFunctionElement;
}
var parent = node.parent;
if (parent is FunctionDeclarationImpl) {
// We have already created a function scope and don't need to do so again.
super.visitFunctionExpression(node);
_visitDocumentationComment(parent.documentationComment);
return;
}
nameScope = FormalParameterScope(
TypeParameterScope(nameScope, element.typeParameters2),
element.formalParameters,
);
super.visitFunctionExpression(node);
} finally {
nameScope = outerScope;
_enclosingClosure = outerClosure;
}
}
@override
void visitFunctionTypeAlias(covariant FunctionTypeAliasImpl node) {
node.metadata.accept(this);
Scope outerScope = nameScope;
try {
var element = node.declaredFragment!.element;
nameScope = TypeParameterScope(nameScope, element.typeParameters2);
node.returnType?.accept(this);
node.typeParameters?.accept(this);
node.parameters.accept(this);
// Visiting the parameters added them to the scope as a side effect. So it
// is safe to visit the documentation comment now.
_visitDocumentationComment(node.documentationComment);
} finally {
nameScope = outerScope;
}
}
@override
void visitFunctionTypedFormalParameter(
covariant FunctionTypedFormalParameterImpl node,
) {
node.metadata.accept(this);
Scope outerScope = nameScope;
try {
var element = node.declaredFragment!.element;
nameScope = TypeParameterScope(nameScope, element.typeParameters2);
_visitDocumentationComment(node.documentationComment);
node.returnType?.accept(this);
node.typeParameters?.accept(this);
node.parameters.accept(this);
} finally {
nameScope = outerScope;
}
}
@override
void visitGenericFunctionType(covariant GenericFunctionTypeImpl 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 {
var element = node.declaredFragment!.element;
nameScope = TypeParameterScope(nameScope, element.typeParameters2);
node.nameScope = nameScope;
super.visitGenericFunctionType(node);
} finally {
nameScope = outerScope;
}
}
@override
void visitGenericTypeAlias(covariant GenericTypeAliasImpl node) {
node.metadata.accept(this);
Scope outerScope = nameScope;
try {
var element = node.declaredFragment!.element;
nameScope = TypeParameterScope(nameScope, element.typeParameters2);
node.nameScope = nameScope;
node.typeParameters?.accept(this);
node.type.accept(this);
var aliasedElement = element.aliasedElement2;
if (aliasedElement is GenericFunctionTypeElement) {
nameScope = FormalParameterScope(
TypeParameterScope(nameScope, aliasedElement.typeParameters2),
aliasedElement.formalParameters,
);
}
_visitDocumentationComment(node.documentationComment);
} finally {
nameScope = outerScope;
}
}
@override
void visitGuardedPattern(covariant GuardedPatternImpl node) {
var patternVariables = node.variables.values.toList();
for (var variable in patternVariables) {
_define(variable);
}
node.pattern.accept(this);
for (var variable in patternVariables) {
variable.isVisitingWhenClause = true;
}
node.whenClause?.accept(this);
for (var variable in patternVariables) {
variable.isVisitingWhenClause = false;
}
}
@override
void visitHideCombinator(HideCombinator node) {
var scope = nameScope.ifTypeOrNull<LibraryFragmentScope>();
scope?.importsTrackingActive(false);
try {
super.visitHideCombinator(node);
} finally {
scope?.importsTrackingActive(true);
}
}
@override
void visitIfElement(covariant IfElementImpl node) {
_visitIf(node);
}
@override
void visitIfStatement(covariant IfStatementImpl node) {
_visitIf(node);
}
@override
void visitLabeledStatement(LabeledStatement node) {
var outerScope = _addScopesFor(node.labels, node.unlabeled);
try {
super.visitLabeledStatement(node);
} finally {
_labelScope = outerScope;
}
}
@override
void visitLibraryDirective(covariant LibraryDirectiveImpl node) {
node.metadata.accept(this);
_visitDocumentationComment(node.documentationComment);
}
@override
void visitLibraryIdentifier(LibraryIdentifier node) {}
@override
void visitMethodDeclaration(covariant MethodDeclarationImpl node) {
node.body.localVariableInfo = _localVariableInfo;
node.metadata.accept(this);
Scope outerScope = nameScope;
try {
var element = node.declaredFragment!.element;
nameScope = TypeParameterScope(nameScope, element.typeParameters2);
node.nameScope = nameScope;
node.returnType?.accept(this);
node.typeParameters?.accept(this);
node.parameters?.accept(this);
// Visiting the parameters added them to the scope as a side effect. So it
// is safe to visit the documentation comment now.
_visitDocumentationComment(node.documentationComment);
node.body.accept(this);
} finally {
nameScope = outerScope;
}
}
@override
void visitMethodInvocation(MethodInvocation node) {
// Only visit the method name if there's no real target (so this is an
// unprefixed function invocation, outside a cascade). This is the only
// circumstance in which the method name is meant to be looked up in the
// current scope.
node.target?.accept(this);
if (node.realTarget == null) {
node.methodName.accept(this);
}
node.typeArguments?.accept(this);
node.argumentList.accept(this);
}
@override
void visitMixinDeclaration(covariant MixinDeclarationImpl node) {
Scope outerScope = nameScope;
try {
var element = node.declaredFragment!.element;
node.metadata.accept(this);
nameScope = TypeParameterScope(nameScope, element.typeParameters2);
node.nameScope = nameScope;
node.typeParameters?.accept(this);
node.onClause?.accept(this);
node.implementsClause?.accept(this);
nameScope = InstanceScope(nameScope, element);
_visitDocumentationComment(node.documentationComment);
node.members.accept(this);
} finally {
nameScope = outerScope;
}
}
@override
void visitNamedType(NamedType node) {
// All TypeName(s) are already resolved, so we don't resolve it here.
// But there might be type arguments with Expression(s), such as
// annotations on formal parameters of GenericFunctionType(s).
node.typeArguments?.accept(this);
}
@override
void visitPatternVariableDeclaration(
covariant PatternVariableDeclarationImpl node,
) {
for (var variable in node.elements) {
_define(variable);
}
super.visitPatternVariableDeclaration(node);
}
@override
void visitPrefixedIdentifier(PrefixedIdentifier node) {
// Do not visit the identifier after the `.`, since it is not meant to be
// looked up in the current scope.
node.prefix.accept(this);
}
@override
void visitPropertyAccess(PropertyAccess node) {
// Do not visit the property name, since it is not meant to be looked up in
// the current scope.
node.target?.accept(this);
}
@override
void visitShowCombinator(ShowCombinator node) {
var scope = nameScope.ifTypeOrNull<LibraryFragmentScope>();
scope?.importsTrackingActive(false);
try {
super.visitShowCombinator(node);
} finally {
scope?.importsTrackingActive(true);
}
}
@override
void visitSimpleIdentifier(covariant SimpleIdentifierImpl node) {
// Ignore if already resolved - declaration or type.
if (node.inDeclarationContext()) {
return;
}
// Ignore if qualified.
var parent = node.parent;
if (parent is ConstructorName && parent.name == node) {
return;
}
if (parent is Label && parent.parent is NamedExpression) {
return;
}
var scopeLookupResult = nameScope.lookup(node.name);
node.scopeLookupResult = scopeLookupResult;
// Ignore if it cannot be a reference to a local variable.
if (parent is FieldFormalParameter) {
return;
} else if (parent is ConstructorDeclaration && parent.returnType == node) {
return;
} else if (parent is ConstructorFieldInitializer &&
parent.fieldName == node) {
return;
}
if (parent is Label) {
return;
}
// Prepare VariableElement.
var element = scopeLookupResult.getter2;
if (element is! VariableElement) {
return;
}
// Must be local or parameter.
ElementKind kind = element.kind;
if (kind == ElementKind.LOCAL_VARIABLE || kind == ElementKind.PARAMETER) {
node.element = element;
if (node.inSetterContext()) {
if (element is PatternVariableElementImpl2 &&
element.isVisitingWhenClause) {
errorReporter.atNode(
node,
CompileTimeErrorCode.PATTERN_VARIABLE_ASSIGNMENT_INSIDE_GUARD,
);
}
_localVariableInfo.potentiallyMutatedInScope.add(element);
}
}
if (element is JoinPatternVariableElementImpl2) {
element.references.add(node);
}
}
@override
void visitSwitchExpression(covariant SwitchExpressionImpl node) {
node.expression.accept(this);
for (var case_ in node.cases) {
_withNameScope(() {
case_.nameScope = nameScope;
var guardedPattern = case_.guardedPattern;
var variables = guardedPattern.variables;
for (var variable in variables.values) {
_define(variable);
}
case_.accept(this);
});
}
}
@override
void visitSwitchStatement(covariant SwitchStatementImpl node) {
var outerScope = _labelScope;
var outerImplicitScope = _implicitLabelScope;
try {
_implicitLabelScope = _implicitLabelScope.nest(node);
for (var member in node.members) {
for (var label in member.labels) {
var labelName = label.label;
var labelElement = labelName.element as LabelElement;
_labelScope = LabelScope(
_labelScope,
labelName.name,
member,
labelElement,
);
}
}
node.expression.accept(this);
for (var group in node.memberGroups) {
for (var member in group.members) {
if (member is SwitchCaseImpl) {
member.expression.accept(this);
} else if (member is SwitchPatternCaseImpl) {
_withNameScope(() {
member.guardedPattern.accept(this);
});
}
}
if (group.members.isEmpty) {
return;
}
var lastMember = group.members.last;
_withDeclaredLocals(lastMember, lastMember.statements, () {
for (var variable in group.variables.values) {
_define(variable);
}
lastMember.statements.accept(this);
});
}
} finally {
_labelScope = outerScope;
_implicitLabelScope = outerImplicitScope;
}
}
@override
void visitTopLevelVariableDeclaration(
covariant TopLevelVariableDeclarationImpl node,
) {
node.metadata.accept(this);
_visitDocumentationComment(node.documentationComment);
node.variables.accept(this);
}
@override
void visitVariableDeclaration(VariableDeclaration node) {
super.visitVariableDeclaration(node);
if (node.parent!.parent is ForParts) {
_define(node.declaredElement2!);
}
}
@override
void visitWhileStatement(WhileStatement node) {
node.condition.accept(this);
ImplicitLabelScope outerImplicitScope = _implicitLabelScope;
try {
_implicitLabelScope = _implicitLabelScope.nest(node);
_visitStatementInScope(node.body);
} finally {
_implicitLabelScope = outerImplicitScope;
}
}
/// Adds scopes for each of the given [labels].
///
/// Returns the scope that was in effect before the new scopes were added.
LabelScope? _addScopesFor(NodeList<Label> labels, AstNode node) {
var outerScope = _labelScope;
for (var label in labels) {
var labelNameNode = label.label;
var labelName = labelNameNode.name;
var labelElement = labelNameNode.element as LabelElement;
_labelScope = LabelScope(_labelScope, labelName, node, labelElement);
}
return outerScope;
}
void _define(Element element) {
(nameScope as LocalScope).add(element);
}
/// Return the target of a break or continue statement, and update the static
/// element of its label (if any). The [parentNode] is the AST node of the
/// break or continue statement. The [labelNode] is the label contained in
/// that statement (if any). The flag [isContinue] is `true` if the node being
/// visited is a continue statement.
AstNode? _lookupBreakOrContinueTarget(
AstNode parentNode,
SimpleIdentifierImpl? labelNode,
bool isContinue,
) {
if (labelNode == null) {
return implicitLabelScope.getTarget(isContinue);
} else {
var labelScope = _labelScope;
if (labelScope == null) {
// There are no labels in scope, so by definition the label is
// undefined.
errorReporter.atNode(
labelNode,
CompileTimeErrorCode.LABEL_UNDEFINED,
arguments: [labelNode.name],
);
return null;
}
var definingScope = labelScope.lookup(labelNode.name);
if (definingScope == null) {
// No definition of the given label name could be found in any
// enclosing scope.
errorReporter.atNode(
labelNode,
CompileTimeErrorCode.LABEL_UNDEFINED,
arguments: [labelNode.name],
);
return null;
}
// The target has been found.
labelNode.element = definingScope.element;
if (_enclosingClosure case var enclosingClosure?) {
var labelFragment = definingScope.element.firstFragment;
var labelContainer = labelFragment.enclosingFragment;
if (!identical(labelContainer, enclosingClosure.firstFragment)) {
errorReporter.atNode(
labelNode,
CompileTimeErrorCode.LABEL_IN_OUTER_SCOPE,
arguments: [labelNode.name],
);
}
}
var node = definingScope.node;
if (isContinue &&
node is! DoStatement &&
node is! ForStatement &&
node is! SwitchMember &&
node is! WhileStatement) {
errorReporter.atNode(
parentNode,
CompileTimeErrorCode.CONTINUE_LABEL_INVALID,
);
}
return node;
}
}
/// Visits a documentation comment with a [DocumentationCommentScope] that encloses the
/// current [nameScope].
void _visitDocumentationComment(CommentImpl? node) {
if (node == null) return;
Scope outerScope = nameScope;
Scope docImportInnerScope = _docImportScope.innerScope;
try {
_docImportScope.innerScope = nameScope;
nameScope = _docImportScope;
node.nameScope = nameScope;
node.accept(this);
} finally {
nameScope = outerScope;
_docImportScope.innerScope = docImportInnerScope;
}
}
void _visitIf(IfElementOrStatementImpl node) {
node.expression.accept(this);
var caseClause = node.caseClause;
if (caseClause != null) {
var guardedPattern = caseClause.guardedPattern;
_withNameScope(() {
caseClause.nameScope = nameScope;
var variables = guardedPattern.variables;
for (var variable in variables.values) {
_define(variable);
}
guardedPattern.accept(this);
node.ifTrue.accept(this);
});
node.ifFalse?.accept(this);
} else {
node.ifTrue.accept(this);
node.ifFalse?.accept(this);
}
}
/// Visits the given statement.
///
/// This is used by [ResolverVisitor] to correctly visit the 'then' and 'else'
/// statements of an 'if' statement.
void _visitStatementInScope(Statement? node) {
if (node is BlockImpl) {
// Don't create a scope around a block because the block will create it's
// own scope.
visitBlock(node);
} else if (node != null) {
var outerNameScope = nameScope;
try {
nameScope = LocalScope(nameScope);
node.accept(this);
} finally {
nameScope = outerNameScope;
}
}
}
void _withDeclaredLocals(
AstNodeWithNameScopeMixin node,
List<Statement> statements,
void Function() f,
) {
var outerScope = nameScope;
try {
var enclosedScope = LocalScope(nameScope);
for (var statement in BlockScope.elementsInStatements(statements)) {
if (!statement.isWildcardFunction) {
enclosedScope.add(statement);
}
}
nameScope = enclosedScope;
node.nameScope = nameScope;
f();
} finally {
nameScope = outerScope;
}
}
/// Run [f] with the new name scope.
void _withNameScope(void Function() f) {
var current = nameScope;
try {
nameScope = LocalScope(current);
f();
} finally {
nameScope = current;
}
}
/// Return the [Scope] to use while resolving inside the [node].
///
/// Not every node has the scope set, for example we set the scopes for
/// blocks, but statements don't have separate scopes. The compilation unit
/// has the library scope.
static Scope? getNodeNameScope(AstNode node) =>
node is AstNodeWithNameScopeMixin ? node.nameScope : null;
}
/// Tracker for whether a `switch` statement has `default` or is on an
/// enumeration, and all the enum constants are covered.
class SwitchExhaustiveness {
/// If the switch is on an enumeration, the set of enum constants to cover.
/// Otherwise `null`.
final Set<FieldElement>? _enumConstants;
/// If the switch is on an enumeration, is `true` if the null value is
/// covered, because the switch expression type is non-nullable, or `null`
/// was covered explicitly.
bool _isNullEnumValueCovered = false;
bool isExhaustive = false;
factory SwitchExhaustiveness(TypeImpl expressionType) {
if (expressionType is InterfaceType) {
var enum_ = expressionType.element3;
if (enum_ is EnumElementImpl2) {
return SwitchExhaustiveness._(
enum_.constants2.toSet(),
expressionType.nullabilitySuffix == NullabilitySuffix.none,
);
}
}
return SwitchExhaustiveness._(null, false);
}
SwitchExhaustiveness._(this._enumConstants, this._isNullEnumValueCovered);
void visitSwitchExpressionCase(SwitchExpressionCaseImpl node) {
if (_enumConstants != null) {
ExpressionImpl? caseConstant;
var guardedPattern = node.guardedPattern;
if (guardedPattern.whenClause == null) {
var pattern = guardedPattern.pattern.unParenthesized;
if (pattern is ConstantPatternImpl) {
caseConstant = pattern.expression;
}
}
_handleCaseConstant(caseConstant);
}
}
void visitSwitchMember(SwitchStatementCaseGroup group) {
for (var node in group.members) {
if (_enumConstants != null) {
ExpressionImpl? caseConstant;
if (node is SwitchCaseImpl) {
caseConstant = node.expression;
} else if (node is SwitchPatternCaseImpl) {
var guardedPattern = node.guardedPattern;
if (guardedPattern.whenClause == null) {
var pattern = guardedPattern.pattern.unParenthesized;
if (pattern is ConstantPatternImpl) {
caseConstant = pattern.expression;
}
}
}
_handleCaseConstant(caseConstant);
} else if (node is SwitchDefault) {
isExhaustive = true;
}
}
}
void _handleCaseConstant(ExpressionImpl? caseConstant) {
if (caseConstant != null) {
var element = _referencedElement(caseConstant);
if (element is PropertyAccessorElement) {
_enumConstants!.remove(element.variable3);
}
if (caseConstant is NullLiteral) {
_isNullEnumValueCovered = true;
}
if (_enumConstants!.isEmpty && _isNullEnumValueCovered) {
isExhaustive = true;
}
}
}
static Element? _referencedElement(Expression expression) {
if (expression is ParenthesizedExpression) {
return _referencedElement(expression.expression);
} else if (expression is PrefixedIdentifier) {
return expression.element;
} else if (expression is PropertyAccess) {
return expression.propertyName.element;
} else if (expression is SimpleIdentifier) {
return expression.element;
}
return null;
}
}
class _WhyNotPromotedVisitor
implements
NonPromotionReasonVisitor<
List<DiagnosticMessage>,
AstNode,
PromotableElement
> {
final Source source;
final SyntacticEntity _errorEntity;
final FlowAnalysisDataForTesting? _dataForTesting;
PropertyAccessorElement? propertyReference;
_WhyNotPromotedVisitor(this.source, this._errorEntity, this._dataForTesting);
@override
List<DiagnosticMessage> visitDemoteViaExplicitWrite(
DemoteViaExplicitWrite<PromotableElement> reason,
) {
var node = reason.node as AstNode;
if (node is ForEachPartsWithIdentifier) {
node = node.identifier;
}
if (_dataForTesting != null) {
_dataForTesting.nonPromotionReasonTargets[node] = reason.shortName;
}
var variableName = reason.variable.name3;
return [_contextMessageForWrite(variableName, node, reason)];
}
@override
List<DiagnosticMessage> visitPropertyNotPromotedForInherentReason(
PropertyNotPromotedForInherentReason reason,
) {
var receiverElement = reason.propertyMember;
if (receiverElement is PropertyAccessorElement) {
var property = propertyReference = receiverElement;
var propertyName = reason.propertyName;
String message = switch (reason.whyNotPromotable) {
shared.PropertyNonPromotabilityReason.isNotField =>
"'$propertyName' refers to a getter so it couldn't be promoted.",
shared.PropertyNonPromotabilityReason.isNotPrivate =>
"'$propertyName' refers to a public property so it couldn't be "
"promoted.",
shared.PropertyNonPromotabilityReason.isExternal =>
"'$propertyName' refers to an external field so it couldn't be "
"promoted.",
shared.PropertyNonPromotabilityReason.isNotFinal =>
"'$propertyName' refers to a non-final field so it couldn't be "
"promoted.",
};
return [
DiagnosticMessageImpl(
filePath: property.firstFragment.libraryFragment.source.fullName,
message: message,
offset: property.nonSynthetic2.firstFragment.nameOffset2!,
length: property.name3!.length,
url: reason.documentationLink.url,
),
if (!reason.fieldPromotionEnabled)
_fieldPromotionUnavailableMessage(property, propertyName),
];
} else {
assert(
receiverElement == null,
'Unrecognized property element: ${receiverElement.runtimeType}',
);
return [];
}
}
@override
List<DiagnosticMessage> visitPropertyNotPromotedForNonInherentReason(
PropertyNotPromotedForNonInherentReason reason,
) {
var receiverElement = reason.propertyMember;
if (receiverElement is PropertyAccessorElement) {
var property = propertyReference = receiverElement;
var propertyName = reason.propertyName;
var library = receiverElement.library2 as LibraryElementImpl;
var fieldNonPromotabilityInfo = library.fieldNameNonPromotabilityInfo;
var fieldNameInfo = fieldNonPromotabilityInfo[reason.propertyName];
var messages = <DiagnosticMessage>[];
void addConflictMessage({
required Element conflictingElement,
required String kind,
required Element enclosingElement,
required NonPromotionDocumentationLink link,
}) {
var enclosingKindName = enclosingElement.kind.displayName;
var enclosingName = enclosingElement.name3;
var message =
"'$propertyName' couldn't be promoted because there is a "
"conflicting $kind in $enclosingKindName '$enclosingName'";
var nonSyntheticElement = conflictingElement.nonSynthetic2;
var nonSyntheticFragment = nonSyntheticElement.firstFragment;
var source = nonSyntheticFragment.libraryFragment?.source;
messages.add(
DiagnosticMessageImpl(
filePath: source!.fullName,
message: message,
offset: nonSyntheticFragment.nameOffset2!,
length: nonSyntheticElement.name3!.length,
url: link.url,
),
);
}
if (fieldNameInfo != null) {
for (var field in fieldNameInfo.conflictingFields) {
addConflictMessage(
conflictingElement: field,
kind: 'non-promotable field',
enclosingElement: field.enclosingElement,
link: NonPromotionDocumentationLink.conflictingNonPromotableField,
);
}
for (var getter in fieldNameInfo.conflictingGetters) {
addConflictMessage(
conflictingElement: getter,
kind: 'getter',
enclosingElement: getter.enclosingElement,
link: NonPromotionDocumentationLink.conflictingGetter,
);
}
for (var nsmClass in fieldNameInfo.conflictingNsmClasses) {
addConflictMessage(
conflictingElement: nsmClass,
kind: 'noSuchMethod forwarder',
enclosingElement: nsmClass,
link:
NonPromotionDocumentationLink.conflictingNoSuchMethodForwarder,
);
}
}
if (reason.fieldPromotionEnabled) {
// The only possible non-inherent reasons for field promotion to fail
// are because of conflicts and because field promotion is disabled. So
// if field promotion is enabled, the loops above should have found a
// conflict.
assert(messages.isNotEmpty);
} else {
messages.add(_fieldPromotionUnavailableMessage(property, propertyName));
}
return messages;
} else {
assert(
receiverElement == null,
'Unrecognized property element: ${receiverElement.runtimeType}',
);
return [];
}
}
@override
List<DiagnosticMessage> visitThisNotPromoted(ThisNotPromoted reason) {
return [
DiagnosticMessageImpl(
filePath: source.fullName,
message: "'this' can't be promoted",
offset: _errorEntity.offset,
length: _errorEntity.length,
url: reason.documentationLink.url,
),
];
}
DiagnosticMessageImpl _contextMessageForWrite(
String? variableName,
AstNode node,
DemoteViaExplicitWrite<PromotableElement> reason,
) {
return DiagnosticMessageImpl(
filePath: source.fullName,
message:
"Variable '${variableName!}' could not be promoted due to an "
"assignment",
offset: node.offset,
length: node.length,
url: reason.documentationLink.url,
);
}
DiagnosticMessageImpl _fieldPromotionUnavailableMessage(
PropertyAccessorElement property,
String propertyName,
) {
return DiagnosticMessageImpl(
filePath: property.firstFragment.libraryFragment.source.fullName,
message:
"'$propertyName' couldn't be promoted "
"because field promotion is only available in Dart 3.2 and "
"above.",
offset: property.nonSynthetic2.firstFragment.nameOffset2!,
length: property.name3!.length,
url: NonPromotionDocumentationLink.fieldPromotionUnavailable.url,
);
}
}
extension on Element {
bool get isWildcardFunction =>
this is LocalFunctionElement &&
name3 == '_' &&
library2.hasWildcardVariablesFeatureEnabled;
}