| // Copyright (c) 2018, 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. |
| |
| // TODO(jensj): Probably all `_createVariableGet(result)` needs their offset |
| // "nulled out". |
| |
| import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart'; |
| 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' |
| hide MapPatternEntry; |
| import 'package:_fe_analyzer_shared/src/util/link.dart'; |
| import 'package:_fe_analyzer_shared/src/util/null_value.dart'; |
| import 'package:_fe_analyzer_shared/src/util/stack_checker.dart'; |
| import 'package:_fe_analyzer_shared/src/util/value_kind.dart'; |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/names.dart'; |
| import 'package:kernel/type_algebra.dart'; |
| import 'package:kernel/type_environment.dart'; |
| import 'package:kernel/src/non_null.dart'; |
| |
| import '../api_prototype/experimental_flags.dart'; |
| import '../api_prototype/lowering_predicates.dart'; |
| import '../base/instrumentation.dart' |
| show |
| InstrumentationValueForMember, |
| InstrumentationValueForType, |
| InstrumentationValueForTypeArgs; |
| import '../base/problems.dart' as problems |
| show internalProblem, unhandled, unsupported; |
| import '../base/uri_offset.dart'; |
| import '../codes/cfe_codes.dart'; |
| import '../kernel/body_builder.dart' show combineStatements; |
| import '../kernel/collections.dart' |
| show |
| ControlFlowElement, |
| ControlFlowMapEntry, |
| ForElement, |
| ForElementBase, |
| ForInElement, |
| ForInMapEntry, |
| ForMapEntry, |
| ForMapEntryBase, |
| IfCaseElement, |
| IfCaseMapEntry, |
| IfElement, |
| IfMapEntry, |
| NullAwareElement, |
| NullAwareMapEntry, |
| PatternForElement, |
| PatternForMapEntry, |
| SpreadElement, |
| SpreadMapEntry, |
| convertToElement; |
| import '../kernel/hierarchy/class_member.dart'; |
| import '../kernel/implicit_type_argument.dart' show ImplicitTypeArgument; |
| import '../kernel/internal_ast.dart'; |
| import '../kernel/late_lowering.dart' as late_lowering; |
| import '../source/constructor_declaration.dart'; |
| import '../source/source_library_builder.dart'; |
| import '../source/source_loader.dart'; |
| import 'closure_context.dart'; |
| import 'external_ast_helper.dart'; |
| import 'for_in.dart'; |
| import 'inference_helper.dart'; |
| import 'inference_results.dart'; |
| import 'inference_visitor_base.dart'; |
| import 'object_access_target.dart'; |
| import 'shared_type_analyzer.dart'; |
| import 'stack_values.dart'; |
| import 'type_constraint_gatherer.dart'; |
| import 'type_inference_engine.dart'; |
| import 'type_inferrer.dart' show TypeInferrerImpl; |
| import 'type_schema.dart' show UnknownType; |
| |
| abstract class InferenceVisitor { |
| /// Performs type inference on the given [expression]. |
| /// |
| /// [typeContext] is the expected type of the expression, based on surrounding |
| /// code. [typeNeeded] indicates whether it is necessary to compute the |
| /// actual type of the expression. If [typeNeeded] is `true`, |
| /// [ExpressionInferenceResult.inferredType] is the actual type of the |
| /// expression; otherwise the [UnknownType]. |
| /// |
| /// Derived classes should override this method with logic that dispatches on |
| /// the expression type and calls the appropriate specialized "infer" method. |
| ExpressionInferenceResult inferExpression( |
| Expression expression, DartType typeContext, |
| {bool isVoidAllowed = false, bool forEffect = false}); |
| |
| /// Performs type inference on the given [statement]. |
| /// |
| /// If [closureContext] is not null, the [statement] is inferred using |
| /// [closureContext] as the current context. |
| StatementInferenceResult inferStatement(Statement statement, |
| [ClosureContext? closureContext]); |
| |
| /// Performs type inference on the given [initializer]. |
| InitializerInferenceResult inferInitializer(Initializer initializer); |
| } |
| |
| class InferenceVisitorImpl extends InferenceVisitorBase |
| with |
| TypeAnalyzer< |
| TreeNode, |
| Statement, |
| Expression, |
| VariableDeclaration, |
| DartType, |
| Pattern, |
| InvalidExpression, |
| DartType, |
| StructuralParameter, |
| TypeDeclarationType, |
| TypeDeclaration>, |
| StackChecker |
| implements |
| ExpressionVisitor1<ExpressionInferenceResult, DartType>, |
| StatementVisitor<StatementInferenceResult>, |
| InitializerVisitor<InitializerInferenceResult>, |
| PatternVisitor1<PatternResult<DartType>, SharedMatchContext>, |
| InferenceVisitor { |
| /// Debug-only: if `true`, manipulations of [_rewriteStack] performed by |
| /// [popRewrite] and [pushRewrite] will be printed. |
| static const bool _debugRewriteStack = false; |
| |
| Class? mapEntryClass; |
| |
| @override |
| final OperationsCfe operations; |
| |
| /// Context information for the current closure, or `null` if we are not |
| /// inside a closure. |
| ClosureContext? _closureContext; |
| |
| /// If a switch statement is being visited and the type being switched on is a |
| /// (possibly nullable) enumerated type, the set of enum values for which no |
| /// case head has been seen yet; otherwise `null`. |
| /// |
| /// Enum values are represented by the [Field] object they are desugared into. |
| /// If the type being switched on is nullable, then this set also includes a |
| /// value of `null` if no case head has been seen yet that handles `null`. |
| Set<Field?>? _enumFields; |
| |
| /// Stack for obtaining rewritten expressions and statements. After |
| /// [dispatchExpression] or [dispatchStatement] visits a node for type |
| /// inference, the visited node (which may have been changed by the inference |
| /// process) is pushed onto this stack. Later, during the processing of the |
| /// enclosing node, the visited node is popped off the stack again, and the |
| /// enclosing node is updated to point to the new, rewritten node. |
| /// |
| /// 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 or statement to push. |
| final List<Object> _rewriteStack = []; |
| |
| @override |
| final TypeAnalyzerOptions options; |
| |
| final ConstructorDeclaration? constructorDeclaration; |
| |
| @override |
| late final SharedTypeAnalyzerErrors errors = new SharedTypeAnalyzerErrors( |
| visitor: this, |
| helper: helper, |
| uri: uriForInstrumentation, |
| coreTypes: coreTypes); |
| |
| /// The innermost cascade whose expressions are currently being visited, or |
| /// `null` if no cascade's expressions are currently being visited. |
| Cascade? _enclosingCascade; |
| |
| /// Set to `true` when we are inside a try-statement or a local function. |
| /// |
| /// This is used to optimize the encoding of [AssignedVariablePattern]. When |
| /// a pattern assignment occurs in a try block or a local function, a |
| /// partially matched pattern is observable, since exceptions occurring during |
| /// the matching can be caught. |
| // TODO(johnniwinther): This can be improved by detecting whether the assigned |
| // variable was declared outside the try statement or local function. |
| bool _inTryOrLocalFunction = false; |
| |
| InferenceVisitorImpl(TypeInferrerImpl inferrer, InferenceHelper helper, |
| this.constructorDeclaration, this.operations) |
| : options = new TypeAnalyzerOptions( |
| nullSafetyEnabled: true, |
| patternsEnabled: |
| inferrer.libraryBuilder.libraryFeatures.patterns.isEnabled, |
| inferenceUpdate3Enabled: inferrer |
| .libraryBuilder.libraryFeatures.inferenceUpdate3.isEnabled), |
| super(inferrer, helper); |
| |
| @override |
| int get stackHeight => _rewriteStack.length; |
| |
| @override |
| Object? lookupStack(int index) => |
| _rewriteStack[_rewriteStack.length - index - 1]; |
| |
| /// Used to report an internal error encountered in the stack listener. |
| @override |
| // Coverage-ignore(suite): Not run. |
| Never internalProblem(Message message, int charOffset, Uri uri) { |
| return problems.internalProblem(message, charOffset, uri); |
| } |
| |
| /// Checks that [base] is a valid base stack height for a call to |
| /// [checkStack]. |
| /// |
| /// This can be used to initialize a stack base for subsequent calls to |
| /// [checkStack]. For instance: |
| /// |
| /// int? stackBase; |
| /// // Set up the current stack height as the stack base. |
| /// assert(checkStackBase(node, stackBase = stackHeight)); |
| /// ... |
| /// // Check that the stack is empty, relative to the stack base. |
| /// assert(checkStack(node, [])); |
| /// |
| /// or |
| /// |
| /// int? stackBase; |
| /// // Assert that the current stack height is at least 4 and set |
| /// // the stack height - 4 up as the stack base. |
| /// assert(checkStackBase(node, stackBase = stackHeight - 4)); |
| /// ... |
| /// // Check that the stack contains a single `Expression` element, |
| /// // relative to the stack base. |
| /// assert(checkStack(node, [ValuesKind.Expression])); |
| /// |
| bool checkStackBase(TreeNode? node, int base) { |
| return checkStackBaseStateForAssert(helper.uri, node?.fileOffset, base); |
| } |
| |
| /// Checks the top of the current stack against [kinds]. If a mismatch is |
| /// found, a top of the current stack is print along with the expected [kinds] |
| /// marking the frames that don't match, and throws an exception. |
| /// |
| /// [base] it is used as the reference stack base height at which the [kinds] |
| /// are expected to occur, which allows for checking that the stack is empty |
| /// wrt. the stack base height. |
| /// |
| /// Use this in assert statements like |
| /// |
| /// assert(checkState(node, |
| /// [ValueKind.Expression, ValueKind.StatementOrNull], |
| /// base: stackBase)); |
| /// |
| /// to document the expected stack and get earlier errors on unexpected stack |
| /// content. |
| bool checkStack(TreeNode? node, int? base, List<ValueKind> kinds) { |
| return checkStackStateForAssert(helper.uri, node?.fileOffset, kinds, |
| base: base); |
| } |
| |
| ClosureContext get closureContext => _closureContext!; |
| |
| @override |
| StatementInferenceResult inferStatement(Statement statement, |
| [ClosureContext? closureContext]) { |
| ClosureContext? oldClosureContext = _closureContext; |
| if (closureContext != null) { |
| _closureContext = closureContext; |
| } |
| registerIfUnreachableForTesting(statement); |
| |
| // For full (non-top level) inference, we need access to the |
| // ExpressionGeneratorHelper so that we can perform error recovery. |
| StatementInferenceResult result; |
| if (statement is InternalStatement) { |
| result = statement.acceptInference(this); |
| } else { |
| result = statement.accept(this); |
| } |
| _closureContext = oldClosureContext; |
| return result; |
| } |
| |
| ExpressionInferenceResult _inferExpression( |
| Expression expression, DartType typeContext, |
| {bool isVoidAllowed = false, bool forEffect = false}) { |
| registerIfUnreachableForTesting(expression); |
| |
| ExpressionInferenceResult result; |
| if (expression is ExpressionJudgment) { |
| result = expression.acceptInference(this, typeContext); |
| } else if (expression is InternalExpression) { |
| result = expression.acceptInference(this, typeContext); |
| } else { |
| result = expression.accept1(this, typeContext); |
| } |
| DartType inferredType = result.inferredType; |
| if (inferredType is VoidType && !isVoidAllowed) { |
| if (expression.parent is! ArgumentsImpl) { |
| helper.addProblem( |
| messageVoidExpression, expression.fileOffset, noLength); |
| } |
| } |
| if (coreTypes.isBottom(result.inferredType)) { |
| flowAnalysis.handleExit(); |
| if (shouldThrowUnsoundnessException && |
| // Coverage-ignore(suite): Not run. |
| // Don't throw on expressions that inherently return the bottom type. |
| !(result.nullAwareAction is Throw || |
| result.nullAwareAction is Rethrow || |
| result.nullAwareAction is InvalidExpression)) { |
| // Coverage-ignore-block(suite): Not run. |
| Expression replacement = createLet( |
| createVariable(result.expression, result.inferredType), |
| createReachabilityError( |
| expression.fileOffset, messageNeverValueError)); |
| flowAnalysis.forwardExpression(replacement, result.expression); |
| result = |
| new ExpressionInferenceResult(result.inferredType, replacement); |
| } |
| } |
| return result; |
| } |
| |
| @override |
| ExpressionInferenceResult inferExpression( |
| Expression expression, DartType typeContext, |
| {bool isVoidAllowed = false, bool forEffect = false}) { |
| ExpressionInferenceResult result = _inferExpression(expression, typeContext, |
| isVoidAllowed: isVoidAllowed, forEffect: forEffect); |
| return result.stopShorting(); |
| } |
| |
| @override |
| InitializerInferenceResult inferInitializer(Initializer initializer) { |
| InitializerInferenceResult inferenceResult; |
| if (initializer is InitializerJudgment) { |
| inferenceResult = initializer.acceptInference(this); |
| } else { |
| inferenceResult = initializer.accept(this); |
| } |
| return inferenceResult; |
| } |
| |
| ExpressionInferenceResult inferNullAwareExpression( |
| Expression expression, DartType typeContext, |
| {bool isVoidAllowed = false, bool forEffect = false}) { |
| ExpressionInferenceResult result = _inferExpression(expression, typeContext, |
| isVoidAllowed: isVoidAllowed, forEffect: forEffect); |
| return result; |
| } |
| |
| void inferSyntheticVariable(VariableDeclarationImpl variable) { |
| assert(variable.isImplicitlyTyped); |
| assert(variable.initializer != null); |
| ExpressionInferenceResult result = inferExpression( |
| variable.initializer!, const UnknownType(), |
| isVoidAllowed: true); |
| variable.initializer = result.expression..parent = variable; |
| DartType inferredType = |
| inferDeclarationType(result.inferredType, forSyntheticVariable: true); |
| instrumentation?.record(uriForInstrumentation, variable.fileOffset, 'type', |
| new InstrumentationValueForType(inferredType)); |
| variable.type = inferredType; |
| } |
| |
| Link<NullAwareGuard> inferSyntheticVariableNullAware( |
| VariableDeclarationImpl variable) { |
| assert(variable.isImplicitlyTyped); |
| assert(variable.initializer != null); |
| ExpressionInferenceResult result = inferNullAwareExpression( |
| variable.initializer!, const UnknownType(), |
| isVoidAllowed: true); |
| |
| Link<NullAwareGuard> nullAwareGuards = result.nullAwareGuards; |
| variable.initializer = result.nullAwareAction..parent = variable; |
| |
| DartType inferredType = |
| inferDeclarationType(result.inferredType, forSyntheticVariable: true); |
| instrumentation?.record(uriForInstrumentation, variable.fileOffset, 'type', |
| new InstrumentationValueForType(inferredType)); |
| variable.type = inferredType; |
| return nullAwareGuards; |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| /// Computes uri and offset for [node] for internal errors in a way that is |
| /// safe for both top-level and full inference. |
| UriOffset _computeUriOffset(TreeNode node) { |
| Uri uri = helper.uri; |
| int fileOffset = node.fileOffset; |
| return new UriOffset(uri, fileOffset); |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult _unhandledExpression( |
| Expression node, DartType typeContext) { |
| UriOffset uriOffset = _computeUriOffset(node); |
| problems.unhandled("$node (${node.runtimeType})", "InferenceVisitor", |
| uriOffset.fileOffset, uriOffset.uri); |
| } |
| |
| @override |
| ExpressionInferenceResult visitBlockExpression( |
| BlockExpression node, DartType typeContext) { |
| // This is only used for error cases. The spec doesn't use this and |
| // therefore doesn't specify the type context for the subterms. |
| StatementInferenceResult bodyResult = inferStatement(node.body); |
| if (bodyResult.hasChanged) { |
| // Coverage-ignore-block(suite): Not run. |
| node.body = (bodyResult.statement as Block)..parent = node; |
| } |
| ExpressionInferenceResult valueResult = |
| inferExpression(node.value, const UnknownType(), isVoidAllowed: true); |
| node.value = valueResult.expression..parent = node; |
| return new ExpressionInferenceResult(valueResult.inferredType, node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitConstantExpression( |
| ConstantExpression node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| ExpressionInferenceResult visitDynamicGet( |
| DynamicGet node, DartType typeContext) { |
| // The node has already been inferred, for instance as part of a for-in |
| // loop, so just compute the result type. |
| DartType resultType; |
| switch (node.kind) { |
| case DynamicAccessKind.Dynamic: |
| resultType = const DynamicType(); |
| break; |
| case DynamicAccessKind.Never: |
| // Coverage-ignore(suite): Not run. |
| resultType = NeverType.fromNullability(Nullability.nonNullable); |
| break; |
| case DynamicAccessKind.Invalid: |
| case DynamicAccessKind.Unresolved: |
| resultType = const InvalidType(); |
| break; |
| } |
| return new ExpressionInferenceResult(resultType, node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitInstanceGet( |
| InstanceGet node, DartType typeContext) { |
| // This node is created as part of a lowering and doesn't need inference. |
| return new ExpressionInferenceResult( |
| node.getStaticType(staticTypeContext), node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitInstanceTearOff( |
| InstanceTearOff node, DartType typeContext) { |
| // This node is created as part of a lowering and doesn't need inference. |
| return new ExpressionInferenceResult( |
| node.getStaticType(staticTypeContext), node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitDynamicInvocation( |
| DynamicInvocation node, DartType typeContext) { |
| // This node is created as part of a lowering and doesn't need inference. |
| return new ExpressionInferenceResult( |
| node.getStaticType(staticTypeContext), node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitDynamicSet( |
| DynamicSet node, DartType typeContext) { |
| // This node is created as part of a lowering and doesn't need inference. |
| return new ExpressionInferenceResult( |
| node.getStaticType(staticTypeContext), node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitEqualsCall( |
| EqualsCall node, DartType typeContext) { |
| // This node is created as part of a lowering and doesn't need inference. |
| return new ExpressionInferenceResult( |
| node.getStaticType(staticTypeContext), node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitEqualsNull( |
| EqualsNull node, DartType typeContext) { |
| // This node is created as part of a lowering and doesn't need inference. |
| return new ExpressionInferenceResult( |
| node.getStaticType(staticTypeContext), node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitFunctionInvocation( |
| FunctionInvocation node, DartType typeContext) { |
| // This node is created as part of a lowering and doesn't need inference. |
| return new ExpressionInferenceResult( |
| node.getStaticType(staticTypeContext), node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitInstanceInvocation( |
| InstanceInvocation node, DartType typeContext) { |
| // This node is created as part of a lowering and doesn't need inference. |
| return new ExpressionInferenceResult( |
| node.getStaticType(staticTypeContext), node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitInstanceGetterInvocation( |
| InstanceGetterInvocation node, DartType typeContext) { |
| // This node is created as part of a lowering and doesn't need inference. |
| return new ExpressionInferenceResult( |
| node.getStaticType(staticTypeContext), node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitInstanceSet( |
| InstanceSet node, DartType typeContext) { |
| // This node is created as part of a lowering and doesn't need inference. |
| return new ExpressionInferenceResult( |
| node.getStaticType(staticTypeContext), node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitLocalFunctionInvocation( |
| LocalFunctionInvocation node, DartType typeContext) { |
| // This node is created as part of a lowering and doesn't need inference. |
| return new ExpressionInferenceResult( |
| node.getStaticType(staticTypeContext), node); |
| } |
| |
| @override |
| ExpressionInferenceResult visitStaticTearOff( |
| StaticTearOff node, DartType typeContext) { |
| ensureMemberType(node.target); |
| DartType type = |
| node.target.function.computeFunctionType(Nullability.nonNullable); |
| return instantiateTearOff(type, typeContext, node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitFunctionTearOff( |
| FunctionTearOff node, DartType typeContext) { |
| // This node is created as part of a lowering and doesn't need inference. |
| return new ExpressionInferenceResult( |
| node.getStaticType(staticTypeContext), node); |
| } |
| |
| @override |
| ExpressionInferenceResult visitFileUriExpression( |
| FileUriExpression node, DartType typeContext) { |
| ExpressionInferenceResult result = |
| inferExpression(node.expression, typeContext); |
| node.expression = result.expression..parent = node; |
| return new ExpressionInferenceResult(result.inferredType, node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitInstanceCreation( |
| InstanceCreation node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| ExpressionInferenceResult visitConstructorTearOff( |
| ConstructorTearOff node, DartType typeContext) { |
| ensureMemberType(node.target); |
| DartType type = |
| node.target.function!.computeFunctionType(Nullability.nonNullable); |
| return instantiateTearOff(type, typeContext, node); |
| } |
| |
| @override |
| ExpressionInferenceResult visitRedirectingFactoryTearOff( |
| RedirectingFactoryTearOff node, DartType typeContext) { |
| DartType type = |
| node.target.function.computeFunctionType(Nullability.nonNullable); |
| return instantiateTearOff(type, typeContext, node); |
| } |
| |
| @override |
| ExpressionInferenceResult visitTypedefTearOff( |
| TypedefTearOff node, DartType typeContext) { |
| ExpressionInferenceResult expressionResult = inferExpression( |
| node.expression, const UnknownType(), |
| isVoidAllowed: true); |
| node.expression = expressionResult.expression..parent = node; |
| assert( |
| expressionResult.inferredType is FunctionType, |
| // Coverage-ignore(suite): Not run. |
| "Expected a FunctionType from tearing off a constructor from " |
| "a typedef, but got '${expressionResult.inferredType.runtimeType}'."); |
| FunctionType expressionType = expressionResult.inferredType as FunctionType; |
| |
| assert(expressionType.typeParameters.length == node.typeArguments.length); |
| FunctionType resultType = FunctionTypeInstantiator.instantiate( |
| expressionType, node.typeArguments); |
| FreshStructuralParameters freshStructuralParameters = |
| getFreshStructuralParameters(node.structuralParameters); |
| resultType = |
| freshStructuralParameters.substitute(resultType) as FunctionType; |
| resultType = new FunctionType(resultType.positionalParameters, |
| resultType.returnType, resultType.declaredNullability, |
| namedParameters: resultType.namedParameters, |
| typeParameters: freshStructuralParameters.freshTypeParameters, |
| requiredParameterCount: resultType.requiredParameterCount); |
| ExpressionInferenceResult inferredResult = |
| instantiateTearOff(resultType, typeContext, node); |
| return ensureAssignableResult(typeContext, inferredResult); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitListConcatenation( |
| ListConcatenation node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitMapConcatenation( |
| MapConcatenation node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| ExpressionInferenceResult visitSetConcatenation( |
| SetConcatenation node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| StatementInferenceResult _unhandledStatement(Statement node) { |
| UriOffset uriOffset = _computeUriOffset(node); |
| return problems.unhandled("${node.runtimeType}", "InferenceVisitor", |
| uriOffset.fileOffset, uriOffset.uri); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| StatementInferenceResult visitAssertBlock(AssertBlock node) { |
| return _unhandledStatement(node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| StatementInferenceResult visitTryCatch(TryCatch node) { |
| return _unhandledStatement(node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| StatementInferenceResult visitTryFinally(TryFinally node) { |
| return _unhandledStatement(node); |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| Never _unhandledInitializer(Initializer node) { |
| problems.unhandled("${node.runtimeType}", "InferenceVisitor", |
| node.fileOffset, node.location!.file); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| InitializerInferenceResult visitInvalidInitializer(InvalidInitializer node) { |
| _unhandledInitializer(node); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| InitializerInferenceResult visitLocalInitializer(LocalInitializer node) { |
| _unhandledInitializer(node); |
| } |
| |
| @override |
| ExpressionInferenceResult visitInvalidExpression( |
| InvalidExpression node, DartType typeContext) { |
| if (node.expression != null) { |
| ExpressionInferenceResult result = |
| inferExpression(node.expression!, typeContext, isVoidAllowed: true); |
| node.expression = result.expression..parent = node; |
| } |
| return new ExpressionInferenceResult(const InvalidType(), node); |
| } |
| |
| @override |
| ExpressionInferenceResult visitInstantiation( |
| Instantiation node, DartType typeContext) { |
| ExpressionInferenceResult operandResult = inferExpression( |
| node.expression, const UnknownType(), |
| isVoidAllowed: true); |
| Expression operand = operandResult.expression; |
| DartType operandType = operandResult.inferredType; |
| if (operandType is! FunctionType) { |
| ObjectAccessTarget callMember = findInterfaceMember( |
| operandType, callName, operand.fileOffset, |
| isSetter: false, includeExtensionMethods: true); |
| switch (callMember.kind) { |
| case ObjectAccessTargetKind.instanceMember: |
| Member? target = callMember.classMember; |
| if (target is Procedure && target.kind == ProcedureKind.Method) { |
| operandType = callMember.getGetterType(this); |
| operand = new InstanceTearOff( |
| InstanceAccessKind.Instance, operand, callName, |
| interfaceTarget: target, resultType: operandType) |
| ..fileOffset = operand.fileOffset; |
| } |
| break; |
| case ObjectAccessTargetKind.extensionMember: |
| case ObjectAccessTargetKind.extensionTypeMember: |
| if (callMember.tearoffTarget != null && |
| callMember.declarationMethodKind == ClassMemberKind.Method) { |
| operandType = callMember.getGetterType(this); |
| operand = new StaticInvocation( |
| callMember.tearoffTarget as Procedure, |
| new Arguments(<Expression>[operand], |
| types: callMember.receiverTypeArguments) |
| ..fileOffset = operand.fileOffset) |
| ..fileOffset = operand.fileOffset; |
| } |
| break; |
| case ObjectAccessTargetKind.nullableInstanceMember: |
| case ObjectAccessTargetKind.superMember: |
| case ObjectAccessTargetKind.objectMember: |
| case ObjectAccessTargetKind.nullableCallFunction: |
| case ObjectAccessTargetKind.nullableExtensionMember: |
| case ObjectAccessTargetKind.dynamic: |
| case ObjectAccessTargetKind.never: |
| case ObjectAccessTargetKind.invalid: |
| case ObjectAccessTargetKind.missing: |
| case ObjectAccessTargetKind.ambiguous: |
| case ObjectAccessTargetKind.callFunction: |
| case ObjectAccessTargetKind.recordIndexed: |
| case ObjectAccessTargetKind.nullableRecordIndexed: |
| case ObjectAccessTargetKind.nullableRecordNamed: |
| case ObjectAccessTargetKind.recordNamed: |
| case ObjectAccessTargetKind.nullableExtensionTypeMember: |
| case ObjectAccessTargetKind.extensionTypeRepresentation: |
| // Coverage-ignore(suite): Not run. |
| case ObjectAccessTargetKind.nullableExtensionTypeRepresentation: |
| break; |
| } |
| } |
| node.expression = operand..parent = node; |
| Expression result = node; |
| DartType resultType = const InvalidType(); |
| if (operandType is FunctionType) { |
| if (operandType.typeParameters.length == node.typeArguments.length) { |
| checkBoundsInInstantiation( |
| operandType, node.typeArguments, node.fileOffset, |
| inferred: false); |
| if (operandType.isPotentiallyNullable) { |
| result = helper.buildProblem( |
| templateInstantiationNullableGenericFunctionType |
| .withArguments(operandType), |
| node.fileOffset, |
| noLength); |
| } else { |
| resultType = FunctionTypeInstantiator.instantiate( |
| operandType, node.typeArguments); |
| } |
| } else { |
| if (operandType.typeParameters.isEmpty) { |
| result = helper.buildProblem( |
| templateInstantiationNonGenericFunctionType |
| .withArguments(operandType), |
| node.fileOffset, |
| noLength); |
| } else if (operandType.typeParameters.length > |
| node.typeArguments.length) { |
| result = helper.buildProblem( |
| templateInstantiationTooFewArguments.withArguments( |
| operandType.typeParameters.length, node.typeArguments.length), |
| node.fileOffset, |
| noLength); |
| } else if (operandType.typeParameters.length < |
| node.typeArguments.length) { |
| result = helper.buildProblem( |
| templateInstantiationTooManyArguments.withArguments( |
| operandType.typeParameters.length, node.typeArguments.length), |
| node.fileOffset, |
| noLength); |
| } |
| } |
| } else if (operandType is! InvalidType) { |
| result = helper.buildProblem( |
| templateInstantiationNonGenericFunctionType |
| .withArguments(operandType), |
| node.fileOffset, |
| noLength); |
| } |
| return new ExpressionInferenceResult(resultType, result); |
| } |
| |
| @override |
| ExpressionInferenceResult visitIntLiteral( |
| IntLiteral node, DartType typeContext) { |
| return new ExpressionInferenceResult( |
| coreTypes.intRawType(Nullability.nonNullable), node); |
| } |
| |
| @override |
| ExpressionInferenceResult visitAsExpression( |
| AsExpression node, DartType typeContext) { |
| ExpressionInferenceResult operandResult = |
| inferExpression(node.operand, const UnknownType(), isVoidAllowed: true); |
| node.operand = operandResult.expression..parent = node; |
| flowAnalysis.asExpression_end(node.operand, node.type); |
| return new ExpressionInferenceResult(node.type, node); |
| } |
| |
| @override |
| InitializerInferenceResult visitAssertInitializer(AssertInitializer node) { |
| StatementInferenceResult result = inferStatement(node.statement); |
| if (result.hasChanged) { |
| // Coverage-ignore-block(suite): Not run. |
| node.statement = (result.statement as AssertStatement)..parent = node; |
| } |
| return const SuccessfulInitializerInferenceResult(); |
| } |
| |
| @override |
| StatementInferenceResult visitAssertStatement(AssertStatement node) { |
| flowAnalysis.assert_begin(); |
| InterfaceType expectedType = coreTypes.boolRawType(Nullability.nonNullable); |
| ExpressionInferenceResult conditionResult = |
| inferExpression(node.condition, expectedType, isVoidAllowed: true); |
| |
| Expression condition = |
| ensureAssignableResult(expectedType, conditionResult).expression; |
| node.condition = condition..parent = node; |
| flowAnalysis.assert_afterCondition(node.condition); |
| if (node.message != null) { |
| ExpressionInferenceResult messageResult = inferExpression( |
| node.message!, const UnknownType(), |
| isVoidAllowed: true); |
| node.message = messageResult.expression..parent = node; |
| } |
| flowAnalysis.assert_end(); |
| return const StatementInferenceResult(); |
| } |
| |
| bool _isIncompatibleWithAwait(DartType type) { |
| if (isNullableTypeConstructorApplication(type)) { |
| return _isIncompatibleWithAwait( |
| computeTypeWithoutNullabilityMarker(type)); |
| } else { |
| switch (type) { |
| case ExtensionType(): |
| return typeSchemaEnvironment.hierarchy |
| .getExtensionTypeAsInstanceOfClass( |
| type, coreTypes.futureClass) == |
| null; |
| case TypeParameterType(): |
| return _isIncompatibleWithAwait(type.parameter.bound); |
| case StructuralParameterType(): |
| // Coverage-ignore(suite): Not run. |
| return _isIncompatibleWithAwait(type.parameter.bound); |
| case IntersectionType(): |
| return _isIncompatibleWithAwait(type.right); |
| case DynamicType(): |
| case VoidType(): |
| case FutureOrType(): |
| case InterfaceType(): |
| case TypedefType(): |
| case FunctionType(): |
| case RecordType(): |
| case NullType(): |
| case NeverType(): |
| case AuxiliaryType(): |
| case InvalidType(): |
| return false; |
| } |
| } |
| } |
| |
| @override |
| ExpressionInferenceResult visitAwaitExpression( |
| AwaitExpression node, DartType typeContext) { |
| if (typeContext is DynamicType) { |
| typeContext = const UnknownType(); |
| } |
| typeContext = wrapFutureOrType(typeContext); |
| ExpressionInferenceResult operandResult = |
| inferExpression(node.operand, typeContext, isVoidAllowed: false); |
| DartType operandType = operandResult.inferredType; |
| DartType flattenType = typeSchemaEnvironment.flatten(operandType); |
| if (_isIncompatibleWithAwait(operandType)) { |
| Expression wrapped = operandResult.expression; |
| node.operand = helper.wrapInProblem( |
| wrapped, messageAwaitOfExtensionTypeNotFuture, wrapped.fileOffset, 1); |
| wrapped.parent = node.operand; |
| } else { |
| node.operand = operandResult.expression..parent = node; |
| } |
| DartType runtimeCheckType = new InterfaceType( |
| coreTypes.futureClass, Nullability.nonNullable, [flattenType]); |
| if (!typeSchemaEnvironment.isSubtypeOf( |
| operandType, runtimeCheckType, SubtypeCheckMode.withNullabilities)) { |
| node.runtimeCheckType = runtimeCheckType; |
| } |
| return new ExpressionInferenceResult(flattenType, node); |
| } |
| |
| List<Statement>? _visitStatements<T extends Statement>(List<T> statements) { |
| List<Statement>? result; |
| for (int index = 0; index < statements.length; index++) { |
| T statement = statements[index]; |
| StatementInferenceResult statementResult = inferStatement(statement); |
| if (statementResult.hasChanged) { |
| if (result == null) { |
| result = <T>[]; |
| result.addAll(statements.sublist(0, index)); |
| } |
| if (statementResult.statementCount == 1) { |
| result.add(statementResult.statement); |
| } else { |
| result.addAll(statementResult.statements); |
| } |
| } else if (result != null) { |
| result.add(statement); |
| } |
| } |
| return result; |
| } |
| |
| @override |
| StatementInferenceResult visitBlock(Block node) { |
| registerIfUnreachableForTesting(node); |
| List<Statement>? result = _visitStatements<Statement>(node.statements); |
| if (result != null) { |
| Block block = new Block(result)..fileOffset = node.fileOffset; |
| libraryBuilder.loader.dataForTesting |
| // Coverage-ignore(suite): Not run. |
| ?.registerAlias(node, block); |
| return new StatementInferenceResult.single(block); |
| } else { |
| return const StatementInferenceResult(); |
| } |
| } |
| |
| @override |
| ExpressionInferenceResult visitBoolLiteral( |
| BoolLiteral node, DartType typeContext) { |
| flowAnalysis.booleanLiteral(node, node.value); |
| return new ExpressionInferenceResult( |
| coreTypes.boolRawType(Nullability.nonNullable), node); |
| } |
| |
| @override |
| StatementInferenceResult visitBreakStatement( |
| covariant BreakStatementImpl node) { |
| // TODO(johnniwinther): Refactor break/continue encoding. |
| assert(node.targetStatement != null); |
| if (node.isContinue) { |
| flowAnalysis.handleContinue(node.targetStatement); |
| } else { |
| flowAnalysis.handleBreak(node.targetStatement); |
| } |
| return const StatementInferenceResult(); |
| } |
| |
| ExpressionInferenceResult visitCascade(Cascade node, DartType typeContext) { |
| ExpressionInferenceResult result = inferExpression( |
| node.variable.initializer!, typeContext, |
| isVoidAllowed: false); |
| |
| node.variable.initializer = result.expression..parent = node.variable; |
| node.variable.type = result.inferredType; |
| flowAnalysis.cascadeExpression_afterTarget( |
| result.expression, result.inferredType, |
| isNullAware: node.isNullAware); |
| NullAwareGuard? nullAwareGuard; |
| if (node.isNullAware) { |
| nullAwareGuard = createNullAwareGuard(node.variable); |
| } |
| |
| Cascade? previousEnclosingCascade = _enclosingCascade; |
| _enclosingCascade = node; |
| List<ExpressionInferenceResult> expressionResults = |
| <ExpressionInferenceResult>[]; |
| for (Expression expression in node.expressions) { |
| expressionResults.add(inferExpression(expression, const UnknownType(), |
| isVoidAllowed: true, forEffect: true)); |
| } |
| List<Statement> body = []; |
| for (int index = 0; index < expressionResults.length; index++) { |
| body.add(_createExpressionStatement(expressionResults[index].expression)); |
| } |
| _enclosingCascade = previousEnclosingCascade; |
| |
| Expression replacement = _createBlockExpression(node.variable.fileOffset, |
| _createBlock(body), createVariableGet(node.variable)); |
| |
| if (node.isNullAware) { |
| replacement = |
| nullAwareGuard!.createExpression(result.inferredType, replacement); |
| } else { |
| replacement = new Let(node.variable, replacement) |
| ..fileOffset = node.fileOffset; |
| } |
| flowAnalysis.cascadeExpression_end(replacement); |
| return new ExpressionInferenceResult(result.inferredType, replacement); |
| } |
| |
| @override |
| PropertyTarget<Expression> computePropertyTarget(Expression target) { |
| if (_enclosingCascade case Cascade(:var variable) |
| when target is VariableGet && target.variable == variable) { |
| // `target` is an implicit reference to the target of a cascade |
| // expression; flow analysis uses `CascadePropertyTarget` to represent |
| // this situation. |
| return CascadePropertyTarget.singleton; |
| } else { |
| // `target` is an ordinary expression. |
| return new ExpressionPropertyTarget(target); |
| } |
| } |
| |
| Block _createBlock(List<Statement> statements) { |
| return new Block(statements); |
| } |
| |
| BlockExpression _createBlockExpression( |
| int fileOffset, Block body, Expression value) { |
| assert(fileOffset != TreeNode.noOffset); |
| return new BlockExpression(body, value)..fileOffset = fileOffset; |
| } |
| |
| ExpressionStatement _createExpressionStatement(Expression expression) { |
| assert(expression.fileOffset != TreeNode.noOffset); |
| return new ExpressionStatement(expression) |
| ..fileOffset = expression.fileOffset; |
| } |
| |
| @override |
| ExpressionInferenceResult visitConditionalExpression( |
| ConditionalExpression node, DartType typeContext) { |
| flowAnalysis.conditional_conditionBegin(); |
| InterfaceType expectedType = coreTypes.boolRawType(Nullability.nonNullable); |
| ExpressionInferenceResult conditionResult = |
| inferExpression(node.condition, expectedType, isVoidAllowed: true); |
| Expression condition = |
| ensureAssignableResult(expectedType, conditionResult).expression; |
| node.condition = condition..parent = node; |
| flowAnalysis.conditional_thenBegin(node.condition, node); |
| bool isThenReachable = flowAnalysis.isReachable; |
| |
| // A conditional expression `E` of the form `b ? e1 : e2` with context |
| // type `K` is analyzed as follows: |
| // |
| // - Let `T1` be the type of `e1` inferred with context type `K` |
| ExpressionInferenceResult thenResult = |
| inferExpression(node.then, typeContext, isVoidAllowed: true); |
| node.then = thenResult.expression..parent = node; |
| registerIfUnreachableForTesting(node.then, isReachable: isThenReachable); |
| DartType t1 = thenResult.inferredType; |
| |
| // - Let `T2` be the type of `e2` inferred with context type `K` |
| flowAnalysis.conditional_elseBegin(node.then, thenResult.inferredType); |
| bool isOtherwiseReachable = flowAnalysis.isReachable; |
| ExpressionInferenceResult otherwiseResult = |
| inferExpression(node.otherwise, typeContext, isVoidAllowed: true); |
| node.otherwise = otherwiseResult.expression..parent = node; |
| registerIfUnreachableForTesting(node.otherwise, |
| isReachable: isOtherwiseReachable); |
| DartType t2 = otherwiseResult.inferredType; |
| |
| // - Let `T` be `UP(T1, T2)` |
| DartType t = typeSchemaEnvironment.getStandardUpperBound(t1, t2); |
| |
| // - Let `S` be the greatest closure of `K` |
| DartType s = computeGreatestClosure(typeContext); |
| |
| DartType inferredType; |
| // If `inferenceUpdate3` is not enabled, then the type of `E` is `T`. |
| if (!libraryBuilder.libraryFeatures.inferenceUpdate3.isEnabled) { |
| inferredType = t; |
| } else |
| // - If `T <: S` then the type of `E` is `T` |
| if (typeSchemaEnvironment.isSubtypeOf( |
| t, s, SubtypeCheckMode.withNullabilities)) { |
| inferredType = t; |
| } else |
| // - Otherwise, if `T1 <: S` and `T2 <: S`, then the type of `E` is `S` |
| if (typeSchemaEnvironment.isSubtypeOf( |
| t1, s, SubtypeCheckMode.withNullabilities) && |
| typeSchemaEnvironment.isSubtypeOf( |
| t2, s, SubtypeCheckMode.withNullabilities)) { |
| inferredType = s; |
| } else |
| // - Otherwise, the type of `E` is `T` |
| { |
| inferredType = t; |
| } |
| |
| flowAnalysis.conditional_end( |
| node, inferredType, node.otherwise, otherwiseResult.inferredType); |
| node.staticType = inferredType; |
| return new ExpressionInferenceResult(inferredType, node); |
| } |
| |
| @override |
| ExpressionInferenceResult visitConstructorInvocation( |
| ConstructorInvocation node, DartType typeContext) { |
| ensureMemberType(node.target); |
| bool hadExplicitTypeArguments = hasExplicitTypeArguments(node.arguments); |
| FunctionType functionType = |
| node.target.function.computeThisFunctionType(Nullability.nonNullable); |
| InvocationInferenceResult result = inferInvocation( |
| this, |
| typeContext, |
| node.fileOffset, |
| new InvocationTargetFunctionType(functionType), |
| node.arguments as ArgumentsImpl, |
| isConst: node.isConst, |
| staticTarget: node.target); |
| SourceLibraryBuilder library = libraryBuilder; |
| if (!hadExplicitTypeArguments) { |
| library.checkBoundsInConstructorInvocation( |
| node, typeSchemaEnvironment, helper.uri, |
| inferred: true); |
| } |
| return new ExpressionInferenceResult( |
| result.inferredType, result.applyResult(node)); |
| } |
| |
| @override |
| StatementInferenceResult visitContinueSwitchStatement( |
| ContinueSwitchStatement node) { |
| flowAnalysis.handleContinue(node.target.body); |
| return const StatementInferenceResult(); |
| } |
| |
| ExpressionInferenceResult visitExtensionTearOff( |
| ExtensionTearOff node, DartType typeContext) { |
| FunctionType calleeType = |
| node.target.function.computeFunctionType(Nullability.nonNullable); |
| TypeArgumentsInfo typeArgumentsInfo = getTypeArgumentsInfo(node.arguments); |
| InvocationInferenceResult result = inferInvocation( |
| this, |
| typeContext, |
| node.fileOffset, |
| new InvocationTargetFunctionType(calleeType), |
| node.arguments as ArgumentsImpl, |
| staticTarget: node.target); |
| StaticInvocation replacement = |
| new StaticInvocation(node.target, node.arguments); |
| libraryBuilder.checkBoundsInStaticInvocation( |
| replacement, typeSchemaEnvironment, helper.uri, typeArgumentsInfo); |
| return instantiateTearOff( |
| result.inferredType, typeContext, result.applyResult(replacement)); |
| } |
| |
| ExpressionInferenceResult visitExtensionSet( |
| ExtensionSet node, DartType typeContext) { |
| ExpressionInferenceResult receiverResult = inferExpression( |
| node.receiver, const UnknownType(), |
| isVoidAllowed: false); |
| |
| List<DartType> extensionTypeArguments = computeExtensionTypeArgument( |
| node.extension, node.explicitTypeArguments, receiverResult.inferredType, |
| treeNodeForTesting: node); |
| |
| DartType receiverType = |
| getExtensionReceiverType(node.extension, extensionTypeArguments); |
| |
| Expression receiver = |
| ensureAssignableResult(receiverType, receiverResult).expression; |
| |
| ObjectAccessTarget target = new ExtensionAccessTarget(receiverType, |
| node.target, null, ClassMemberKind.Setter, extensionTypeArguments); |
| |
| DartType valueType = target.getSetterType(this); |
| |
| ExpressionInferenceResult valueResult = |
| inferExpression(node.value, valueType, isVoidAllowed: false); |
| valueResult = ensureAssignableResult(valueType, valueResult); |
| Expression value = valueResult.expression; |
| |
| VariableDeclaration? valueVariable; |
| if (node.forEffect) { |
| // No need for value variable. |
| } else { |
| valueVariable = createVariable(value, valueResult.inferredType); |
| value = createVariableGet(valueVariable); |
| } |
| |
| VariableDeclaration? receiverVariable; |
| if (node.forEffect || isPureExpression(receiver)) { |
| // No need for receiver variable. |
| } else { |
| receiverVariable = createVariable(receiver, receiverResult.inferredType); |
| receiver = createVariableGet(receiverVariable); |
| } |
| Expression assignment = new StaticInvocation( |
| node.target, |
| new Arguments(<Expression>[receiver, value], |
| types: extensionTypeArguments) |
| ..fileOffset = node.fileOffset) |
| ..fileOffset = node.fileOffset; |
| |
| Expression replacement; |
| if (node.forEffect) { |
| assert(receiverVariable == null); |
| assert(valueVariable == null); |
| replacement = assignment; |
| } else { |
| assert(valueVariable != null); |
| VariableDeclaration assignmentVariable = |
| createVariable(assignment, const VoidType()); |
| replacement = createLet(valueVariable!, |
| createLet(assignmentVariable, createVariableGet(valueVariable))); |
| if (receiverVariable != null) { |
| replacement = createLet(receiverVariable, replacement); |
| } |
| } |
| replacement.fileOffset = node.fileOffset; |
| return new ExpressionInferenceResult(valueResult.inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitCompoundExtensionSet( |
| CompoundExtensionSet node, DartType typeContext) { |
| ExpressionInferenceResult receiverResult = inferExpression( |
| node.receiver, const UnknownType(), |
| isVoidAllowed: false); |
| |
| List<DartType> extensionTypeArguments = computeExtensionTypeArgument( |
| node.extension, node.explicitTypeArguments, receiverResult.inferredType, |
| treeNodeForTesting: node); |
| |
| DartType receiverType = |
| getExtensionReceiverType(node.extension, extensionTypeArguments); |
| |
| Expression receiver = |
| ensureAssignableResult(receiverType, receiverResult).expression; |
| |
| VariableDeclaration? receiverVariable; |
| Expression readReceiver; |
| Expression writeReceiver; |
| if (isPureExpression(receiver)) { |
| // Coverage-ignore-block(suite): Not run. |
| readReceiver = receiver; |
| writeReceiver = clonePureExpression(receiver); |
| } else { |
| receiverVariable = createVariable(receiver, receiverType); |
| readReceiver = createVariableGet(receiverVariable); |
| writeReceiver = createVariableGet(receiverVariable); |
| } |
| |
| ObjectAccessTarget readTarget = node.getter == null |
| ? const ObjectAccessTarget.missing() |
| : new ExtensionAccessTarget(receiverType, node.getter!, null, |
| ClassMemberKind.Getter, extensionTypeArguments); |
| |
| DartType readType = readTarget.getGetterType(this); |
| |
| Expression read; |
| if (readTarget.isMissing) { |
| // Coverage-ignore-block(suite): Not run. |
| read = createMissingPropertyGet( |
| node.readOffset, readType, node.propertyName, |
| receiver: readReceiver); |
| } else { |
| assert(readTarget.isExtensionMember); |
| read = new StaticInvocation( |
| readTarget.member as Procedure, |
| new Arguments(<Expression>[ |
| readReceiver, |
| ], types: readTarget.receiverTypeArguments) |
| ..fileOffset = node.readOffset) |
| ..fileOffset = node.readOffset; |
| } |
| |
| ObjectAccessTarget writeTarget = node.setter == null |
| ? const ObjectAccessTarget.missing() |
| : new ExtensionAccessTarget(receiverType, node.setter!, null, |
| ClassMemberKind.Setter, extensionTypeArguments); |
| |
| DartType valueType = writeTarget.getSetterType(this); |
| |
| ExpressionInferenceResult binaryResult = _computeBinaryExpression( |
| node.binaryOffset, |
| valueType, |
| read, |
| readType, |
| node.binaryName, |
| node.rhs, |
| null); |
| |
| binaryResult = |
| ensureAssignableResult(valueType, binaryResult, isVoidAllowed: true); |
| Expression value = binaryResult.expression; |
| |
| VariableDeclaration? valueVariable; |
| if (node.forEffect) { |
| // No need for value variable. |
| } else { |
| valueVariable = createVariable(value, valueType); |
| value = createVariableGet(valueVariable); |
| } |
| |
| Expression write; |
| if (writeTarget.isMissing) { |
| write = createMissingPropertySet( |
| node.writeOffset, writeReceiver, readType, node.propertyName, value, |
| forEffect: node.forEffect); |
| } else { |
| assert(writeTarget.isExtensionMember); |
| write = new StaticInvocation( |
| writeTarget.member as Procedure, |
| new Arguments(<Expression>[ |
| writeReceiver, |
| value, |
| ], types: writeTarget.receiverTypeArguments) |
| ..fileOffset = node.writeOffset) |
| ..fileOffset = node.writeOffset; |
| } |
| |
| Expression replacement; |
| if (node.forEffect) { |
| assert(valueVariable == null); |
| replacement = write; |
| } else { |
| assert(valueVariable != null); |
| VariableDeclaration writeVariable = |
| createVariable(write, const VoidType()); |
| replacement = createLet(valueVariable!, |
| createLet(writeVariable, createVariableGet(valueVariable))); |
| } |
| if (receiverVariable != null) { |
| replacement = createLet(receiverVariable, replacement); |
| } |
| replacement.fileOffset = node.fileOffset; |
| return new ExpressionInferenceResult(valueType, replacement); |
| } |
| |
| ExpressionInferenceResult visitDeferredCheck( |
| DeferredCheck node, DartType typeContext) { |
| // Since the variable is not used in the body we don't need to type infer |
| // it. We can just type infer the body. |
| ExpressionInferenceResult result = |
| inferExpression(node.expression, typeContext, isVoidAllowed: true); |
| |
| Expression replacement = new Let(node.variable, result.expression) |
| ..fileOffset = node.fileOffset; |
| return new ExpressionInferenceResult(result.inferredType, replacement); |
| } |
| |
| @override |
| StatementInferenceResult visitDoStatement(DoStatement node) { |
| flowAnalysis.doStatement_bodyBegin(node); |
| StatementInferenceResult bodyResult = inferStatement(node.body); |
| if (bodyResult.hasChanged) { |
| // Coverage-ignore-block(suite): Not run. |
| node.body = bodyResult.statement..parent = node; |
| } |
| flowAnalysis.doStatement_conditionBegin(); |
| InterfaceType boolType = coreTypes.boolRawType(Nullability.nonNullable); |
| ExpressionInferenceResult conditionResult = |
| inferExpression(node.condition, boolType, isVoidAllowed: true); |
| Expression condition = |
| ensureAssignableResult(boolType, conditionResult).expression; |
| node.condition = condition..parent = node; |
| flowAnalysis.doStatement_end(condition); |
| return const StatementInferenceResult(); |
| } |
| |
| @override |
| ExpressionInferenceResult visitDoubleLiteral( |
| DoubleLiteral node, DartType typeContext) { |
| return new ExpressionInferenceResult( |
| coreTypes.doubleRawType(Nullability.nonNullable), node); |
| } |
| |
| @override |
| StatementInferenceResult visitEmptyStatement(EmptyStatement node) { |
| // No inference needs to be done. |
| return const StatementInferenceResult(); |
| } |
| |
| @override |
| StatementInferenceResult visitExpressionStatement(ExpressionStatement node) { |
| ExpressionInferenceResult result = inferExpression( |
| node.expression, const UnknownType(), |
| isVoidAllowed: true, forEffect: true); |
| node.expression = result.expression..parent = node; |
| return const StatementInferenceResult(); |
| } |
| |
| ExpressionInferenceResult visitFactoryConstructorInvocation( |
| FactoryConstructorInvocation node, DartType typeContext) { |
| bool hadExplicitTypeArguments = hasExplicitTypeArguments(node.arguments); |
| |
| FunctionType functionType = |
| node.target.function.computeThisFunctionType(Nullability.nonNullable); |
| |
| InvocationInferenceResult result = inferInvocation( |
| this, |
| typeContext, |
| node.fileOffset, |
| new InvocationTargetFunctionType(functionType), |
| node.arguments as ArgumentsImpl, |
| isConst: node.isConst, |
| staticTarget: node.target); |
| node.hasBeenInferred = true; |
| SourceLibraryBuilder library = libraryBuilder; |
| if (!hadExplicitTypeArguments) { |
| library.checkBoundsInFactoryInvocation( |
| node, typeSchemaEnvironment, helper.uri, |
| inferred: true); |
| } |
| Expression resolvedExpression = helper.resolveRedirectingFactoryTarget( |
| node.target, node.arguments, node.fileOffset, node.isConst)!; |
| Expression resultExpression = result.applyResult(resolvedExpression); |
| |
| return new ExpressionInferenceResult(result.inferredType, resultExpression); |
| } |
| |
| /// Returns the function type of [constructor] when called through [typedef]. |
| FunctionType _computeAliasedConstructorFunctionType( |
| Constructor constructor, Typedef typedef) { |
| ensureMemberType(constructor); |
| FunctionNode function = constructor.function; |
| // We need create a copy of the list of type parameters, otherwise |
| // transformations like erasure don't work. |
| List<TypeParameter> classTypeParametersCopy = |
| new List.of(constructor.enclosingClass.typeParameters); |
| FreshStructuralParametersFromTypeParameters freshTypeParameters = |
| getFreshStructuralParametersFromTypeParameters(typedef.typeParameters); |
| List<StructuralParameter> typedefTypeParametersCopy = |
| freshTypeParameters.freshTypeParameters; |
| List<DartType> asTypeArguments = freshTypeParameters.freshTypeArguments; |
| TypedefType typedefType = new TypedefType( |
| typedef, libraryBuilder.library.nonNullable, asTypeArguments); |
| DartType unaliasedTypedef = typedefType.unalias; |
| assert(unaliasedTypedef is InterfaceType, |
| "[typedef] is assumed to resolve to an interface type"); |
| InterfaceType targetType = unaliasedTypedef as InterfaceType; |
| Substitution substitution = Substitution.fromPairs( |
| classTypeParametersCopy, targetType.typeArguments); |
| List<DartType> positional = function.positionalParameters |
| .map((VariableDeclaration decl) => |
| substitution.substituteType(decl.type)) |
| .toList(growable: false); |
| List<NamedType> named = function.namedParameters |
| .map((VariableDeclaration decl) => new NamedType( |
| decl.name!, substitution.substituteType(decl.type), |
| isRequired: decl.isRequired)) |
| .toList(growable: false); |
| named.sort(); |
| return new FunctionType( |
| positional, typedefType.unalias, libraryBuilder.library.nonNullable, |
| namedParameters: named, |
| typeParameters: typedefTypeParametersCopy, |
| requiredParameterCount: function.requiredParameterCount); |
| } |
| |
| ExpressionInferenceResult visitTypeAliasedConstructorInvocation( |
| TypeAliasedConstructorInvocation node, DartType typeContext) { |
| assert(getExplicitTypeArguments(node.arguments) == null); |
| Typedef typedef = node.typeAliasBuilder.typedef; |
| FunctionType calleeType = |
| _computeAliasedConstructorFunctionType(node.target, typedef); |
| calleeType = replaceReturnType(calleeType, calleeType.returnType.unalias); |
| InvocationInferenceResult result = inferInvocation( |
| this, |
| typeContext, |
| node.fileOffset, |
| new InvocationTargetFunctionType(calleeType), |
| node.arguments as ArgumentsImpl, |
| isConst: node.isConst, |
| staticTarget: node.target); |
| node.hasBeenInferred = true; |
| |
| Expression resolvedExpression = |
| helper.unaliasSingleTypeAliasedConstructorInvocation(node); |
| Expression resultingExpression = result.applyResult(resolvedExpression); |
| |
| return new ExpressionInferenceResult( |
| result.inferredType, resultingExpression); |
| } |
| |
| /// Returns the function type of [factory] when called through [typedef]. |
| FunctionType _computeAliasedFactoryFunctionType( |
| Procedure factory, Typedef typedef) { |
| assert( |
| factory.isFactory || factory.isExtensionTypeMember, |
| // Coverage-ignore(suite): Not run. |
| "Only run this method on a factory: $factory"); |
| ensureMemberType(factory); |
| FunctionNode function = factory.function; |
| // We need create a copy of the list of type parameters, otherwise |
| // transformations like erasure don't work. |
| List<TypeParameter> classTypeParametersCopy = |
| new List.of(function.typeParameters); |
| FreshStructuralParametersFromTypeParameters freshTypeParameters = |
| getFreshStructuralParametersFromTypeParameters(typedef.typeParameters); |
| List<StructuralParameter> typedefTypeParametersCopy = |
| freshTypeParameters.freshTypeParameters; |
| List<DartType> asTypeArguments = freshTypeParameters.freshTypeArguments; |
| TypedefType typedefType = new TypedefType( |
| typedef, libraryBuilder.library.nonNullable, asTypeArguments); |
| DartType unaliasedTypedef = typedefType.unalias; |
| assert(unaliasedTypedef is TypeDeclarationType, |
| "[typedef] is assumed to resolve to a type declaration type"); |
| TypeDeclarationType targetType = unaliasedTypedef as TypeDeclarationType; |
| Substitution substitution = Substitution.fromPairs( |
| classTypeParametersCopy, targetType.typeArguments); |
| List<DartType> positional = function.positionalParameters |
| .map((VariableDeclaration decl) => |
| substitution.substituteType(decl.type)) |
| .toList(growable: false); |
| List<NamedType> named = function.namedParameters |
| .map( |
| // Coverage-ignore(suite): Not run. |
| (VariableDeclaration decl) => new NamedType( |
| decl.name!, substitution.substituteType(decl.type), |
| isRequired: decl.isRequired)) |
| .toList(growable: false); |
| named.sort(); |
| return new FunctionType( |
| positional, typedefType.unalias, libraryBuilder.library.nonNullable, |
| namedParameters: named, |
| typeParameters: typedefTypeParametersCopy, |
| requiredParameterCount: function.requiredParameterCount); |
| } |
| |
| ExpressionInferenceResult visitTypeAliasedFactoryInvocation( |
| TypeAliasedFactoryInvocation node, DartType typeContext) { |
| assert(getExplicitTypeArguments(node.arguments) == null); |
| Typedef typedef = node.typeAliasBuilder.typedef; |
| FunctionType calleeType = |
| _computeAliasedFactoryFunctionType(node.target, typedef); |
| calleeType = replaceReturnType(calleeType, calleeType.returnType.unalias); |
| InvocationInferenceResult result = inferInvocation( |
| this, |
| typeContext, |
| node.fileOffset, |
| new InvocationTargetFunctionType(calleeType), |
| node.arguments as ArgumentsImpl, |
| isConst: node.isConst, |
| staticTarget: node.target); |
| |
| Expression resolvedExpression = |
| helper.unaliasSingleTypeAliasedFactoryInvocation(node)!; |
| Expression resultExpression = result.applyResult(resolvedExpression); |
| |
| node.hasBeenInferred = true; |
| return new ExpressionInferenceResult(result.inferredType, resultExpression); |
| } |
| |
| @override |
| InitializerInferenceResult visitFieldInitializer(FieldInitializer node) { |
| DartType fieldType = node.field.type; |
| fieldType = constructorDeclaration!.substituteFieldType(fieldType); |
| ExpressionInferenceResult initializerResult = |
| inferExpression(node.value, fieldType); |
| Expression initializer = ensureAssignableResult( |
| fieldType, initializerResult, |
| fileOffset: node.fileOffset) |
| .expression; |
| node.value = initializer..parent = node; |
| return const SuccessfulInitializerInferenceResult(); |
| } |
| |
| ForInResult handleForInDeclaringVariable( |
| TreeNode node, |
| VariableDeclaration variable, |
| Expression iterable, |
| Statement? expressionEffects, |
| {bool isAsync = false}) { |
| DartType elementType; |
| bool isVariableTypeNeeded = false; |
| if (variable is VariableDeclarationImpl && variable.isImplicitlyTyped) { |
| isVariableTypeNeeded = true; |
| elementType = const UnknownType(); |
| } else { |
| elementType = variable.type; |
| } |
| |
| ExpressionInferenceResult iterableResult = |
| inferForInIterable(iterable, elementType, isAsync: isAsync); |
| DartType inferredType = iterableResult.inferredType; |
| if (isVariableTypeNeeded) { |
| instrumentation?.record(uriForInstrumentation, variable.fileOffset, |
| 'type', new InstrumentationValueForType(inferredType)); |
| variable.type = inferredType; |
| } |
| |
| // This is matched by the call to [forEach_end] in |
| // [inferElement], [inferMapEntry] or [inferForInStatement]. |
| flowAnalysis.declare(variable, variable.type, initialized: true); |
| flowAnalysis.forEach_bodyBegin(node); |
| |
| VariableDeclaration tempVariable = new VariableDeclaration(null, |
| type: inferredType, isFinal: true, isSynthesized: true); |
| VariableGet variableGet = new VariableGet(tempVariable) |
| ..fileOffset = variable.fileOffset; |
| TreeNode parent = variable.parent!; |
| Expression implicitDowncast = ensureAssignable( |
| variable.type, inferredType, variableGet, |
| isVoidAllowed: true, |
| fileOffset: parent.fileOffset, |
| errorTemplate: templateForInLoopElementTypeNotAssignable, |
| nullabilityErrorTemplate: |
| templateForInLoopElementTypeNotAssignableNullability, |
| nullabilityPartErrorTemplate: |
| templateForInLoopElementTypeNotAssignablePartNullability); |
| Statement? expressionEffect; |
| if (!identical(implicitDowncast, variableGet)) { |
| variable.initializer = implicitDowncast..parent = variable; |
| expressionEffect = variable; |
| variable = tempVariable; |
| } |
| if (expressionEffects != null) { |
| // Coverage-ignore-block(suite): Not run. |
| StatementInferenceResult bodyResult = inferStatement(expressionEffects); |
| if (bodyResult.hasChanged) { |
| expressionEffects = bodyResult.statement; |
| } |
| if (expressionEffect != null) { |
| expressionEffects = |
| combineStatements(expressionEffect, expressionEffects); |
| } |
| } else { |
| expressionEffects = expressionEffect; |
| } |
| return new ForInResult( |
| variable, iterableResult.expression, null, expressionEffects); |
| } |
| |
| ExpressionInferenceResult inferForInIterable( |
| Expression iterable, DartType elementType, |
| {bool isAsync = false}) { |
| Class iterableClass = |
| isAsync ? coreTypes.streamClass : coreTypes.iterableClass; |
| DartType context = |
| wrapType(elementType, iterableClass, Nullability.nonNullable); |
| ExpressionInferenceResult iterableResult = |
| inferExpression(iterable, context, isVoidAllowed: false); |
| DartType iterableType = iterableResult.inferredType; |
| iterable = iterableResult.expression; |
| DartType inferredExpressionType = iterableType.nonTypeVariableBound; |
| iterable = ensureAssignable( |
| wrapType(const DynamicType(), iterableClass, Nullability.nonNullable), |
| inferredExpressionType, |
| iterable, |
| errorTemplate: templateForInLoopTypeNotIterable, |
| nullabilityErrorTemplate: templateForInLoopTypeNotIterableNullability, |
| nullabilityPartErrorTemplate: |
| templateForInLoopTypeNotIterablePartNullability); |
| DartType inferredType = const DynamicType(); |
| if (inferredExpressionType is TypeDeclarationType) { |
| // TODO(johnniwinther): Should we use the type of |
| // `iterable.iterator.current` instead? |
| List<DartType>? supertypeArguments = hierarchyBuilder |
| .getTypeArgumentsAsInstanceOf(inferredExpressionType, iterableClass); |
| if (supertypeArguments != null) { |
| inferredType = supertypeArguments[0]; |
| } |
| } |
| return new ExpressionInferenceResult(inferredType, iterable); |
| } |
| |
| ForInVariable computeForInVariable( |
| Expression? syntheticAssignment, bool hasProblem) { |
| if (syntheticAssignment is VariableSet) { |
| return new LocalForInVariable(syntheticAssignment); |
| } else if (syntheticAssignment is PropertySet) { |
| return new PropertyForInVariable(syntheticAssignment); |
| } else if (syntheticAssignment is AbstractSuperPropertySet) { |
| // Coverage-ignore-block(suite): Not run. |
| return new AbstractSuperPropertyForInVariable(syntheticAssignment); |
| } else if (syntheticAssignment is SuperPropertySet) { |
| return new SuperPropertyForInVariable(syntheticAssignment); |
| } else if (syntheticAssignment is StaticSet) { |
| return new StaticForInVariable(syntheticAssignment); |
| } else if (syntheticAssignment is InvalidExpression || hasProblem) { |
| return new InvalidForInVariable(syntheticAssignment); |
| } else { |
| // Coverage-ignore-block(suite): Not run. |
| UriOffset uriOffset = _computeUriOffset(syntheticAssignment!); |
| return problems.unhandled( |
| "${syntheticAssignment.runtimeType}", |
| "handleForInStatementWithoutVariable", |
| uriOffset.fileOffset, |
| uriOffset.uri); |
| } |
| } |
| |
| ForInResult _handleForInWithoutVariable( |
| TreeNode node, |
| VariableDeclaration variable, |
| Expression iterable, |
| Expression? syntheticAssignment, |
| Statement? expressionEffects, |
| {bool isAsync = false, |
| required bool hasProblem}) { |
| ForInVariable forInVariable = |
| computeForInVariable(syntheticAssignment, hasProblem); |
| DartType elementType = forInVariable.computeElementType(this); |
| ExpressionInferenceResult iterableResult = |
| inferForInIterable(iterable, elementType, isAsync: isAsync); |
| DartType inferredType = iterableResult.inferredType; |
| variable.type = inferredType; |
| // This is matched by the call to [forEach_end] in |
| // [inferElement], [inferMapEntry] or [inferForInStatement]. |
| flowAnalysis.forEach_bodyBegin(node); |
| syntheticAssignment = forInVariable.inferAssignment(this, inferredType); |
| if (syntheticAssignment is VariableSet) { |
| flowAnalysis.write(node, variable, inferredType, null); |
| } |
| if (expressionEffects != null) { |
| StatementInferenceResult result = inferStatement(expressionEffects); |
| expressionEffects = result.hasChanged |
| ? |
| // Coverage-ignore(suite): Not run. |
| result.statement |
| : expressionEffects; |
| } |
| |
| return new ForInResult(variable, iterableResult.expression, |
| syntheticAssignment, expressionEffects); |
| } |
| |
| ForInResult _handlePatternForIn( |
| TreeNode node, |
| VariableDeclaration variable, |
| Expression iterable, |
| Expression? syntheticAssignment, |
| PatternVariableDeclaration patternVariableDeclaration, |
| {bool isAsync = false, |
| required bool hasProblem}) { |
| int? stackBase; |
| assert(checkStackBase(node, stackBase = stackHeight)); |
| |
| PatternForInResult<DartType, InvalidExpression> result = |
| analyzePatternForIn( |
| node: node, |
| hasAwait: isAsync, |
| pattern: patternVariableDeclaration.pattern, |
| expression: iterable, |
| dispatchBody: () {}); |
| patternVariableDeclaration.matchedValueType = result.elementType; |
| if (result.patternForInExpressionIsNotIterableError != null) { |
| assert(libraryBuilder.loader.assertProblemReportedElsewhere( |
| "InferenceVisitorImpl._handlePatternForIn: " |
| "can't infer expression in a for-in pattern.", |
| expectedPhase: CompilationPhaseForProblemReporting.bodyBuilding)); |
| } |
| |
| assert(checkStack(node, stackBase, [ |
| /* pattern = */ ValueKinds.Pattern, |
| /* initializer = */ ValueKinds.Expression, |
| ])); |
| |
| Object? rewrite = popRewrite(); |
| if (!identical(rewrite, patternVariableDeclaration.pattern)) { |
| // Coverage-ignore-block(suite): Not run. |
| patternVariableDeclaration.pattern = (rewrite as Pattern) |
| ..parent = patternVariableDeclaration; |
| } |
| |
| rewrite = popRewrite(); |
| if (!identical(rewrite, patternVariableDeclaration.initializer)) { |
| iterable = (rewrite as Expression)..parent = node; |
| } |
| |
| ForInVariable forInVariable = |
| new PatternVariableDeclarationForInVariable(patternVariableDeclaration); |
| |
| variable.type = result.elementType; |
| iterable = ensureAssignable( |
| wrapType( |
| const DynamicType(), |
| isAsync ? coreTypes.streamClass : coreTypes.iterableClass, |
| Nullability.nonNullable), |
| result.expressionType, |
| iterable, |
| errorTemplate: templateForInLoopTypeNotIterable, |
| nullabilityErrorTemplate: templateForInLoopTypeNotIterableNullability, |
| nullabilityPartErrorTemplate: |
| templateForInLoopTypeNotIterablePartNullability); |
| // This is matched by the call to [forEach_end] in |
| // [inferElement], [inferMapEntry] or [inferForInStatement]. |
| flowAnalysis.forEach_bodyBegin(node); |
| syntheticAssignment = |
| forInVariable.inferAssignment(this, result.elementType); |
| if (syntheticAssignment is VariableSet) { |
| // Coverage-ignore-block(suite): Not run. |
| flowAnalysis.write(node, variable, result.elementType, null); |
| } |
| |
| return new ForInResult(variable, /*iterableResult.expression*/ iterable, |
| syntheticAssignment, patternVariableDeclaration); |
| } |
| |
| ForInResult handleForInWithoutVariable( |
| TreeNode node, |
| VariableDeclaration variable, |
| Expression iterable, |
| Expression? syntheticAssignment, |
| Statement? expressionEffects, |
| {bool isAsync = false, |
| required bool hasProblem}) { |
| if (expressionEffects is PatternVariableDeclaration) { |
| return _handlePatternForIn( |
| node, variable, iterable, syntheticAssignment, expressionEffects, |
| isAsync: isAsync, hasProblem: hasProblem); |
| } else { |
| return _handleForInWithoutVariable( |
| node, variable, iterable, syntheticAssignment, expressionEffects, |
| isAsync: isAsync, hasProblem: hasProblem); |
| } |
| } |
| |
| @override |
| StatementInferenceResult visitForInStatement(ForInStatement node) { |
| assert(node.variable.name != null); |
| ForInResult result = handleForInDeclaringVariable( |
| node, node.variable, node.iterable, null, |
| isAsync: node.isAsync); |
| |
| StatementInferenceResult bodyResult = inferStatement(node.body); |
| |
| // This is matched by the call to [forEach_bodyBegin] in |
| // [handleForInWithoutVariable] or [handleForInDeclaringVariable]. |
| flowAnalysis.forEach_end(); |
| |
| Statement body = bodyResult.hasChanged ? bodyResult.statement : node.body; |
| if (result.expressionSideEffects != null) { |
| body = combineStatements(result.expressionSideEffects!, body); |
| } |
| if (result.syntheticAssignment != null) { |
| // Coverage-ignore-block(suite): Not run. |
| body = combineStatements( |
| createExpressionStatement(result.syntheticAssignment!), body); |
| } |
| node.variable = result.variable..parent = node; |
| node.iterable = result.iterable..parent = node; |
| node.body = body..parent = node; |
| return const StatementInferenceResult(); |
| } |
| |
| StatementInferenceResult visitForInStatementWithSynthesizedVariable( |
| ForInStatementWithSynthesizedVariable node) { |
| assert(node.variable!.name == null); |
| ForInResult result = handleForInWithoutVariable(node, node.variable!, |
| node.iterable, node.syntheticAssignment, node.expressionEffects, |
| isAsync: node.isAsync, hasProblem: node.hasProblem); |
| |
| StatementInferenceResult bodyResult = inferStatement(node.body); |
| |
| // This is matched by the call to [forEach_bodyBegin] in |
| // [handleForInWithoutVariable] or [handleForInDeclaringVariable]. |
| flowAnalysis.forEach_end(); |
| |
| Statement body = bodyResult.hasChanged |
| ? |
| // Coverage-ignore(suite): Not run. |
| bodyResult.statement |
| : node.body; |
| if (result.expressionSideEffects != null) { |
| body = combineStatements(result.expressionSideEffects!, body); |
| } |
| if (result.syntheticAssignment != null) { |
| body = combineStatements( |
| createExpressionStatement(result.syntheticAssignment!), body); |
| } |
| Statement replacement = new ForInStatement( |
| result.variable, result.iterable, body, |
| isAsync: node.isAsync) |
| ..fileOffset = node.fileOffset |
| ..bodyOffset = node.bodyOffset; |
| libraryBuilder.loader.dataForTesting |
| // Coverage-ignore(suite): Not run. |
| ?.registerAlias(node, replacement); |
| return new StatementInferenceResult.single(replacement); |
| } |
| |
| @override |
| StatementInferenceResult visitForStatement(ForStatement node) { |
| List<VariableDeclaration>? variables; |
| for (int index = 0; index < node.variables.length; index++) { |
| VariableDeclaration variable = node.variables[index]; |
| if (variable.name == null) { |
| if (variable.initializer != null) { |
| ExpressionInferenceResult result = inferExpression( |
| variable.initializer!, const UnknownType(), |
| isVoidAllowed: true); |
| variable.initializer = result.expression..parent = variable; |
| variable.type = result.inferredType; |
| } |
| } else { |
| StatementInferenceResult variableResult = inferStatement(variable); |
| if (variableResult.hasChanged) { |
| // Coverage-ignore-block(suite): Not run. |
| if (variables == null) { |
| variables = <VariableDeclaration>[]; |
| variables.addAll(node.variables.sublist(0, index)); |
| } |
| if (variableResult.statementCount == 1) { |
| variables.add(variableResult.statement as VariableDeclaration); |
| } else { |
| for (Statement variable in variableResult.statements) { |
| variables.add(variable as VariableDeclaration); |
| } |
| } |
| } |
| // Coverage-ignore(suite): Not run. |
| else if (variables != null) { |
| variables.add(variable); |
| } |
| } |
| } |
| if (variables != null) { |
| // Coverage-ignore-block(suite): Not run. |
| node.variables.clear(); |
| node.variables.addAll(variables); |
| setParents(variables, node); |
| } |
| flowAnalysis.for_conditionBegin(node); |
| if (node.condition != null) { |
| InterfaceType expectedType = |
| coreTypes.boolRawType(Nullability.nonNullable); |
| ExpressionInferenceResult conditionResult = |
| inferExpression(node.condition!, expectedType, isVoidAllowed: true); |
| Expression condition = |
| ensureAssignableResult(expectedType, conditionResult).expression; |
| node.condition = condition..parent = node; |
| } |
| |
| flowAnalysis.for_bodyBegin(node, node.condition); |
| StatementInferenceResult bodyResult = inferStatement(node.body); |
| if (bodyResult.hasChanged) { |
| // Coverage-ignore-block(suite): Not run. |
| node.body = bodyResult.statement..parent = node; |
| } |
| flowAnalysis.for_updaterBegin(); |
| for (int index = 0; index < node.updates.length; index++) { |
| ExpressionInferenceResult updateResult = inferExpression( |
| node.updates[index], const UnknownType(), |
| isVoidAllowed: true); |
| node.updates[index] = updateResult.expression..parent = node; |
| } |
| flowAnalysis.for_end(); |
| return const StatementInferenceResult(); |
| } |
| |
| FunctionType visitFunctionNode(FunctionNode node, DartType? typeContext, |
| DartType? returnContext, int returnTypeInstrumentationOffset) { |
| return inferLocalFunction(this, node, typeContext, |
| returnTypeInstrumentationOffset, returnContext); |
| } |
| |
| @override |
| StatementInferenceResult visitFunctionDeclaration( |
| covariant FunctionDeclarationImpl node) { |
| bool oldInTryOrLocalFunction = _inTryOrLocalFunction; |
| _inTryOrLocalFunction = true; |
| VariableDeclaration variable = node.variable; |
| flowAnalysis.functionExpression_begin(node); |
| inferMetadata(this, variable, variable.annotations); |
| DartType? returnContext = |
| node.hasImplicitReturnType ? null : node.function.returnType; |
| FunctionType inferredType = |
| visitFunctionNode(node.function, null, returnContext, node.fileOffset); |
| if (dataForTesting != null && |
| // Coverage-ignore(suite): Not run. |
| node.hasImplicitReturnType) { |
| // Coverage-ignore-block(suite): Not run. |
| dataForTesting!.typeInferenceResult.inferredVariableTypes[node] = |
| inferredType.returnType; |
| } |
| variable.type = inferredType; |
| flowAnalysis.declare(variable, variable.type, initialized: true); |
| flowAnalysis.functionExpression_end(); |
| _inTryOrLocalFunction = oldInTryOrLocalFunction; |
| return const StatementInferenceResult(); |
| } |
| |
| @override |
| ExpressionInferenceResult visitFunctionExpression( |
| FunctionExpression node, DartType typeContext) { |
| bool oldInTryOrLocalFunction = _inTryOrLocalFunction; |
| _inTryOrLocalFunction = true; |
| flowAnalysis.functionExpression_begin(node); |
| FunctionType inferredType = |
| visitFunctionNode(node.function, typeContext, null, node.fileOffset); |
| if (dataForTesting != null) { |
| // Coverage-ignore-block(suite): Not run. |
| dataForTesting!.typeInferenceResult.inferredVariableTypes[node] = |
| inferredType.returnType; |
| } |
| flowAnalysis.functionExpression_end(); |
| _inTryOrLocalFunction = oldInTryOrLocalFunction; |
| return new ExpressionInferenceResult(inferredType, node); |
| } |
| |
| ExpressionInferenceResult visitIfNullExpression( |
| IfNullExpression node, DartType typeContext) { |
| // An if-null expression `E` of the form `e1 ?? e2` with context type `K` is |
| // analyzed as follows: |
| // |
| // - Let `T1` be the type of `e1` inferred with context type `K?`. |
| ExpressionInferenceResult lhsResult = inferExpression( |
| node.left, computeNullable(typeContext), |
| isVoidAllowed: false); |
| DartType t1 = lhsResult.inferredType; |
| |
| // This ends any shorting in `node.left`. |
| Expression left = lhsResult.expression; |
| |
| flowAnalysis.ifNullExpression_rightBegin(node.left, t1); |
| |
| // - Let `T2` be the type of `e2` inferred with context type `J`, where: |
| // - If `K` is `_` or `dynamic`, `J = T1`. |
| DartType j; |
| if (typeContext is UnknownType || typeContext is DynamicType) { |
| j = t1; |
| } else |
| // - Otherwise, `J = K`. |
| { |
| j = typeContext; |
| } |
| ExpressionInferenceResult rhsResult = |
| inferExpression(node.right, j, isVoidAllowed: true); |
| DartType t2 = rhsResult.inferredType; |
| flowAnalysis.ifNullExpression_end(); |
| |
| // - Let `T` be `UP(NonNull(T1), T2)`. |
| DartType nonNullT1 = t1.toNonNull(); |
| DartType t = typeSchemaEnvironment.getStandardUpperBound(nonNullT1, t2); |
| |
| // - Let `S` be the greatest closure of `K`. |
| DartType s = computeGreatestClosure(typeContext); |
| |
| DartType inferredType; |
| // If `inferenceUpdate3` is not enabled, then the type of `E` is `T`. |
| if (!libraryBuilder.libraryFeatures.inferenceUpdate3.isEnabled) { |
| inferredType = t; |
| } else |
| // - If `T <: S`, then the type of `E` is `T`. |
| if (typeSchemaEnvironment.isSubtypeOf( |
| t, s, SubtypeCheckMode.withNullabilities)) { |
| inferredType = t; |
| } else |
| // - Otherwise, if `NonNull(T1) <: S` and `T2 <: S`, then the type of `E` is |
| // `S`. |
| if (typeSchemaEnvironment.isSubtypeOf( |
| nonNullT1, s, SubtypeCheckMode.withNullabilities) && |
| typeSchemaEnvironment.isSubtypeOf( |
| t2, s, SubtypeCheckMode.withNullabilities)) { |
| inferredType = s; |
| } else |
| // - Otherwise, the type of `E` is `T`. |
| { |
| inferredType = t; |
| } |
| |
| Expression replacement; |
| if (left is ThisExpression) { |
| replacement = left; |
| } else { |
| VariableDeclaration variable = createVariable(left, t1); |
| Expression equalsNull = createEqualsNull(createVariableGet(variable), |
| fileOffset: lhsResult.expression.fileOffset); |
| VariableGet variableGet = createVariableGet(variable); |
| if (!identical(nonNullT1, t1)) { |
| variableGet.promotedType = nonNullT1; |
| } |
| ConditionalExpression conditional = new ConditionalExpression( |
| equalsNull, rhsResult.expression, variableGet, inferredType) |
| ..fileOffset = node.fileOffset; |
| replacement = new Let(variable, conditional) |
| ..fileOffset = node.fileOffset; |
| } |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| @override |
| StatementInferenceResult visitIfStatement(IfStatement node) { |
| flowAnalysis.ifStatement_conditionBegin(); |
| InterfaceType expectedType = coreTypes.boolRawType(Nullability.nonNullable); |
| ExpressionInferenceResult conditionResult = |
| inferExpression(node.condition, expectedType, isVoidAllowed: true); |
| Expression condition = |
| ensureAssignableResult(expectedType, conditionResult).expression; |
| node.condition = condition..parent = node; |
| flowAnalysis.ifStatement_thenBegin(condition, node); |
| StatementInferenceResult thenResult = inferStatement(node.then); |
| if (thenResult.hasChanged) { |
| node.then = thenResult.statement..parent = node; |
| } |
| if (node.otherwise != null) { |
| flowAnalysis.ifStatement_elseBegin(); |
| StatementInferenceResult otherwiseResult = |
| inferStatement(node.otherwise!); |
| if (otherwiseResult.hasChanged) { |
| // Coverage-ignore-block(suite): Not run. |
| node.otherwise = otherwiseResult.statement..parent = node; |
| } |
| } |
| flowAnalysis.ifStatement_end(node.otherwise != null); |
| return const StatementInferenceResult(); |
| } |
| |
| @override |
| StatementInferenceResult visitIfCaseStatement(IfCaseStatement node) { |
| int? stackBase; |
| assert(checkStackBase(node, stackBase = stackHeight)); |
| |
| IfCaseStatementResult<DartType, InvalidExpression> analysisResult = |
| analyzeIfCaseStatement(node, node.expression, node.patternGuard.pattern, |
| node.patternGuard.guard, node.then, node.otherwise, { |
| for (VariableDeclaration variable |
| in node.patternGuard.pattern.declaredVariables) |
| variable.name!: variable |
| }); |
| |
| node.matchedValueType = analysisResult.matchedExpressionType; |
| |
| assert(checkStack(node, stackBase, [ |
| /* ifFalse = */ ValueKinds.StatementOrNull, |
| /* ifTrue = */ ValueKinds.Statement, |
| /* guard = */ ValueKinds.ExpressionOrNull, |
| /* pattern = */ ValueKinds.Pattern, |
| /* scrutinee = */ ValueKinds.Expression, |
| ])); |
| |
| Object? rewrite = popRewrite(NullValues.Statement); |
| if (!identical(node.otherwise, rewrite)) { |
| // Coverage-ignore-block(suite): Not run. |
| node.otherwise = (rewrite as Statement)..parent = node; |
| } |
| rewrite = popRewrite(); |
| if (!identical(node.then, rewrite)) { |
| // Coverage-ignore-block(suite): Not run. |
| node.then = (rewrite as Statement)..parent = node; |
| } |
| rewrite = popRewrite(NullValues.Expression); |
| InvalidExpression? guardError = analysisResult.nonBooleanGuardError; |
| if (guardError != null) { |
| node.patternGuard.guard = guardError..parent = node.patternGuard; |
| } else { |
| if (!identical(node.patternGuard.guard, rewrite)) { |
| node.patternGuard.guard = (rewrite as Expression) |
| ..parent = node.patternGuard; |
| } |
| if (analysisResult.guardType is DynamicType) { |
| node.patternGuard.guard = _createImplicitAs( |
| node.patternGuard.guard!.fileOffset, |
| node.patternGuard.guard!, |
| coreTypes.boolNonNullableRawType) |
| ..parent = node.patternGuard; |
| } |
| } |
| rewrite = popRewrite(); |
| if (!identical(node.patternGuard.pattern, rewrite)) { |
| node.patternGuard.pattern = (rewrite as Pattern) |
| ..parent = node.patternGuard; |
| } |
| rewrite = popRewrite(); |
| if (!identical(node.expression, rewrite)) { |
| node.expression = (rewrite as Expression)..parent = node; |
| } |
| |
| assert(checkStack(node, stackBase, [/*empty*/])); |
| |
| return const StatementInferenceResult(); |
| } |
| |
| ExpressionInferenceResult visitIntJudgment( |
| IntJudgment node, DartType typeContext) { |
| if (isDoubleContext(typeContext)) { |
| double? doubleValue = node.asDouble(); |
| if (doubleValue != null) { |
| Expression replacement = new DoubleLiteral(doubleValue) |
| ..fileOffset = node.fileOffset; |
| DartType inferredType = |
| coreTypes.doubleRawType(Nullability.nonNullable); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| } |
| Expression? error = checkWebIntLiteralsErrorIfUnexact( |
| node.value, node.literal, node.fileOffset); |
| if (error != null) { |
| // Coverage-ignore-block(suite): Not run. |
| return new ExpressionInferenceResult(const DynamicType(), error); |
| } |
| DartType inferredType = coreTypes.intRawType(Nullability.nonNullable); |
| return new ExpressionInferenceResult(inferredType, node); |
| } |
| |
| ExpressionInferenceResult visitShadowLargeIntLiteral( |
| ShadowLargeIntLiteral node, DartType typeContext) { |
| if (isDoubleContext(typeContext)) { |
| // Coverage-ignore-block(suite): Not run. |
| double? doubleValue = node.asDouble(); |
| if (doubleValue != null) { |
| Expression replacement = new DoubleLiteral(doubleValue) |
| ..fileOffset = node.fileOffset; |
| DartType inferredType = |
| coreTypes.doubleRawType(Nullability.nonNullable); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| } |
| |
| int? intValue = node.asInt64(); |
| if (intValue == null) { |
| Expression replacement = helper.buildProblem( |
| templateIntegerLiteralIsOutOfRange.withArguments(node.literal), |
| node.fileOffset, |
| node.literal.length); |
| return new ExpressionInferenceResult(const DynamicType(), replacement); |
| } |
| Expression? error = checkWebIntLiteralsErrorIfUnexact( |
| intValue, node.literal, node.fileOffset); |
| if (error != null) { |
| // Coverage-ignore-block(suite): Not run. |
| return new ExpressionInferenceResult(const DynamicType(), error); |
| } |
| Expression replacement = new IntLiteral(intValue); |
| DartType inferredType = coreTypes.intRawType(Nullability.nonNullable); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| InitializerInferenceResult visitShadowInvalidInitializer( |
| ShadowInvalidInitializer node) { |
| ExpressionInferenceResult initializerResult = inferExpression( |
| node.variable.initializer!, const UnknownType(), |
| isVoidAllowed: false); |
| node.variable.initializer = initializerResult.expression |
| ..parent = node.variable; |
| return const SuccessfulInitializerInferenceResult(); |
| } |
| |
| InitializerInferenceResult visitShadowInvalidFieldInitializer( |
| ShadowInvalidFieldInitializer node) { |
| ExpressionInferenceResult initializerResult = |
| inferExpression(node.value, node.fieldType, isVoidAllowed: false); |
| node.value = initializerResult.expression..parent = node; |
| return const SuccessfulInitializerInferenceResult(); |
| } |
| |
| @override |
| ExpressionInferenceResult visitIsExpression( |
| IsExpression node, DartType typeContext) { |
| ExpressionInferenceResult operandResult = inferExpression( |
| node.operand, const UnknownType(), |
| isVoidAllowed: false); |
| node.operand = operandResult.expression..parent = node; |
| flowAnalysis.isExpression_end( |
| node, node.operand, /*isNot:*/ false, node.type); |
| return new ExpressionInferenceResult( |
| coreTypes.boolRawType(Nullability.nonNullable), node); |
| } |
| |
| @override |
| StatementInferenceResult visitLabeledStatement(LabeledStatement node) { |
| flowAnalysis.labeledStatement_begin(node); |
| StatementInferenceResult bodyResult = inferStatement(node.body); |
| flowAnalysis.labeledStatement_end(); |
| if (bodyResult.hasChanged) { |
| // Coverage-ignore-block(suite): Not run. |
| node.body = bodyResult.statement..parent = node; |
| } |
| return const StatementInferenceResult(); |
| } |
| |
| DartType? getSpreadElementType( |
| DartType spreadType, DartType spreadTypeBound, bool isNullAware) { |
| if (coreTypes.isNull(spreadTypeBound)) { |
| return isNullAware ? const NeverType.nonNullable() : null; |
| } |
| if (spreadTypeBound is TypeDeclarationType) { |
| List<DartType>? supertypeArguments = |
| typeSchemaEnvironment.getTypeArgumentsAsInstanceOf( |
| spreadTypeBound, coreTypes.iterableClass); |
| if (supertypeArguments == null) { |
| return null; |
| } |
| return supertypeArguments.single; |
| } else if (spreadType is DynamicType) { |
| return const DynamicType(); |
| } else if (coreTypes.isBottom(spreadType)) { |
| return const NeverType.nonNullable(); |
| } |
| return null; |
| } |
| |
| ExpressionInferenceResult _inferSpreadElement( |
| SpreadElement element, |
| DartType inferredTypeArgument, |
| Map<TreeNode, DartType> inferredSpreadTypes, |
| Map<Expression, DartType> inferredConditionTypes) { |
| ExpressionInferenceResult spreadResult = inferExpression( |
| element.expression, |
| new InterfaceType( |
| coreTypes.iterableClass, |
| element.isNullAware |
| ? Nullability.nullable |
| : Nullability.nonNullable, |
| <DartType>[inferredTypeArgument]), |
| isVoidAllowed: true); |
| element.expression = spreadResult.expression..parent = element; |
| DartType spreadType = spreadResult.inferredType; |
| inferredSpreadTypes[element.expression] = spreadType; |
| Expression replacement = element; |
| DartType spreadTypeBound = spreadType.nonTypeVariableBound; |
| DartType? spreadElementType = |
| getSpreadElementType(spreadType, spreadTypeBound, element.isNullAware); |
| if (spreadElementType == null) { |
| if (coreTypes.isNull(spreadTypeBound) && !element.isNullAware) { |
| replacement = helper.buildProblem( |
| templateNonNullAwareSpreadIsNull.withArguments(spreadType), |
| element.expression.fileOffset, |
| 1); |
| } else { |
| if (spreadType.isPotentiallyNullable && |
| spreadType is! DynamicType && |
| spreadType is! NullType && |
| !element.isNullAware) { |
| Expression receiver = element.expression; |
| replacement = helper.buildProblem( |
| messageNullableSpreadError, receiver.fileOffset, 1, |
| context: getWhyNotPromotedContext( |
| flowAnalysis.whyNotPromoted(receiver)(), |
| element, |
| // Coverage-ignore(suite): Not run. |
| (type) => !type.isPotentiallyNullable)); |
| } |
| |
| replacement = helper.buildProblem( |
| templateSpreadTypeMismatch.withArguments(spreadType), |
| element.expression.fileOffset, |
| 1); |
| _copyNonPromotionReasonToReplacement(element, replacement); |
| } |
| } else if (spreadTypeBound is InterfaceType) { |
| if (!isAssignable(inferredTypeArgument, spreadElementType)) { |
| IsSubtypeOf subtypeCheckResult = |
| typeSchemaEnvironment.performNullabilityAwareSubtypeCheck( |
| spreadElementType, inferredTypeArgument); |
| if (subtypeCheckResult.isSubtypeWhenIgnoringNullabilities()) { |
| if (spreadElementType == subtypeCheckResult.subtype && |
| inferredTypeArgument == subtypeCheckResult.supertype) { |
| replacement = helper.buildProblem( |
| templateSpreadElementTypeMismatchNullability.withArguments( |
| spreadElementType, inferredTypeArgument), |
| element.expression.fileOffset, |
| 1); |
| } else { |
| replacement = helper.buildProblem( |
| templateSpreadElementTypeMismatchPartNullability.withArguments( |
| spreadElementType, |
| inferredTypeArgument, |
| subtypeCheckResult.subtype!, |
| subtypeCheckResult.supertype!), |
| element.expression.fileOffset, |
| 1); |
| } |
| } else { |
| replacement = helper.buildProblem( |
| templateSpreadElementTypeMismatch.withArguments( |
| spreadElementType, inferredTypeArgument), |
| element.expression.fileOffset, |
| 1); |
| } |
| } |
| if (spreadType.isPotentiallyNullable && |
| spreadType is! DynamicType && |
| spreadType is! NullType && |
| !element.isNullAware) { |
| Expression receiver = element.expression; |
| replacement = helper.buildProblem( |
| messageNullableSpreadError, receiver.fileOffset, 1, |
| context: getWhyNotPromotedContext( |
| flowAnalysis.whyNotPromoted(receiver)(), |
| element, |
| // Coverage-ignore(suite): Not run. |
| (type) => !type.isPotentiallyNullable)); |
| _copyNonPromotionReasonToReplacement(element, replacement); |
| } |
| } |
| |
| // Use 'dynamic' for error recovery. |
| element.elementType = spreadElementType ?? const DynamicType(); |
| return new ExpressionInferenceResult(element.elementType!, replacement); |
| } |
| |
| ExpressionInferenceResult _inferNullAwareElement( |
| NullAwareElement element, |
| DartType inferredTypeArgument, |
| Map<TreeNode, DartType> inferredSpreadTypes, |
| Map<Expression, DartType> inferredConditionTypes) { |
| // TODO(cstefantsova): Ensure the flow analysis is properly invoked when it |
| // supports null-aware elements. |
| |
| ExpressionInferenceResult expressionResult = inferElement( |
| element.expression, |
| inferredTypeArgument.withDeclaredNullability(Nullability.nullable), |
| inferredSpreadTypes, |
| inferredConditionTypes); |
| element.expression = expressionResult.expression..parent = element; |
| |
| return new ExpressionInferenceResult( |
| computeNonNull(expressionResult.inferredType), element); |
| } |
| |
| ExpressionInferenceResult _inferIfElement( |
| IfElement element, |
| DartType inferredTypeArgument, |
| Map<TreeNode, DartType> inferredSpreadTypes, |
| Map<Expression, DartType> inferredConditionTypes) { |
| flowAnalysis.ifStatement_conditionBegin(); |
| DartType boolType = coreTypes.boolRawType(Nullability.nonNullable); |
| ExpressionInferenceResult conditionResult = |
| inferExpression(element.condition, boolType, isVoidAllowed: false); |
| Expression condition = |
| ensureAssignableResult(boolType, conditionResult).expression; |
| element.condition = condition..parent = element; |
| flowAnalysis.ifStatement_thenBegin(condition, element); |
| ExpressionInferenceResult thenResult = inferElement(element.then, |
| inferredTypeArgument, inferredSpreadTypes, inferredConditionTypes); |
| element.then = thenResult.expression..parent = element; |
| ExpressionInferenceResult? otherwiseResult; |
| if (element.otherwise != null) { |
| flowAnalysis.ifStatement_elseBegin(); |
| otherwiseResult = inferElement(element.otherwise!, inferredTypeArgument, |
| inferredSpreadTypes, inferredConditionTypes); |
| element.otherwise = otherwiseResult.expression..parent = element; |
| } |
| flowAnalysis.ifStatement_end(element.otherwise != null); |
| return new ExpressionInferenceResult( |
| otherwiseResult == null |
| ? thenResult.inferredType |
| : typeSchemaEnvironment.getStandardUpperBound( |
| thenResult.inferredType, otherwiseResult.inferredType), |
| element); |
| } |
| |
| ExpressionInferenceResult _inferIfCaseElement( |
| IfCaseElement element, |
| DartType inferredTypeArgument, |
| Map<TreeNode, DartType> inferredSpreadTypes, |
| Map<Expression, DartType> inferredConditionTypes) { |
| int? stackBase; |
| assert(checkStackBase(element, stackBase = stackHeight)); |
| |
| ListAndSetElementInferenceContext context = |
| new ListAndSetElementInferenceContext( |
| inferredTypeArgument: inferredTypeArgument, |
| inferredSpreadTypes: inferredSpreadTypes, |
| inferredConditionTypes: inferredConditionTypes); |
| IfCaseStatementResult<DartType, InvalidExpression> analysisResult = |
| analyzeIfCaseElement( |
| node: element, |
| expression: element.expression, |
| pattern: element.patternGuard.pattern, |
| variables: { |
| for (VariableDeclaration variable |
| in element.patternGuard.pattern.declaredVariables) |
| variable.name!: variable |
| }, |
| guard: element.patternGuard.guard, |
| ifTrue: element.then, |
| ifFalse: element.otherwise, |
| context: context); |
| |
| element.matchedValueType = analysisResult.matchedExpressionType; |
| |
| assert(checkStack(element, stackBase, [ |
| /* ifFalse = */ ValueKinds.ExpressionOrNull, |
| /* ifTrue = */ ValueKinds.Expression, |
| /* guard = */ ValueKinds.ExpressionOrNull, |
| /* pattern = */ ValueKinds.Pattern, |
| /* scrutinee = */ ValueKinds.Expression, |
| ])); |
| |
| Object? rewrite = popRewrite(NullValues.Expression); |
| if (!identical(element.otherwise, rewrite)) { |
| // Coverage-ignore-block(suite): Not run. |
| element.otherwise = (rewrite as Expression?)?..parent = element; |
| } |
| |
| rewrite = popRewrite(); |
| if (!identical(element.then, rewrite)) { |
| // Coverage-ignore-block(suite): Not run. |
| element.then = (rewrite as Expression)..parent = element; |
| } |
| |
| PatternGuard patternGuard = element.patternGuard; |
| rewrite = popRewrite(NullValues.Expression); |
| InvalidExpression? guardError = analysisResult.nonBooleanGuardError; |
| if (guardError != null) { |
| // Coverage-ignore-block(suite): Not run. |
| patternGuard.guard = guardError..parent = patternGuard; |
| } else { |
| if (!identical(patternGuard.guard, rewrite)) { |
| patternGuard.guard = (rewrite as Expression?)?..parent = patternGuard; |
| } |
| if (analysisResult.guardType is DynamicType) { |
| patternGuard.guard = _createImplicitAs(patternGuard.guard!.fileOffset, |
| patternGuard.guard!, coreTypes.boolNonNullableRawType) |
| ..parent = patternGuard; |
| } |
| } |
| |
| rewrite = popRewrite(); |
| if (!identical(patternGuard.pattern, rewrite)) { |
| // Coverage-ignore-block(suite): Not run. |
| patternGuard.pattern = (rewrite as Pattern)..parent = patternGuard; |
| } |
| |
| rewrite = popRewrite(); |
| if (!identical(element.expression, rewrite)) { |
| // Coverage-ignore-block(suite): Not run. |
| element.expression = (rewrite as Expression)..parent = patternGuard; |
| } |
| |
| DartType thenType = context.inferredConditionTypes[element.then]!; |
| DartType? otherwiseType = element.otherwise == null |
| ? null |
| : context.inferredConditionTypes[element.otherwise!]!; |
| return new ExpressionInferenceResult( |
| otherwiseType == null |
| ? thenType |
| : typeSchemaEnvironment.getStandardUpperBound( |
| thenType, otherwiseType), |
| element); |
| } |
| |
| ExpressionInferenceResult _inferPatternForElement( |
| PatternForElement element, |
| DartType inferredTypeArgument, |
| Map<TreeNode, DartType> inferredSpreadTypes, |
| Map<Expression, DartType> inferredConditionTypes) { |
| int? stackBase; |
| assert(checkStackBase(element, stackBase = stackHeight)); |
| |
| PatternVariableDeclaration patternVariableDeclaration = |
| element.patternVariableDeclaration; |
| PatternVariableDeclarationAnalysisResult<DartType, DartType> |
| analysisResult = analyzePatternVariableDeclaration( |
| patternVariableDeclaration, |
| patternVariableDeclaration.pattern, |
| patternVariableDeclaration.initializer, |
| isFinal: patternVariableDeclaration.isFinal); |
| patternVariableDeclaration.matchedValueType = |
| analysisResult.initializerType; |
| |
| assert(checkStack(element, stackBase, [ |
| /* pattern = */ ValueKinds.Pattern, |
| /* initializer = */ ValueKinds.Expression, |
| ])); |
| |
| Object? rewrite = popRewrite(NullValues.Expression); |
| if (!identical(patternVariableDeclaration.pattern, rewrite)) { |
| // Coverage-ignore-block(suite): Not run. |
| patternVariableDeclaration.pattern = (rewrite as Pattern) |
| ..parent = patternVariableDeclaration; |
| } |
| |
| rewrite = popRewrite(); |
| if (!identical(patternVariableDeclaration.initializer, rewrite)) { |
| patternVariableDeclaration.initializer = (rewrite as Expression) |
| ..parent = patternVariableDeclaration; |
| } |
| |
| List<VariableDeclaration> declaredVariables = |
| patternVariableDeclaration.pattern.declaredVariables; |
| assert(declaredVariables.length == element.intermediateVariables.length); |
| assert(declaredVariables.length == element.variables.length); |
| for (int i = 0; i < declaredVariables.length; i++) { |
| DartType type = declaredVariables[i].type; |
| element.intermediateVariables[i].type = type; |
| element.variables[i].type = type; |
| } |
| |
| return _inferForElementBase(element, inferredTypeArgument, |
| inferredSpreadTypes, inferredConditionTypes); |
| } |
| |
| ExpressionInferenceResult _inferForElement( |
| ForElement element, |
| DartType inferredTypeArgument, |
| Map<TreeNode, DartType> inferredSpreadTypes, |
| Map<Expression, DartType> inferredConditionTypes) { |
| return _inferForElementBase(element, inferredTypeArgument, |
| inferredSpreadTypes, inferredConditionTypes); |
| } |
| |
| ExpressionInferenceResult _inferForElementBase( |
| ForElementBase element, |
| DartType inferredTypeArgument, |
| Map<TreeNode, DartType> inferredSpreadTypes, |
| Map<Expression, DartType> inferredConditionTypes) { |
| // TODO(johnniwinther): Use _visitStatements instead. |
| List<VariableDeclaration>? variables; |
| for (int index = 0; index < element.variables.length; index++) { |
| VariableDeclaration variable = element.variables[index]; |
| if (variable.name == null) { |
| if (variable.initializer != null) { |
| ExpressionInferenceResult initializerResult = inferExpression( |
| variable.initializer!, variable.type, |
| isVoidAllowed: true); |
| variable.initializer = initializerResult.expression |
| ..parent = variable; |
| variable.type = initializerResult.inferredType; |
| } |
| } else { |
| StatementInferenceResult variableResult = inferStatement(variable); |
| if (variableResult.hasChanged) { |
| // Coverage-ignore-block(suite): Not run. |
| if (variables == null) { |
| variables = <VariableDeclaration>[]; |
| variables.addAll(element.variables.sublist(0, index)); |
| } |
| if (variableResult.statementCount == 1) { |
| variables.add(variableResult.statement as VariableDeclaration); |
| } else { |
| for (Statement variable in variableResult.statements) { |
| variables.add(variable as VariableDeclaration); |
| } |
| } |
| } |
| // Coverage-ignore(suite): Not run. |
| else if (variables != null) { |
| variables.add(variable); |
| } |
| } |
| } |
| if (variables != null) { |
| // Coverage-ignore-block(suite): Not run. |
| element.variables.clear(); |
| element.variables.addAll(variables); |
| setParents(variables, element); |
| } |
| |
| flowAnalysis.for_conditionBegin(element); |
| if (element.condition != null) { |
| ExpressionInferenceResult conditionResult = inferExpression( |
| element.condition!, coreTypes.boolRawType(Nullability.nonNullable), |
| isVoidAllowed: false); |
| element.condition = conditionResult.expression..parent = element; |
| inferredConditionTypes[element.condition!] = conditionResult.inferredType; |
| } |
| flowAnalysis.for_bodyBegin(null, element.condition); |
| ExpressionInferenceResult bodyResult = inferElement(element.body, |
| inferredTypeArgument, inferredSpreadTypes, inferredConditionTypes); |
| element.body = bodyResult.expression..parent = element; |
| flowAnalysis.for_updaterBegin(); |
| for (int index = 0; index < element.updates.length; index++) { |
| ExpressionInferenceResult updateResult = inferExpression( |
| element.updates[index], const UnknownType(), |
| isVoidAllowed: true); |
| element.updates[index] = updateResult.expression..parent = element; |
| } |
| flowAnalysis.for_end(); |
| return new ExpressionInferenceResult(bodyResult.inferredType, element); |
| } |
| |
| ExpressionInferenceResult _inferForInElement( |
| ForInElement element, |
| DartType inferredTypeArgument, |
| Map<TreeNode, DartType> inferredSpreadTypes, |
| Map<Expression, DartType> inferredConditionTypes) { |
| ForInResult result; |
| if (element.variable.name == null) { |
| result = handleForInWithoutVariable( |
| element, |
| element.variable, |
| element.iterable, |
| element.syntheticAssignment, |
| element.expressionEffects, |
| isAsync: element.isAsync, |
| hasProblem: element.problem != null); |
| } else { |
| result = handleForInDeclaringVariable(element, element.variable, |
| element.iterable, element.expressionEffects, |
| isAsync: element.isAsync); |
| } |
| element.variable = result.variable..parent = element; |
| element.iterable = result.iterable..parent = element; |
| // TODO(johnniwinther): Use ?.. here instead. |
| element.syntheticAssignment = result.syntheticAssignment; |
| result.syntheticAssignment?.parent = element; |
| // TODO(johnniwinther): Use ?.. here instead. |
| element.expressionEffects = result.expressionSideEffects; |
| result.expressionSideEffects?.parent = element; |
| |
| if (element.problem != null) { |
| // Coverage-ignore-block(suite): Not run. |
| ExpressionInferenceResult problemResult = inferExpression( |
| element.problem!, const UnknownType(), |
| isVoidAllowed: true); |
| element.problem = problemResult.expression..parent = element; |
| } |
| ExpressionInferenceResult bodyResult = inferElement(element.body, |
| inferredTypeArgument, inferredSpreadTypes, inferredConditionTypes); |
| element.body = bodyResult.expression..parent = element; |
| // This is matched by the call to [forEach_bodyBegin] in |
| // [handleForInWithoutVariable] or [handleForInDeclaringVariable]. |
| flowAnalysis.forEach_end(); |
| return new ExpressionInferenceResult(bodyResult.inferredType, element); |
| } |
| |
| ExpressionInferenceResult inferElement( |
| Expression element, |
| DartType inferredTypeArgument, |
| Map<TreeNode, DartType> inferredSpreadTypes, |
| Map<Expression, DartType> inferredConditionTypes) { |
| if (element is ControlFlowElement) { |
| switch (element) { |
| case SpreadElement(): |
| return _inferSpreadElement(element, inferredTypeArgument, |
| inferredSpreadTypes, inferredConditionTypes); |
| case NullAwareElement(): |
| return _inferNullAwareElement(element, inferredTypeArgument, |
| inferredSpreadTypes, inferredConditionTypes); |
| case IfElement(): |
| return _inferIfElement(element, inferredTypeArgument, |
| inferredSpreadTypes, inferredConditionTypes); |
| case IfCaseElement(): |
| return _inferIfCaseElement(element, inferredTypeArgument, |
| inferredSpreadTypes, inferredConditionTypes); |
| case ForElement(): |
| return _inferForElement(element, inferredTypeArgument, |
| inferredSpreadTypes, inferredConditionTypes); |
| case PatternForElement(): |
| return _inferPatternForElement(element, inferredTypeArgument, |
| inferredSpreadTypes, inferredConditionTypes); |
| case ForInElement(): |
| return _inferForInElement(element, inferredTypeArgument, |
| inferredSpreadTypes, inferredConditionTypes); |
| } |
| } else { |
| ExpressionInferenceResult result = |
| inferExpression(element, inferredTypeArgument, isVoidAllowed: true); |
| if (inferredTypeArgument is! UnknownType) { |
| result = ensureAssignableResult(inferredTypeArgument, result, |
| isVoidAllowed: inferredTypeArgument is VoidType); |
| } |
| return result; |
| } |
| } |
| |
| void _copyNonPromotionReasonToReplacement( |
| TreeNode oldNode, TreeNode replacement) { |
| if (!identical(oldNode, replacement) && |
| dataForTesting |
| // Coverage-ignore(suite): Not run. |
| ?.flowAnalysisResult != |
| null) { |
| // Coverage-ignore-block(suite): Not run. |
| dataForTesting!.flowAnalysisResult.nonPromotionReasons[replacement] = |
| dataForTesting!.flowAnalysisResult.nonPromotionReasons[oldNode]!; |
| } |
| } |
| |
| void checkElement( |
| ControlFlowElement item, |
| Expression parent, |
| DartType typeArgument, |
| Map<TreeNode, DartType> inferredSpreadTypes, |
| Map<Expression, DartType> inferredConditionTypes) { |
| switch (item) { |
| case SpreadElement(): |
| DartType? spreadType = inferredSpreadTypes[item.expression]; |
| if (spreadType is DynamicType) { |
| Expression expression = ensureAssignable( |
| coreTypes.iterableRawType(item.isNullAware |
| ? Nullability.nullable |
| : Nullability.nonNullable), |
| spreadType, |
| item.expression); |
| item.expression = expression..parent = item; |
| } |
| case NullAwareElement(:Expression expression): |
| if (expression is ControlFlowElement) { |
| // Coverage-ignore-block(suite): Not run. |
| checkElement(expression, item, typeArgument, inferredSpreadTypes, |
| inferredConditionTypes); |
| } |
| case IfElement(:Expression then, :Expression? otherwise): |
| if (then is ControlFlowElement) { |
| checkElement(then, item, typeArgument, inferredSpreadTypes, |
| inferredConditionTypes); |
| } |
| if (otherwise is ControlFlowElement) { |
| checkElement(otherwise, item, typeArgument, inferredSpreadTypes, |
| inferredConditionTypes); |
| } |
| case IfCaseElement(:Expression then, :Expression? otherwise): |
| if (then is ControlFlowElement) { |
| checkElement(then, item, typeArgument, inferredSpreadTypes, |
| inferredConditionTypes); |
| } |
| if (otherwise is ControlFlowElement) { |
| checkElement(otherwise, item, typeArgument, inferredSpreadTypes, |
| inferredConditionTypes); |
| } |
| case ForElement(:Expression? condition, :Expression body): |
| if (condition != null) { |
| DartType conditionType = inferredConditionTypes[condition]!; |
| Expression assignableCondition = ensureAssignable( |
| coreTypes.boolRawType(Nullability.nonNullable), |
| conditionType, |
| condition); |
| item.condition = assignableCondition..parent = item; |
| } |
| if (body is ControlFlowElement) { |
| checkElement(body, item, typeArgument, inferredSpreadTypes, |
| inferredConditionTypes); |
| } |
| case PatternForElement(:Expression? condition, :Expression body): |
| if (condition != null) { |
| DartType conditionType = inferredConditionTypes[condition]!; |
| Expression assignableCondition = ensureAssignable( |
| coreTypes.boolRawType(Nullability.nonNullable), |
| conditionType, |
| condition); |
| item.condition = assignableCondition..parent = item; |
| } |
| if (body is ControlFlowElement) { |
| checkElement(body, item, typeArgument, inferredSpreadTypes, |
| inferredConditionTypes); |
| } |
| case ForInElement(:Expression body): |
| if (body is ControlFlowElement) { |
| checkElement(body, item, typeArgument, inferredSpreadTypes, |
| inferredConditionTypes); |
| } |
| } |
| } |
| |
| @override |
| ExpressionInferenceResult visitListLiteral( |
| ListLiteral node, DartType typeContext) { |
| Class listClass = coreTypes.listClass; |
| InterfaceType listType = |
| coreTypes.thisInterfaceType(listClass, Nullability.nonNullable); |
| List<DartType>? inferredTypes; |
| DartType inferredTypeArgument; |
| bool inferenceNeeded = node.typeArgument is ImplicitTypeArgument; |
| List<DartType> formalTypes = []; |
| List<DartType> actualTypes = []; |
| Map<TreeNode, DartType> inferredSpreadTypes = |
| new Map<TreeNode, DartType>.identity(); |
| Map<Expression, DartType> inferredConditionTypes = |
| new Map<Expression, DartType>.identity(); |
| TypeConstraintGatherer? gatherer; |
| FreshStructuralParametersFromTypeParameters freshTypeParameters = |
| getFreshStructuralParametersFromTypeParameters( |
| listClass.typeParameters); |
| List<StructuralParameter> typeParametersToInfer = |
| freshTypeParameters.freshTypeParameters; |
| listType = freshTypeParameters.substitute(listType) as InterfaceType; |
| if (inferenceNeeded) { |
| gatherer = typeSchemaEnvironment.setupGenericTypeInference( |
| listType, typeParametersToInfer, typeContext, |
| isConst: node.isConst, |
| typeOperations: operations, |
| inferenceResultForTesting: dataForTesting |
| // Coverage-ignore(suite): Not run. |
| ?.typeInferenceResult, |
| treeNodeForTesting: node); |
| inferredTypes = typeSchemaEnvironment.choosePreliminaryTypes( |
| gatherer, typeParametersToInfer, null); |
| inferredTypeArgument = inferredTypes[0]; |
| } else { |
| inferredTypeArgument = node.typeArgument; |
| } |
| for (int index = 0; index < node.expressions.length; ++index) { |
| ExpressionInferenceResult result = inferElement(node.expressions[index], |
| inferredTypeArgument, inferredSpreadTypes, inferredConditionTypes); |
| node.expressions[index] = result.expression..parent = node; |
| actualTypes.add(result.inferredType); |
| if (inferenceNeeded) { |
| formalTypes.add(listType.typeArguments[0]); |
| } |
| } |
| if (inferenceNeeded) { |
| gatherer!.constrainArguments(formalTypes, actualTypes, |
| treeNodeForTesting: node); |
| inferredTypes = typeSchemaEnvironment.chooseFinalTypes( |
| gatherer, typeParametersToInfer, inferredTypes!); |
| if (dataForTesting != null) { |
| // Coverage-ignore-block(suite): Not run. |
| dataForTesting!.typeInferenceResult.inferredTypeArguments[node] = |
| inferredTypes; |
| } |
| inferredTypeArgument = inferredTypes[0]; |
| instrumentation?.record( |
| uriForInstrumentation, |
| node.fileOffset, |
| 'typeArgs', |
| new Ins
|