| // 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. |
| |
| part of "kernel_shadow_ast.dart"; |
| |
| class InferenceVisitor |
| implements |
| ExpressionVisitor1<ExpressionInferenceResult, DartType>, |
| StatementVisitor<void>, |
| InitializerVisitor<void> { |
| final ShadowTypeInferrer inferrer; |
| |
| Class mapEntryClass; |
| |
| // Stores the offset of the map entry found by inferMapEntry. |
| int mapEntryOffset = null; |
| |
| // Stores the offset of the map spread found by inferMapEntry. |
| int mapSpreadOffset = null; |
| |
| // Stores the offset of the iterable spread found by inferMapEntry. |
| int iterableSpreadOffset = null; |
| |
| // Stores the type of the iterable spread found by inferMapEntry. |
| DartType iterableSpreadType = null; |
| |
| InferenceVisitor(this.inferrer); |
| |
| ExpressionInferenceResult _unhandledExpression( |
| Expression node, DartType typeContext) { |
| unhandled("${node.runtimeType}", "InferenceVisitor", node.fileOffset, |
| inferrer.helper.uri); |
| return const ExpressionInferenceResult(const InvalidType()); |
| } |
| |
| @override |
| ExpressionInferenceResult defaultExpression( |
| Expression node, DartType typeContext) { |
| if (node is InternalExpression) { |
| switch (node.kind) { |
| case InternalExpressionKind.Cascade: |
| return visitCascade(node, typeContext); |
| case InternalExpressionKind.CompoundExtensionIndexSet: |
| return visitCompoundExtensionIndexSet(node, typeContext); |
| case InternalExpressionKind.CompoundIndexSet: |
| return visitCompoundIndexSet(node, typeContext); |
| case InternalExpressionKind.CompoundPropertySet: |
| return visitCompoundPropertySet(node, typeContext); |
| case InternalExpressionKind.CompoundSuperIndexSet: |
| return visitCompoundSuperIndexSet(node, typeContext); |
| case InternalExpressionKind.DeferredCheck: |
| return visitDeferredCheck(node, typeContext); |
| case InternalExpressionKind.ExtensionIndexSet: |
| return visitExtensionIndexSet(node, typeContext); |
| case InternalExpressionKind.ExtensionTearOff: |
| return visitExtensionTearOff(node, typeContext); |
| case InternalExpressionKind.ExtensionSet: |
| return visitExtensionSet(node, typeContext); |
| case InternalExpressionKind.IfNull: |
| return visitIfNull(node, typeContext); |
| case InternalExpressionKind.IfNullExtensionIndexSet: |
| return visitIfNullExtensionIndexSet(node, typeContext); |
| case InternalExpressionKind.IfNullIndexSet: |
| return visitIfNullIndexSet(node, typeContext); |
| case InternalExpressionKind.IfNullPropertySet: |
| return visitIfNullPropertySet(node, typeContext); |
| case InternalExpressionKind.IfNullSet: |
| return visitIfNullSet(node, typeContext); |
| case InternalExpressionKind.IfNullSuperIndexSet: |
| return visitIfNullSuperIndexSet(node, typeContext); |
| case InternalExpressionKind.IndexSet: |
| return visitIndexSet(node, typeContext); |
| case InternalExpressionKind.LoadLibraryTearOff: |
| return visitLoadLibraryTearOff(node, typeContext); |
| case InternalExpressionKind.LocalPostIncDec: |
| return visitLocalPostIncDec(node, typeContext); |
| case InternalExpressionKind.NullAwareCompoundSet: |
| return visitNullAwareCompoundSet(node, typeContext); |
| case InternalExpressionKind.NullAwareExtension: |
| return visitNullAwareExtension(node, typeContext); |
| case InternalExpressionKind.NullAwareIfNullSet: |
| return visitNullAwareIfNullSet(node, typeContext); |
| case InternalExpressionKind.NullAwareMethodInvocation: |
| return visitNullAwareMethodInvocation(node, typeContext); |
| case InternalExpressionKind.NullAwarePropertyGet: |
| return visitNullAwarePropertyGet(node, typeContext); |
| case InternalExpressionKind.NullAwarePropertySet: |
| return visitNullAwarePropertySet(node, typeContext); |
| case InternalExpressionKind.PropertyPostIncDec: |
| return visitPropertyPostIncDec(node, typeContext); |
| case InternalExpressionKind.StaticPostIncDec: |
| return visitStaticPostIncDec(node, typeContext); |
| case InternalExpressionKind.SuperIndexSet: |
| return visitSuperIndexSet(node, typeContext); |
| case InternalExpressionKind.SuperPostIncDec: |
| return visitSuperPostIncDec(node, typeContext); |
| } |
| } |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| ExpressionInferenceResult defaultBasicLiteral( |
| BasicLiteral node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| ExpressionInferenceResult visitBlockExpression( |
| BlockExpression node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| ExpressionInferenceResult visitConstantExpression( |
| ConstantExpression node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| ExpressionInferenceResult visitDirectMethodInvocation( |
| DirectMethodInvocation node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| ExpressionInferenceResult visitDirectPropertyGet( |
| DirectPropertyGet node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| ExpressionInferenceResult visitDirectPropertySet( |
| DirectPropertySet node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| ExpressionInferenceResult visitFileUriExpression( |
| FileUriExpression node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| ExpressionInferenceResult visitInstanceCreation( |
| InstanceCreation node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| ExpressionInferenceResult visitInstantiation( |
| Instantiation node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| ExpressionInferenceResult visitListConcatenation( |
| ListConcatenation node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| ExpressionInferenceResult visitMapConcatenation( |
| MapConcatenation node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| @override |
| ExpressionInferenceResult visitSetConcatenation( |
| SetConcatenation node, DartType typeContext) { |
| return _unhandledExpression(node, typeContext); |
| } |
| |
| void _unhandledStatement(Statement node) { |
| unhandled("${node.runtimeType}", "InferenceVisitor", node.fileOffset, |
| inferrer.helper.uri); |
| } |
| |
| @override |
| void defaultStatement(Statement node) { |
| _unhandledStatement(node); |
| } |
| |
| @override |
| void visitAssertBlock(AssertBlock node) { |
| _unhandledStatement(node); |
| } |
| |
| void _unhandledInitializer(Initializer node) { |
| unhandled("${node.runtimeType}", "InferenceVisitor", node.fileOffset, |
| node.location.file); |
| } |
| |
| @override |
| void defaultInitializer(Initializer node) { |
| _unhandledInitializer(node); |
| } |
| |
| @override |
| void visitInvalidInitializer(Initializer node) { |
| _unhandledInitializer(node); |
| } |
| |
| @override |
| void visitLocalInitializer(LocalInitializer node) { |
| _unhandledInitializer(node); |
| } |
| |
| @override |
| ExpressionInferenceResult visitInvalidExpression( |
| InvalidExpression node, DartType typeContext) { |
| // TODO(johnniwinther): The inferred type should be an InvalidType. Using |
| // BottomType leads to cascading errors so we use DynamicType for now. |
| return const ExpressionInferenceResult(const DynamicType()); |
| } |
| |
| @override |
| ExpressionInferenceResult visitIntLiteral( |
| IntLiteral node, DartType typeContext) { |
| return new ExpressionInferenceResult( |
| inferrer.coreTypes.intRawType(inferrer.library.nonNullable)); |
| } |
| |
| @override |
| ExpressionInferenceResult visitAsExpression( |
| AsExpression node, DartType typeContext) { |
| inferrer.inferExpression( |
| node.operand, const UnknownType(), !inferrer.isTopLevel, |
| isVoidAllowed: true); |
| return new ExpressionInferenceResult(node.type); |
| } |
| |
| @override |
| void visitAssertInitializer(AssertInitializer node) { |
| inferrer.inferStatement(node.statement); |
| } |
| |
| @override |
| void visitAssertStatement(AssertStatement node) { |
| InterfaceType expectedType = |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable); |
| DartType conditionType = inferrer |
| .inferExpression(node.condition, expectedType, !inferrer.isTopLevel, |
| isVoidAllowed: true) |
| .inferredType; |
| inferrer.ensureAssignable( |
| expectedType, conditionType, node.condition, node.condition.fileOffset); |
| if (node.message != null) { |
| inferrer.inferExpression( |
| node.message, const UnknownType(), !inferrer.isTopLevel, |
| isVoidAllowed: true); |
| } |
| } |
| |
| @override |
| ExpressionInferenceResult visitAwaitExpression( |
| AwaitExpression node, DartType typeContext) { |
| if (!inferrer.typeSchemaEnvironment.isEmptyContext(typeContext)) { |
| typeContext = inferrer.wrapFutureOrType(typeContext); |
| } |
| DartType operandType = inferrer |
| .inferExpression(node.operand, typeContext, true, isVoidAllowed: true) |
| .inferredType; |
| DartType inferredType = |
| inferrer.typeSchemaEnvironment.unfutureType(operandType); |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| @override |
| void visitBlock(Block node) { |
| for (Statement statement in node.statements) { |
| inferrer.inferStatement(statement); |
| } |
| } |
| |
| @override |
| ExpressionInferenceResult visitBoolLiteral( |
| BoolLiteral node, DartType typeContext) { |
| return new ExpressionInferenceResult( |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable)); |
| } |
| |
| @override |
| void visitBreakStatement(BreakStatement node) { |
| // No inference needs to be done. |
| } |
| |
| ExpressionInferenceResult visitCascade(Cascade node, DartType typeContext) { |
| ExpressionInferenceResult result = inferrer.inferExpression( |
| node.expression, typeContext, true, |
| isVoidAllowed: false); |
| node.variable.type = result.inferredType; |
| for (Expression judgment in node.cascades) { |
| inferrer.inferExpression( |
| judgment, const UnknownType(), !inferrer.isTopLevel, |
| isVoidAllowed: true); |
| } |
| Expression replacement = node.replace(); |
| return new ExpressionInferenceResult(result.inferredType, replacement); |
| } |
| |
| @override |
| ExpressionInferenceResult visitConditionalExpression( |
| ConditionalExpression node, DartType typeContext) { |
| InterfaceType expectedType = |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable); |
| DartType conditionType = inferrer |
| .inferExpression(node.condition, expectedType, !inferrer.isTopLevel, |
| isVoidAllowed: true) |
| .inferredType; |
| inferrer.ensureAssignable( |
| expectedType, conditionType, node.condition, node.condition.fileOffset); |
| DartType thenType = inferrer |
| .inferExpression(node.then, typeContext, true, isVoidAllowed: true) |
| .inferredType; |
| DartType otherwiseType = inferrer |
| .inferExpression(node.otherwise, typeContext, true, isVoidAllowed: true) |
| .inferredType; |
| DartType inferredType = inferrer.typeSchemaEnvironment |
| .getStandardUpperBound(thenType, otherwiseType); |
| node.staticType = inferredType; |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| @override |
| ExpressionInferenceResult visitConstructorInvocation( |
| ConstructorInvocation node, DartType typeContext) { |
| LibraryBuilder library = inferrer.engine.beingInferred[node.target]; |
| if (library != null) { |
| // There is a cyclic dependency where inferring the types of the |
| // initializing formals of a constructor required us to infer the |
| // corresponding field type which required us to know the type of the |
| // constructor. |
| String name = node.target.enclosingClass.name; |
| if (node.target.name.name.isNotEmpty) { |
| // TODO(ahe): Use `inferrer.helper.constructorNameForDiagnostics` |
| // instead. However, `inferrer.helper` may be null. |
| name += ".${node.target.name.name}"; |
| } |
| library.addProblem( |
| templateCantInferTypeDueToCircularity.withArguments(name), |
| node.target.fileOffset, |
| name.length, |
| node.target.fileUri); |
| for (VariableDeclaration declaration |
| in node.target.function.positionalParameters) { |
| declaration.type ??= const InvalidType(); |
| } |
| for (VariableDeclaration declaration |
| in node.target.function.namedParameters) { |
| declaration.type ??= const InvalidType(); |
| } |
| } else if ((library = inferrer.engine.toBeInferred[node.target]) != null) { |
| inferrer.engine.toBeInferred.remove(node.target); |
| inferrer.engine.beingInferred[node.target] = library; |
| for (VariableDeclaration declaration |
| in node.target.function.positionalParameters) { |
| inferrer.engine.inferInitializingFormal(declaration, node.target); |
| } |
| for (VariableDeclaration declaration |
| in node.target.function.namedParameters) { |
| inferrer.engine.inferInitializingFormal(declaration, node.target); |
| } |
| inferrer.engine.beingInferred.remove(node.target); |
| } |
| bool hasExplicitTypeArguments = |
| getExplicitTypeArguments(node.arguments) != null; |
| DartType inferredType = inferrer.inferInvocation(typeContext, |
| node.fileOffset, node.target.function.thisFunctionType, node.arguments, |
| returnType: computeConstructorReturnType(node.target), |
| isConst: node.isConst); |
| if (!inferrer.isTopLevel) { |
| SourceLibraryBuilder library = inferrer.library; |
| if (!hasExplicitTypeArguments) { |
| library.checkBoundsInConstructorInvocation( |
| node, inferrer.typeSchemaEnvironment, inferrer.helper.uri, |
| inferred: true); |
| } |
| } |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| @override |
| void visitContinueSwitchStatement(ContinueSwitchStatement node) { |
| // No inference needs to be done. |
| } |
| |
| ExpressionInferenceResult visitExtensionTearOff( |
| ExtensionTearOff node, DartType typeContext) { |
| FunctionType calleeType = node.target != null |
| ? node.target.function.functionType |
| : new FunctionType([], const DynamicType()); |
| bool hadExplicitTypeArguments = |
| getExplicitTypeArguments(node.arguments) != null; |
| DartType inferredType = inferrer.inferInvocation( |
| typeContext, node.fileOffset, calleeType, node.arguments); |
| Expression replacement = new StaticInvocation(node.target, node.arguments); |
| if (!inferrer.isTopLevel && |
| !hadExplicitTypeArguments && |
| node.target != null) { |
| inferrer.library.checkBoundsInStaticInvocation( |
| replacement, inferrer.typeSchemaEnvironment, inferrer.helper.uri, |
| inferred: true); |
| } |
| node.replaceWith(replacement); |
| inferredType = |
| inferrer.instantiateTearOff(inferredType, typeContext, replacement); |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| ExpressionInferenceResult visitExtensionSet( |
| ExtensionSet node, DartType typeContext) { |
| ExpressionInferenceResult receiverResult = inferrer.inferExpression( |
| node.receiver, const UnknownType(), true, |
| isVoidAllowed: false); |
| |
| List<DartType> extensionTypeArguments = |
| inferrer.computeExtensionTypeArgument(node.extension, |
| node.explicitTypeArguments, receiverResult.inferredType); |
| |
| DartType receiverType = inferrer.getExtensionReceiverType( |
| node.extension, extensionTypeArguments); |
| |
| inferrer.ensureAssignable(receiverType, receiverResult.inferredType, |
| node.receiver, node.receiver.fileOffset); |
| |
| ObjectAccessTarget target = new ExtensionAccessTarget( |
| node.target, null, ProcedureKind.Setter, extensionTypeArguments); |
| |
| DartType valueType = |
| inferrer.getSetterType(target, receiverResult.inferredType); |
| |
| ExpressionInferenceResult valueResult = inferrer.inferExpression( |
| node.value, const UnknownType(), true, |
| isVoidAllowed: false); |
| inferrer.ensureAssignable( |
| valueType, valueResult.inferredType, node.value, node.value.fileOffset); |
| |
| Expression value; |
| VariableDeclaration valueVariable; |
| if (node.forEffect) { |
| value = node.value; |
| } else { |
| valueVariable = createVariable(node.value, valueResult.inferredType); |
| value = createVariableGet(valueVariable); |
| } |
| |
| VariableDeclaration receiverVariable; |
| Expression receiver; |
| if (node.forEffect || node.readOnlyReceiver) { |
| receiver = node.receiver; |
| } else { |
| receiverVariable = |
| createVariable(node.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; |
| node.replaceWith(replacement); |
| return new ExpressionInferenceResult(valueResult.inferredType, 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 = inferrer.inferExpression( |
| node.expression, typeContext, true, |
| isVoidAllowed: true); |
| Expression replacement = node.replace(); |
| return new ExpressionInferenceResult(result.inferredType, replacement); |
| } |
| |
| @override |
| void visitDoStatement(DoStatement node) { |
| inferrer.inferStatement(node.body); |
| InterfaceType boolType = |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable); |
| DartType conditionType = inferrer |
| .inferExpression(node.condition, boolType, !inferrer.isTopLevel, |
| isVoidAllowed: true) |
| .inferredType; |
| inferrer.ensureAssignable( |
| boolType, conditionType, node.condition, node.condition.fileOffset); |
| } |
| |
| ExpressionInferenceResult visitDoubleLiteral( |
| DoubleLiteral node, DartType typeContext) { |
| return new ExpressionInferenceResult( |
| inferrer.coreTypes.doubleRawType(inferrer.library.nonNullable)); |
| } |
| |
| @override |
| void visitEmptyStatement(EmptyStatement node) { |
| // No inference needs to be done. |
| } |
| |
| @override |
| void visitExpressionStatement(ExpressionStatement node) { |
| inferrer.inferExpression( |
| node.expression, const UnknownType(), !inferrer.isTopLevel, |
| isVoidAllowed: true); |
| } |
| |
| ExpressionInferenceResult visitFactoryConstructorInvocationJudgment( |
| FactoryConstructorInvocationJudgment node, DartType typeContext) { |
| bool hadExplicitTypeArguments = |
| getExplicitTypeArguments(node.arguments) != null; |
| DartType inferredType = inferrer.inferInvocation(typeContext, |
| node.fileOffset, node.target.function.thisFunctionType, node.arguments, |
| returnType: computeConstructorReturnType(node.target), |
| isConst: node.isConst); |
| node.hasBeenInferred = true; |
| if (!inferrer.isTopLevel) { |
| SourceLibraryBuilder library = inferrer.library; |
| if (!hadExplicitTypeArguments) { |
| library.checkBoundsInFactoryInvocation( |
| node, inferrer.typeSchemaEnvironment, inferrer.helper.uri, |
| inferred: true); |
| } |
| } |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| @override |
| void visitFieldInitializer(FieldInitializer node) { |
| ExpressionInferenceResult initializerResult = |
| inferrer.inferExpression(node.value, node.field.type, true); |
| DartType initializerType = initializerResult.inferredType; |
| inferrer.ensureAssignable( |
| node.field.type, initializerType, node.value, node.fileOffset); |
| } |
| |
| void handleForInDeclaringVariable( |
| VariableDeclaration variable, Expression iterable, Statement body, |
| {bool isAsync: false}) { |
| DartType elementType; |
| bool typeNeeded = false; |
| bool typeChecksNeeded = !inferrer.isTopLevel; |
| if (VariableDeclarationImpl.isImplicitlyTyped(variable)) { |
| typeNeeded = true; |
| elementType = const UnknownType(); |
| } else { |
| elementType = variable.type; |
| } |
| |
| DartType inferredType = inferForInIterable( |
| iterable, elementType, typeNeeded || typeChecksNeeded, |
| isAsync: isAsync); |
| if (typeNeeded) { |
| inferrer.instrumentation?.record(inferrer.uri, variable.fileOffset, |
| 'type', new InstrumentationValueForType(inferredType)); |
| variable.type = inferredType; |
| } |
| |
| if (body != null) inferrer.inferStatement(body); |
| |
| VariableDeclaration tempVar = |
| new VariableDeclaration(null, type: inferredType, isFinal: true); |
| VariableGet variableGet = new VariableGet(tempVar) |
| ..fileOffset = variable.fileOffset; |
| TreeNode parent = variable.parent; |
| Expression implicitDowncast = inferrer.ensureAssignable( |
| variable.type, inferredType, variableGet, parent.fileOffset, |
| template: templateForInLoopElementTypeNotAssignable); |
| if (implicitDowncast != null) { |
| parent.replaceChild(variable, tempVar); |
| variable.initializer = implicitDowncast..parent = variable; |
| if (body == null) { |
| if (parent is ForInElement) { |
| parent.prologue = variable; |
| } else if (parent is ForInMapEntry) { |
| parent.prologue = variable; |
| } else { |
| unhandled("${parent.runtimeType}", "handleForInDeclaringVariable", |
| variable.fileOffset, variable.location.file); |
| } |
| } else { |
| parent.replaceChild(body, combineStatements(variable, body)); |
| } |
| } |
| } |
| |
| DartType inferForInIterable( |
| Expression iterable, DartType elementType, bool typeNeeded, |
| {bool isAsync: false}) { |
| Class iterableClass = isAsync |
| ? inferrer.coreTypes.streamClass |
| : inferrer.coreTypes.iterableClass; |
| DartType context = inferrer.wrapType(elementType, iterableClass); |
| ExpressionInferenceResult iterableResult = inferrer |
| .inferExpression(iterable, context, typeNeeded, isVoidAllowed: false); |
| DartType iterableType = iterableResult.inferredType; |
| if (iterableResult.replacement != null) { |
| iterable = iterableResult.replacement; |
| } |
| DartType inferredExpressionType = |
| inferrer.resolveTypeParameter(iterableType); |
| inferrer.ensureAssignable( |
| inferrer.wrapType(const DynamicType(), iterableClass), |
| inferredExpressionType, |
| iterable, |
| iterable.fileOffset, |
| template: templateForInLoopTypeNotIterable); |
| DartType inferredType; |
| if (typeNeeded) { |
| inferredType = const DynamicType(); |
| if (inferredExpressionType is InterfaceType) { |
| InterfaceType supertype = inferrer.classHierarchy |
| .getTypeAsInstanceOf(inferredExpressionType, iterableClass); |
| if (supertype != null) { |
| inferredType = supertype.typeArguments[0]; |
| } |
| } |
| } |
| return inferredType; |
| } |
| |
| void handleForInWithoutVariable( |
| VariableDeclaration variable, Expression iterable, Statement body, |
| {bool isAsync: false}) { |
| DartType elementType; |
| bool typeChecksNeeded = !inferrer.isTopLevel; |
| DartType syntheticWriteType; |
| Expression rhs; |
| // If `true`, the synthetic statement should not be visited. |
| bool skipStatement = false; |
| ExpressionStatement syntheticStatement = |
| body is Block ? body.statements.first : body; |
| Expression statementExpression = syntheticStatement.expression; |
| Expression syntheticAssignment = statementExpression; |
| if (syntheticAssignment is VariableSet) { |
| syntheticWriteType = elementType = syntheticAssignment.variable.type; |
| rhs = syntheticAssignment.value; |
| // This expression is fully handled in this method so we should not |
| // visit the synthetic statement. |
| skipStatement = true; |
| } else if (syntheticAssignment is PropertySet || |
| syntheticAssignment is SuperPropertySet) { |
| DartType receiverType = inferrer.thisType; |
| ObjectAccessTarget writeTarget = |
| inferrer.findPropertySetMember(receiverType, syntheticAssignment); |
| syntheticWriteType = |
| elementType = inferrer.getSetterType(writeTarget, receiverType); |
| if (syntheticAssignment is PropertySet) { |
| rhs = syntheticAssignment.value; |
| } else if (syntheticAssignment is SuperPropertySet) { |
| rhs = syntheticAssignment.value; |
| } |
| } else if (syntheticAssignment is StaticSet) { |
| syntheticWriteType = elementType = syntheticAssignment.target.setterType; |
| rhs = syntheticAssignment.value; |
| } else if (syntheticAssignment is InvalidExpression) { |
| elementType = const UnknownType(); |
| } else { |
| unhandled( |
| "${syntheticAssignment.runtimeType}", |
| "handleForInStatementWithoutVariable", |
| syntheticAssignment.fileOffset, |
| inferrer.helper.uri); |
| } |
| |
| DartType inferredType = inferForInIterable( |
| iterable, elementType, typeChecksNeeded, |
| isAsync: isAsync); |
| if (typeChecksNeeded) { |
| variable.type = inferredType; |
| } |
| |
| if (body is Block) { |
| for (Statement statement in body.statements) { |
| if (!skipStatement || statement != syntheticStatement) { |
| inferrer.inferStatement(statement); |
| } |
| } |
| } else { |
| if (!skipStatement) { |
| inferrer.inferStatement(body); |
| } |
| } |
| |
| if (syntheticWriteType != null) { |
| inferrer.ensureAssignable( |
| greatestClosure(inferrer.coreTypes, syntheticWriteType), |
| variable.type, |
| rhs, |
| rhs.fileOffset, |
| template: templateForInLoopElementTypeNotAssignable, |
| isVoidAllowed: true); |
| } |
| } |
| |
| @override |
| void visitForInStatement(ForInStatement node) { |
| if (node.variable.name == null) { |
| handleForInWithoutVariable(node.variable, node.iterable, node.body, |
| isAsync: node.isAsync); |
| } else { |
| handleForInDeclaringVariable(node.variable, node.iterable, node.body, |
| isAsync: node.isAsync); |
| } |
| } |
| |
| @override |
| void visitForStatement(ForStatement node) { |
| for (VariableDeclaration variable in node.variables) { |
| if (variable.name == null) { |
| if (variable.initializer != null) { |
| ExpressionInferenceResult result = inferrer.inferExpression( |
| variable.initializer, const UnknownType(), true, |
| isVoidAllowed: true); |
| variable.type = result.inferredType; |
| } |
| } else { |
| inferrer.inferStatement(variable); |
| } |
| } |
| if (node.condition != null) { |
| InterfaceType expectedType = |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable); |
| DartType conditionType = inferrer |
| .inferExpression(node.condition, expectedType, !inferrer.isTopLevel, |
| isVoidAllowed: true) |
| .inferredType; |
| inferrer.ensureAssignable(expectedType, conditionType, node.condition, |
| node.condition.fileOffset); |
| } |
| for (Expression update in node.updates) { |
| inferrer.inferExpression( |
| update, const UnknownType(), !inferrer.isTopLevel, |
| isVoidAllowed: true); |
| } |
| inferrer.inferStatement(node.body); |
| } |
| |
| DartType visitFunctionNode(FunctionNode node, DartType typeContext, |
| DartType returnContext, int returnTypeInstrumentationOffset) { |
| return inferrer.inferLocalFunction( |
| node, typeContext, returnTypeInstrumentationOffset, returnContext); |
| } |
| |
| @override |
| void visitFunctionDeclaration(covariant FunctionDeclarationImpl node) { |
| inferrer.inferMetadataKeepingHelper(node.variable.annotations); |
| DartType returnContext = |
| node._hasImplicitReturnType ? null : node.function.returnType; |
| DartType inferredType = |
| visitFunctionNode(node.function, null, returnContext, node.fileOffset); |
| node.variable.type = inferredType; |
| } |
| |
| @override |
| ExpressionInferenceResult visitFunctionExpression( |
| FunctionExpression node, DartType typeContext) { |
| DartType inferredType = |
| visitFunctionNode(node.function, typeContext, null, node.fileOffset); |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| void visitInvalidSuperInitializerJudgment( |
| InvalidSuperInitializerJudgment node) { |
| Substitution substitution = Substitution.fromSupertype( |
| inferrer.classHierarchy.getClassAsInstanceOf( |
| inferrer.thisType.classNode, node.target.enclosingClass)); |
| inferrer.inferInvocation( |
| null, |
| node.fileOffset, |
| substitution.substituteType( |
| node.target.function.thisFunctionType.withoutTypeParameters), |
| node.argumentsJudgment, |
| returnType: inferrer.thisType, |
| skipTypeArgumentInference: true); |
| } |
| |
| ExpressionInferenceResult visitIfNull( |
| IfNullExpression node, DartType typeContext) { |
| // To infer `e0 ?? e1` in context K: |
| // - Infer e0 in context K to get T0 |
| DartType lhsType = inferrer |
| .inferExpression(node.left, typeContext, true, isVoidAllowed: false) |
| .inferredType; |
| |
| Member equalsMember = inferrer |
| .findInterfaceMember(lhsType, equalsName, node.fileOffset) |
| .member; |
| |
| // - Let J = T0 if K is `?` else K. |
| // - Infer e1 in context J to get T1 |
| DartType rhsType; |
| if (typeContext is UnknownType) { |
| rhsType = inferrer |
| .inferExpression(node.right, lhsType, true, isVoidAllowed: true) |
| .inferredType; |
| } else { |
| rhsType = inferrer |
| .inferExpression(node.right, typeContext, true, isVoidAllowed: true) |
| .inferredType; |
| } |
| // - Let T = greatest closure of K with respect to `?` if K is not `_`, else |
| // UP(t0, t1) |
| // - Then the inferred type is T. |
| DartType inferredType = |
| inferrer.typeSchemaEnvironment.getStandardUpperBound(lhsType, rhsType); |
| VariableDeclaration variable = createVariable(node.left, lhsType); |
| MethodInvocation equalsNull = createEqualsNull( |
| node.left.fileOffset, createVariableGet(variable), equalsMember); |
| ConditionalExpression conditional = new ConditionalExpression( |
| equalsNull, node.right, createVariableGet(variable), inferredType); |
| Expression replacement = new Let(variable, conditional) |
| ..fileOffset = node.fileOffset; |
| node.replaceWith(replacement); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| @override |
| void visitIfStatement(IfStatement node) { |
| InterfaceType expectedType = |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable); |
| DartType conditionType = inferrer |
| .inferExpression(node.condition, expectedType, !inferrer.isTopLevel, |
| isVoidAllowed: true) |
| .inferredType; |
| inferrer.ensureAssignable( |
| expectedType, conditionType, node.condition, node.condition.fileOffset); |
| inferrer.inferStatement(node.then); |
| if (node.otherwise != null) { |
| inferrer.inferStatement(node.otherwise); |
| } |
| } |
| |
| ExpressionInferenceResult visitIntJudgment( |
| IntJudgment node, DartType typeContext) { |
| if (inferrer.isDoubleContext(typeContext)) { |
| double doubleValue = node.asDouble(); |
| if (doubleValue != null) { |
| node.parent.replaceChild( |
| node, new DoubleLiteral(doubleValue)..fileOffset = node.fileOffset); |
| DartType inferredType = |
| inferrer.coreTypes.doubleRawType(inferrer.library.nonNullable); |
| return new ExpressionInferenceResult(inferredType); |
| } |
| } |
| Expression error = checkWebIntLiteralsErrorIfUnexact( |
| inferrer, node.value, node.literal, node.fileOffset); |
| |
| if (error != null) { |
| node.parent.replaceChild(node, error); |
| return const ExpressionInferenceResult(const BottomType()); |
| } |
| DartType inferredType = |
| inferrer.coreTypes.intRawType(inferrer.library.nonNullable); |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| ExpressionInferenceResult visitShadowLargeIntLiteral( |
| ShadowLargeIntLiteral node, DartType typeContext) { |
| if (inferrer.isDoubleContext(typeContext)) { |
| double doubleValue = node.asDouble(); |
| if (doubleValue != null) { |
| node.parent.replaceChild( |
| node, new DoubleLiteral(doubleValue)..fileOffset = node.fileOffset); |
| DartType inferredType = |
| inferrer.coreTypes.doubleRawType(inferrer.library.nonNullable); |
| return new ExpressionInferenceResult(inferredType); |
| } |
| } |
| |
| int intValue = node.asInt64(); |
| if (intValue == null) { |
| Expression replacement = inferrer.helper.buildProblem( |
| templateIntegerLiteralIsOutOfRange.withArguments(node.literal), |
| node.fileOffset, |
| node.literal.length); |
| node.parent.replaceChild(node, replacement); |
| return const ExpressionInferenceResult(const BottomType()); |
| } |
| Expression error = checkWebIntLiteralsErrorIfUnexact( |
| inferrer, intValue, node.literal, node.fileOffset); |
| if (error != null) { |
| node.parent.replaceChild(node, error); |
| return const ExpressionInferenceResult(const BottomType()); |
| } |
| node.parent.replaceChild( |
| node, new IntLiteral(intValue)..fileOffset = node.fileOffset); |
| DartType inferredType = |
| inferrer.coreTypes.intRawType(inferrer.library.nonNullable); |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| void visitShadowInvalidInitializer(ShadowInvalidInitializer node) { |
| inferrer.inferExpression( |
| node.variable.initializer, const UnknownType(), !inferrer.isTopLevel, |
| isVoidAllowed: false); |
| } |
| |
| void visitShadowInvalidFieldInitializer(ShadowInvalidFieldInitializer node) { |
| inferrer.inferExpression(node.value, node.field.type, !inferrer.isTopLevel, |
| isVoidAllowed: false); |
| } |
| |
| @override |
| ExpressionInferenceResult visitIsExpression( |
| IsExpression node, DartType typeContext) { |
| inferrer.inferExpression( |
| node.operand, const UnknownType(), !inferrer.isTopLevel, |
| isVoidAllowed: false); |
| return new ExpressionInferenceResult( |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable)); |
| } |
| |
| @override |
| void visitLabeledStatement(LabeledStatement node) { |
| inferrer.inferStatement(node.body); |
| } |
| |
| DartType getSpreadElementType(DartType spreadType, bool isNullAware) { |
| if (spreadType is InterfaceType) { |
| InterfaceType supertype = inferrer.typeSchemaEnvironment |
| .getTypeAsInstanceOf(spreadType, inferrer.coreTypes.iterableClass); |
| if (supertype != null) return supertype.typeArguments[0]; |
| if (spreadType.classNode == inferrer.coreTypes.nullClass && isNullAware) { |
| return spreadType; |
| } |
| return null; |
| } |
| if (spreadType is DynamicType) return const DynamicType(); |
| return null; |
| } |
| |
| DartType inferElement( |
| Expression element, |
| TreeNode parent, |
| DartType inferredTypeArgument, |
| Map<TreeNode, DartType> inferredSpreadTypes, |
| Map<Expression, DartType> inferredConditionTypes, |
| bool inferenceNeeded, |
| bool typeChecksNeeded) { |
| if (element is SpreadElement) { |
| ExpressionInferenceResult spreadResult = inferrer.inferExpression( |
| element.expression, |
| new InterfaceType(inferrer.coreTypes.iterableClass, |
| <DartType>[inferredTypeArgument]), |
| inferenceNeeded || typeChecksNeeded, |
| isVoidAllowed: true); |
| DartType spreadType = spreadResult.inferredType; |
| inferredSpreadTypes[element.expression] = spreadType; |
| if (typeChecksNeeded) { |
| DartType spreadElementType = |
| getSpreadElementType(spreadType, element.isNullAware); |
| if (spreadElementType == null) { |
| if (spreadType is InterfaceType && |
| spreadType.classNode == inferrer.coreTypes.nullClass && |
| !element.isNullAware) { |
| parent.replaceChild( |
| element, |
| inferrer.helper.buildProblem(messageNonNullAwareSpreadIsNull, |
| element.expression.fileOffset, 1)); |
| } else { |
| parent.replaceChild( |
| element, |
| inferrer.helper.buildProblem( |
| templateSpreadTypeMismatch.withArguments(spreadType), |
| element.expression.fileOffset, |
| 1)); |
| } |
| } else if (spreadType is InterfaceType) { |
| if (!inferrer.isAssignable(inferredTypeArgument, spreadElementType)) { |
| parent.replaceChild( |
| element, |
| inferrer.helper.buildProblem( |
| templateSpreadElementTypeMismatch.withArguments( |
| spreadElementType, inferredTypeArgument), |
| element.expression.fileOffset, |
| 1)); |
| } |
| } |
| } |
| // Use 'dynamic' for error recovery. |
| return element.elementType = |
| getSpreadElementType(spreadType, element.isNullAware) ?? |
| const DynamicType(); |
| } else if (element is IfElement) { |
| DartType boolType = |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable); |
| ExpressionInferenceResult conditionResult = inferrer.inferExpression( |
| element.condition, boolType, typeChecksNeeded, |
| isVoidAllowed: false); |
| DartType conditionType = conditionResult.inferredType; |
| inferrer.ensureAssignable(boolType, conditionType, element.condition, |
| element.condition.fileOffset); |
| DartType thenType = inferElement( |
| element.then, |
| element, |
| inferredTypeArgument, |
| inferredSpreadTypes, |
| inferredConditionTypes, |
| inferenceNeeded, |
| typeChecksNeeded); |
| DartType otherwiseType; |
| if (element.otherwise != null) { |
| otherwiseType = inferElement( |
| element.otherwise, |
| element, |
| inferredTypeArgument, |
| inferredSpreadTypes, |
| inferredConditionTypes, |
| inferenceNeeded, |
| typeChecksNeeded); |
| } |
| return otherwiseType == null |
| ? thenType |
| : inferrer.typeSchemaEnvironment |
| .getStandardUpperBound(thenType, otherwiseType); |
| } else if (element is ForElement) { |
| for (VariableDeclaration declaration in element.variables) { |
| if (declaration.name == null) { |
| if (declaration.initializer != null) { |
| ExpressionInferenceResult initializerResult = |
| inferrer.inferExpression(declaration.initializer, |
| declaration.type, inferenceNeeded || typeChecksNeeded, |
| isVoidAllowed: true); |
| declaration.type = initializerResult.inferredType; |
| } |
| } else { |
| inferrer.inferStatement(declaration); |
| } |
| } |
| if (element.condition != null) { |
| inferredConditionTypes[element.condition] = inferrer |
| .inferExpression( |
| element.condition, |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable), |
| inferenceNeeded || typeChecksNeeded, |
| isVoidAllowed: false) |
| .inferredType; |
| } |
| for (Expression expression in element.updates) { |
| inferrer.inferExpression(expression, const UnknownType(), |
| inferenceNeeded || typeChecksNeeded, |
| isVoidAllowed: true); |
| } |
| return inferElement( |
| element.body, |
| element, |
| inferredTypeArgument, |
| inferredSpreadTypes, |
| inferredConditionTypes, |
| inferenceNeeded, |
| typeChecksNeeded); |
| } else if (element is ForInElement) { |
| if (element.variable.name == null) { |
| handleForInWithoutVariable( |
| element.variable, element.iterable, element.prologue, |
| isAsync: element.isAsync); |
| } else { |
| handleForInDeclaringVariable( |
| element.variable, element.iterable, element.prologue, |
| isAsync: element.isAsync); |
| } |
| if (element.problem != null) { |
| inferrer.inferExpression(element.problem, const UnknownType(), |
| inferenceNeeded || typeChecksNeeded, |
| isVoidAllowed: true); |
| } |
| return inferElement( |
| element.body, |
| element, |
| inferredTypeArgument, |
| inferredSpreadTypes, |
| inferredConditionTypes, |
| inferenceNeeded, |
| typeChecksNeeded); |
| } else { |
| ExpressionInferenceResult result = inferrer.inferExpression( |
| element, inferredTypeArgument, inferenceNeeded || typeChecksNeeded, |
| isVoidAllowed: true); |
| if (result.replacement != null) { |
| element = result.replacement; |
| } |
| DartType inferredType = result.inferredType; |
| if (inferredTypeArgument is! UnknownType) { |
| inferrer.ensureAssignable( |
| inferredTypeArgument, inferredType, element, element.fileOffset, |
| isVoidAllowed: inferredTypeArgument is VoidType); |
| } |
| return inferredType; |
| } |
| } |
| |
| void checkElement( |
| Expression item, |
| Expression parent, |
| DartType typeArgument, |
| Map<TreeNode, DartType> inferredSpreadTypes, |
| Map<Expression, DartType> inferredConditionTypes) { |
| if (item is SpreadElement) { |
| DartType spreadType = inferredSpreadTypes[item.expression]; |
| if (spreadType is DynamicType) { |
| inferrer.ensureAssignable( |
| inferrer.coreTypes.iterableRawType( |
| inferrer.library.nullableIfTrue(item.isNullAware)), |
| spreadType, |
| item.expression, |
| item.expression.fileOffset); |
| } |
| } else if (item is IfElement) { |
| checkElement(item.then, item, typeArgument, inferredSpreadTypes, |
| inferredConditionTypes); |
| if (item.otherwise != null) { |
| checkElement(item.otherwise, item, typeArgument, inferredSpreadTypes, |
| inferredConditionTypes); |
| } |
| } else if (item is ForElement) { |
| if (item.condition != null) { |
| DartType conditionType = inferredConditionTypes[item.condition]; |
| inferrer.ensureAssignable( |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable), |
| conditionType, |
| item.condition, |
| item.condition.fileOffset); |
| } |
| checkElement(item.body, item, typeArgument, inferredSpreadTypes, |
| inferredConditionTypes); |
| } else if (item is ForInElement) { |
| checkElement(item.body, item, typeArgument, inferredSpreadTypes, |
| inferredConditionTypes); |
| } else { |
| // Do nothing. Assignability checks are done during type inference. |
| } |
| } |
| |
| @override |
| ExpressionInferenceResult visitListLiteral( |
| ListLiteral node, DartType typeContext) { |
| Class listClass = inferrer.coreTypes.listClass; |
| InterfaceType listType = listClass.thisType; |
| List<DartType> inferredTypes; |
| DartType inferredTypeArgument; |
| List<DartType> formalTypes; |
| List<DartType> actualTypes; |
| bool inferenceNeeded = node.typeArgument is ImplicitTypeArgument; |
| bool typeChecksNeeded = !inferrer.isTopLevel; |
| Map<TreeNode, DartType> inferredSpreadTypes; |
| Map<Expression, DartType> inferredConditionTypes; |
| if (inferenceNeeded || typeChecksNeeded) { |
| formalTypes = []; |
| actualTypes = []; |
| inferredSpreadTypes = new Map<TreeNode, DartType>.identity(); |
| inferredConditionTypes = new Map<Expression, DartType>.identity(); |
| } |
| if (inferenceNeeded) { |
| inferredTypes = [const UnknownType()]; |
| inferrer.typeSchemaEnvironment.inferGenericFunctionOrType(listType, |
| listClass.typeParameters, null, null, typeContext, inferredTypes, |
| isConst: node.isConst); |
| inferredTypeArgument = inferredTypes[0]; |
| } else { |
| inferredTypeArgument = node.typeArgument; |
| } |
| if (inferenceNeeded || typeChecksNeeded) { |
| for (int i = 0; i < node.expressions.length; ++i) { |
| DartType type = inferElement( |
| node.expressions[i], |
| node, |
| inferredTypeArgument, |
| inferredSpreadTypes, |
| inferredConditionTypes, |
| inferenceNeeded, |
| typeChecksNeeded); |
| actualTypes.add(type); |
| if (inferenceNeeded) { |
| formalTypes.add(listType.typeArguments[0]); |
| } |
| } |
| } |
| if (inferenceNeeded) { |
| inferrer.typeSchemaEnvironment.inferGenericFunctionOrType( |
| listType, |
| listClass.typeParameters, |
| formalTypes, |
| actualTypes, |
| typeContext, |
| inferredTypes); |
| inferredTypeArgument = inferredTypes[0]; |
| inferrer.instrumentation?.record( |
| inferrer.uri, |
| node.fileOffset, |
| 'typeArgs', |
| new InstrumentationValueForTypeArgs([inferredTypeArgument])); |
| node.typeArgument = inferredTypeArgument; |
| } |
| if (typeChecksNeeded) { |
| for (int i = 0; i < node.expressions.length; i++) { |
| checkElement(node.expressions[i], node, node.typeArgument, |
| inferredSpreadTypes, inferredConditionTypes); |
| } |
| } |
| DartType inferredType = |
| new InterfaceType(listClass, [inferredTypeArgument]); |
| if (!inferrer.isTopLevel) { |
| SourceLibraryBuilder library = inferrer.library; |
| if (inferenceNeeded) { |
| library.checkBoundsInListLiteral( |
| node, inferrer.typeSchemaEnvironment, inferrer.helper.uri, |
| inferred: true); |
| } |
| } |
| |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| @override |
| ExpressionInferenceResult visitLogicalExpression( |
| LogicalExpression node, DartType typeContext) { |
| InterfaceType boolType = |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable); |
| DartType leftType = inferrer |
| .inferExpression(node.left, boolType, !inferrer.isTopLevel, |
| isVoidAllowed: false) |
| .inferredType; |
| DartType rightType = inferrer |
| .inferExpression(node.right, boolType, !inferrer.isTopLevel, |
| isVoidAllowed: false) |
| .inferredType; |
| inferrer.ensureAssignable( |
| boolType, leftType, node.left, node.left.fileOffset); |
| inferrer.ensureAssignable( |
| boolType, rightType, node.right, node.right.fileOffset); |
| return new ExpressionInferenceResult(boolType); |
| } |
| |
| // Calculates the key and the value type of a spread map entry of type |
| // spreadMapEntryType and stores them in output in positions offset and offset |
| // + 1. If the types can't be calculated, for example, if spreadMapEntryType |
| // is a function type, the original values in output are preserved. |
| void storeSpreadMapEntryElementTypes(DartType spreadMapEntryType, |
| bool isNullAware, List<DartType> output, int offset) { |
| if (spreadMapEntryType is InterfaceType) { |
| InterfaceType supertype = inferrer.typeSchemaEnvironment |
| .getTypeAsInstanceOf(spreadMapEntryType, inferrer.coreTypes.mapClass); |
| if (supertype != null) { |
| output[offset] = supertype.typeArguments[0]; |
| output[offset + 1] = supertype.typeArguments[1]; |
| } else if (spreadMapEntryType.classNode == inferrer.coreTypes.nullClass && |
| isNullAware) { |
| output[offset] = output[offset + 1] = spreadMapEntryType; |
| } |
| } |
| if (spreadMapEntryType is DynamicType) { |
| output[offset] = output[offset + 1] = const DynamicType(); |
| } |
| } |
| |
| // Note that inferMapEntry adds exactly two elements to actualTypes -- the |
| // actual types of the key and the value. The same technique is used for |
| // actualTypesForSet, only inferMapEntry adds exactly one element to that |
| // list: the actual type of the iterable spread elements in case the map |
| // literal will be disambiguated as a set literal later. |
| void inferMapEntry( |
| MapEntry entry, |
| TreeNode parent, |
| DartType inferredKeyType, |
| DartType inferredValueType, |
| DartType spreadContext, |
| List<DartType> actualTypes, |
| List<DartType> actualTypesForSet, |
| Map<TreeNode, DartType> inferredSpreadTypes, |
| Map<Expression, DartType> inferredConditionTypes, |
| bool inferenceNeeded, |
| bool typeChecksNeeded) { |
| if (entry is SpreadMapEntry) { |
| ExpressionInferenceResult spreadResult = inferrer.inferExpression( |
| entry.expression, spreadContext, inferenceNeeded || typeChecksNeeded, |
| isVoidAllowed: true); |
| DartType spreadType = spreadResult.inferredType; |
| inferredSpreadTypes[entry.expression] = spreadType; |
| int length = actualTypes.length; |
| actualTypes.add(null); |
| actualTypes.add(null); |
| storeSpreadMapEntryElementTypes( |
| spreadType, entry.isNullAware, actualTypes, length); |
| DartType actualKeyType = actualTypes[length]; |
| DartType actualValueType = actualTypes[length + 1]; |
| DartType actualElementType = |
| getSpreadElementType(spreadType, entry.isNullAware); |
| |
| if (typeChecksNeeded) { |
| if (actualKeyType == null) { |
| if (spreadType is InterfaceType && |
| spreadType.classNode == inferrer.coreTypes.nullClass && |
| !entry.isNullAware) { |
| parent.replaceChild( |
| entry, |
| new MapEntry( |
| inferrer.helper.buildProblem( |
| messageNonNullAwareSpreadIsNull, |
| entry.expression.fileOffset, |
| 1), |
| new NullLiteral()) |
| ..fileOffset = entry.fileOffset); |
| } else if (actualElementType != null) { |
| // Don't report the error here, it might be an ambiguous Set. The |
| // error is reported in checkMapEntry if it's disambiguated as map. |
| iterableSpreadType = spreadType; |
| } else { |
| parent.replaceChild( |
| entry, |
| new MapEntry( |
| inferrer.helper.buildProblem( |
| templateSpreadMapEntryTypeMismatch |
| .withArguments(spreadType), |
| entry.expression.fileOffset, |
| 1), |
| new NullLiteral()) |
| ..fileOffset = entry.fileOffset); |
| } |
| } else if (spreadType is InterfaceType) { |
| Expression keyError; |
| Expression valueError; |
| if (!inferrer.isAssignable(inferredKeyType, actualKeyType)) { |
| keyError = inferrer.helper.buildProblem( |
| templateSpreadMapEntryElementKeyTypeMismatch.withArguments( |
| actualKeyType, inferredKeyType), |
| entry.expression.fileOffset, |
| 1); |
| } |
| if (!inferrer.isAssignable(inferredValueType, actualValueType)) { |
| valueError = inferrer.helper.buildProblem( |
| templateSpreadMapEntryElementValueTypeMismatch.withArguments( |
| actualValueType, inferredValueType), |
| entry.expression.fileOffset, |
| 1); |
| } |
| if (keyError != null || valueError != null) { |
| keyError ??= new NullLiteral(); |
| valueError ??= new NullLiteral(); |
| parent.replaceChild( |
| entry, |
| new MapEntry(keyError, valueError) |
| ..fileOffset = entry.fileOffset); |
| } |
| } |
| } |
| |
| // Use 'dynamic' for error recovery. |
| if (actualKeyType == null) { |
| actualKeyType = actualTypes[length] = const DynamicType(); |
| actualValueType = actualTypes[length + 1] = const DynamicType(); |
| } |
| // Store the type in case of an ambiguous Set. Use 'dynamic' for error |
| // recovery. |
| actualTypesForSet.add(actualElementType ?? const DynamicType()); |
| |
| mapEntryClass ??= |
| inferrer.coreTypes.index.getClass('dart:core', 'MapEntry'); |
| // TODO(dmitryas): Handle the case of an ambiguous Set. |
| entry.entryType = new InterfaceType( |
| mapEntryClass, <DartType>[actualKeyType, actualValueType]); |
| |
| bool isMap = inferrer.typeSchemaEnvironment.isSubtypeOf( |
| spreadType, inferrer.coreTypes.mapRawType(inferrer.library.nullable)); |
| bool isIterable = inferrer.typeSchemaEnvironment.isSubtypeOf(spreadType, |
| inferrer.coreTypes.iterableRawType(inferrer.library.nullable)); |
| if (isMap && !isIterable) { |
| mapSpreadOffset = entry.fileOffset; |
| } |
| if (!isMap && isIterable) { |
| iterableSpreadOffset = entry.expression.fileOffset; |
| } |
| |
| return; |
| } else if (entry is IfMapEntry) { |
| DartType boolType = |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable); |
| ExpressionInferenceResult conditionResult = inferrer.inferExpression( |
| entry.condition, boolType, typeChecksNeeded, |
| isVoidAllowed: false); |
| DartType conditionType = conditionResult.inferredType; |
| inferrer.ensureAssignable( |
| boolType, conditionType, entry.condition, entry.condition.fileOffset); |
| // Note that this recursive invocation of inferMapEntry will add two types |
| // to actualTypes; they are the actual types of the current invocation if |
| // the 'else' branch is empty. |
| inferMapEntry( |
| entry.then, |
| entry, |
| inferredKeyType, |
| inferredValueType, |
| spreadContext, |
| actualTypes, |
| actualTypesForSet, |
| inferredSpreadTypes, |
| inferredConditionTypes, |
| inferenceNeeded, |
| typeChecksNeeded); |
| if (entry.otherwise != null) { |
| // We need to modify the actual types added in the recursive call to |
| // inferMapEntry. |
| DartType actualValueType = actualTypes.removeLast(); |
| DartType actualKeyType = actualTypes.removeLast(); |
| DartType actualTypeForSet = actualTypesForSet.removeLast(); |
| inferMapEntry( |
| entry.otherwise, |
| entry, |
| inferredKeyType, |
| inferredValueType, |
| spreadContext, |
| actualTypes, |
| actualTypesForSet, |
| inferredSpreadTypes, |
| inferredConditionTypes, |
| inferenceNeeded, |
| typeChecksNeeded); |
| int length = actualTypes.length; |
| actualTypes[length - 2] = inferrer.typeSchemaEnvironment |
| .getStandardUpperBound(actualKeyType, actualTypes[length - 2]); |
| actualTypes[length - 1] = inferrer.typeSchemaEnvironment |
| .getStandardUpperBound(actualValueType, actualTypes[length - 1]); |
| int lengthForSet = actualTypesForSet.length; |
| actualTypesForSet[lengthForSet - 1] = inferrer.typeSchemaEnvironment |
| .getStandardUpperBound( |
| actualTypeForSet, actualTypesForSet[lengthForSet - 1]); |
| } |
| return; |
| } else if (entry is ForMapEntry) { |
| for (VariableDeclaration declaration in entry.variables) { |
| if (declaration.name == null) { |
| if (declaration.initializer != null) { |
| ExpressionInferenceResult result = inferrer.inferExpression( |
| declaration.initializer, |
| declaration.type, |
| inferenceNeeded || typeChecksNeeded, |
| isVoidAllowed: true); |
| declaration.type = result.inferredType; |
| } |
| } else { |
| inferrer.inferStatement(declaration); |
| } |
| } |
| if (entry.condition != null) { |
| inferredConditionTypes[entry.condition] = inferrer |
| .inferExpression( |
| entry.condition, |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable), |
| inferenceNeeded || typeChecksNeeded, |
| isVoidAllowed: false) |
| .inferredType; |
| } |
| for (Expression expression in entry.updates) { |
| inferrer.inferExpression(expression, const UnknownType(), |
| inferenceNeeded || typeChecksNeeded, |
| isVoidAllowed: true); |
| } |
| // Actual types are added by the recursive call. |
| return inferMapEntry( |
| entry.body, |
| entry, |
| inferredKeyType, |
| inferredValueType, |
| spreadContext, |
| actualTypes, |
| actualTypesForSet, |
| inferredSpreadTypes, |
| inferredConditionTypes, |
| inferenceNeeded, |
| typeChecksNeeded); |
| } else if (entry is ForInMapEntry) { |
| if (entry.variable.name == null) { |
| handleForInWithoutVariable( |
| entry.variable, entry.iterable, entry.prologue, |
| isAsync: entry.isAsync); |
| } else { |
| handleForInDeclaringVariable( |
| entry.variable, entry.iterable, entry.prologue, |
| isAsync: entry.isAsync); |
| } |
| if (entry.problem != null) { |
| inferrer.inferExpression(entry.problem, const UnknownType(), |
| inferenceNeeded || typeChecksNeeded, |
| isVoidAllowed: true); |
| } |
| // Actual types are added by the recursive call. |
| inferMapEntry( |
| entry.body, |
| entry, |
| inferredKeyType, |
| inferredValueType, |
| spreadContext, |
| actualTypes, |
| actualTypesForSet, |
| inferredSpreadTypes, |
| inferredConditionTypes, |
| inferenceNeeded, |
| typeChecksNeeded); |
| } else { |
| ExpressionInferenceResult keyResult = inferrer.inferExpression( |
| entry.key, inferredKeyType, true, |
| isVoidAllowed: true); |
| DartType keyType = keyResult.inferredType; |
| ExpressionInferenceResult valueResult = inferrer.inferExpression( |
| entry.value, inferredValueType, true, |
| isVoidAllowed: true); |
| DartType valueType = valueResult.inferredType; |
| inferrer.ensureAssignable( |
| inferredKeyType, keyType, entry.key, entry.key.fileOffset, |
| isVoidAllowed: inferredKeyType is VoidType); |
| inferrer.ensureAssignable( |
| inferredValueType, valueType, entry.value, entry.value.fileOffset, |
| isVoidAllowed: inferredValueType is VoidType); |
| actualTypes.add(keyType); |
| actualTypes.add(valueType); |
| // Use 'dynamic' for error recovery. |
| actualTypesForSet.add(const DynamicType()); |
| mapEntryOffset = entry.fileOffset; |
| return; |
| } |
| } |
| |
| void checkMapEntry( |
| MapEntry entry, |
| TreeNode parent, |
| Expression cachedKey, |
| Expression cachedValue, |
| DartType keyType, |
| DartType valueType, |
| Map<TreeNode, DartType> inferredSpreadTypes, |
| Map<Expression, DartType> inferredConditionTypes) { |
| // It's disambiguated as a map literal. |
| if (iterableSpreadOffset != null) { |
| parent.replaceChild( |
| entry, |
| new MapEntry( |
| inferrer.helper.buildProblem( |
| templateSpreadMapEntryTypeMismatch |
| .withArguments(iterableSpreadType), |
| iterableSpreadOffset, |
| 1), |
| new NullLiteral())); |
| } |
| if (entry is SpreadMapEntry) { |
| DartType spreadType = inferredSpreadTypes[entry.expression]; |
| if (spreadType is DynamicType) { |
| inferrer.ensureAssignable( |
| inferrer.coreTypes |
| .mapRawType(inferrer.library.nullableIfTrue(entry.isNullAware)), |
| spreadType, |
| entry.expression, |
| entry.expression.fileOffset); |
| } |
| } else if (entry is IfMapEntry) { |
| checkMapEntry(entry.then, entry, cachedKey, cachedValue, keyType, |
| valueType, inferredSpreadTypes, inferredConditionTypes); |
| if (entry.otherwise != null) { |
| checkMapEntry(entry.otherwise, entry, cachedKey, cachedValue, keyType, |
| valueType, inferredSpreadTypes, inferredConditionTypes); |
| } |
| } else if (entry is ForMapEntry) { |
| if (entry.condition != null) { |
| DartType conditionType = inferredConditionTypes[entry.condition]; |
| inferrer.ensureAssignable( |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable), |
| conditionType, |
| entry.condition, |
| entry.condition.fileOffset); |
| } |
| checkMapEntry(entry.body, entry, cachedKey, cachedValue, keyType, |
| valueType, inferredSpreadTypes, inferredConditionTypes); |
| } else if (entry is ForInMapEntry) { |
| checkMapEntry(entry.body, entry, cachedKey, cachedValue, keyType, |
| valueType, inferredSpreadTypes, inferredConditionTypes); |
| } else { |
| // Do nothing. Assignability checks are done during type inference. |
| } |
| } |
| |
| @override |
| ExpressionInferenceResult visitMapLiteral( |
| MapLiteral node, DartType typeContext) { |
| Class mapClass = inferrer.coreTypes.mapClass; |
| InterfaceType mapType = mapClass.thisType; |
| List<DartType> inferredTypes; |
| DartType inferredKeyType; |
| DartType inferredValueType; |
| List<DartType> formalTypes; |
| List<DartType> actualTypes; |
| List<DartType> actualTypesForSet; |
| assert((node.keyType is ImplicitTypeArgument) == |
| (node.valueType is ImplicitTypeArgument)); |
| bool inferenceNeeded = node.keyType is ImplicitTypeArgument; |
| bool typeContextIsMap = node.keyType is! ImplicitTypeArgument; |
| bool typeContextIsIterable = false; |
| if (!inferrer.isTopLevel && inferenceNeeded) { |
| // Ambiguous set/map literal |
| DartType context = |
| inferrer.typeSchemaEnvironment.unfutureType(typeContext); |
| if (context is InterfaceType) { |
| typeContextIsMap = typeContextIsMap || |
| inferrer.classHierarchy |
| .isSubtypeOf(context.classNode, inferrer.coreTypes.mapClass); |
| typeContextIsIterable = typeContextIsIterable || |
| inferrer.classHierarchy.isSubtypeOf( |
| context.classNode, inferrer.coreTypes.iterableClass); |
| if (node.entries.isEmpty && |
| typeContextIsIterable && |
| !typeContextIsMap) { |
| // Set literal |
| SetLiteral setLiteral = new SetLiteral([], |
| typeArgument: const ImplicitTypeArgument(), isConst: node.isConst) |
| ..fileOffset = node.fileOffset; |
| node.parent.replaceChild(node, setLiteral); |
| ExpressionInferenceResult setLiteralResult = |
| visitSetLiteral(setLiteral, typeContext); |
| DartType inferredType = setLiteralResult.inferredType; |
| return new ExpressionInferenceResult(inferredType); |
| } |
| } |
| } |
| bool typeChecksNeeded = !inferrer.isTopLevel; |
| Map<TreeNode, DartType> inferredSpreadTypes; |
| Map<Expression, DartType> inferredConditionTypes; |
| if (inferenceNeeded || typeChecksNeeded) { |
| formalTypes = []; |
| actualTypes = []; |
| actualTypesForSet = []; |
| inferredSpreadTypes = new Map<TreeNode, DartType>.identity(); |
| inferredConditionTypes = new Map<Expression, DartType>.identity(); |
| } |
| if (inferenceNeeded) { |
| inferredTypes = [const UnknownType(), const UnknownType()]; |
| inferrer.typeSchemaEnvironment.inferGenericFunctionOrType(mapType, |
| mapClass.typeParameters, null, null, typeContext, inferredTypes, |
| isConst: node.isConst); |
| inferredKeyType = inferredTypes[0]; |
| inferredValueType = inferredTypes[1]; |
| } else { |
| inferredKeyType = node.keyType; |
| inferredValueType = node.valueType; |
| } |
| List<Expression> cachedKeys = new List(node.entries.length); |
| List<Expression> cachedValues = new List(node.entries.length); |
| for (int i = 0; i < node.entries.length; i++) { |
| MapEntry entry = node.entries[i]; |
| if (entry is! ControlFlowMapEntry) { |
| cachedKeys[i] = node.entries[i].key; |
| cachedValues[i] = node.entries[i].value; |
| } |
| } |
| bool hasMapEntry = false; |
| bool hasMapSpread = false; |
| bool hasIterableSpread = false; |
| if (inferenceNeeded || typeChecksNeeded) { |
| mapEntryOffset = null; |
| mapSpreadOffset = null; |
| iterableSpreadOffset = null; |
| iterableSpreadType = null; |
| DartType spreadTypeContext = const UnknownType(); |
| if (typeContextIsIterable && !typeContextIsMap) { |
| spreadTypeContext = inferrer.typeSchemaEnvironment |
| .getTypeAsInstanceOf(typeContext, inferrer.coreTypes.iterableClass); |
| } else if (!typeContextIsIterable && typeContextIsMap) { |
| spreadTypeContext = new InterfaceType(inferrer.coreTypes.mapClass, |
| <DartType>[inferredKeyType, inferredValueType]); |
| } |
| for (int i = 0; i < node.entries.length; ++i) { |
| MapEntry entry = node.entries[i]; |
| inferMapEntry( |
| entry, |
| node, |
| inferredKeyType, |
| inferredValueType, |
| spreadTypeContext, |
| actualTypes, |
| actualTypesForSet, |
| inferredSpreadTypes, |
| inferredConditionTypes, |
| inferenceNeeded, |
| typeChecksNeeded); |
| if (inferenceNeeded) { |
| formalTypes.add(mapType.typeArguments[0]); |
| formalTypes.add(mapType.typeArguments[1]); |
| } |
| } |
| hasMapEntry = mapEntryOffset != null; |
| hasMapSpread = mapSpreadOffset != null; |
| hasIterableSpread = iterableSpreadOffset != null; |
| } |
| if (inferenceNeeded) { |
| bool canBeSet = !hasMapSpread && !hasMapEntry && !typeContextIsMap; |
| bool canBeMap = !hasIterableSpread && !typeContextIsIterable; |
| if (canBeSet && !canBeMap) { |
| List<Expression> setElements = <Expression>[]; |
| List<DartType> formalTypesForSet = <DartType>[]; |
| InterfaceType setType = inferrer.coreTypes.setClass.thisType; |
| for (int i = 0; i < node.entries.length; ++i) { |
| setElements.add(convertToElement(node.entries[i], inferrer.helper)); |
| formalTypesForSet.add(setType.typeArguments[0]); |
| } |
| |
| List<DartType> inferredTypesForSet = <DartType>[const UnknownType()]; |
| inferrer.typeSchemaEnvironment.inferGenericFunctionOrType( |
| setType, |
| inferrer.coreTypes.setClass.typeParameters, |
| null, |
| null, |
| typeContext, |
| inferredTypesForSet, |
| isConst: node.isConst); |
| inferrer.typeSchemaEnvironment.inferGenericFunctionOrType( |
| inferrer.coreTypes.setClass.thisType, |
| inferrer.coreTypes.setClass.typeParameters, |
| formalTypesForSet, |
| actualTypesForSet, |
| typeContext, |
| inferredTypesForSet); |
| DartType inferredTypeArgument = inferredTypesForSet[0]; |
| inferrer.instrumentation?.record( |
| inferrer.uri, |
| node.fileOffset, |
| 'typeArgs', |
| new InstrumentationValueForTypeArgs([inferredTypeArgument])); |
| |
| SetLiteral setLiteral = new SetLiteral(setElements, |
| typeArgument: inferredTypeArgument, isConst: node.isConst) |
| ..fileOffset = node.fileOffset; |
| node.parent.replaceChild(node, setLiteral); |
| if (typeChecksNeeded) { |
| for (int i = 0; i < setLiteral.expressions.length; i++) { |
| checkElement( |
| setLiteral.expressions[i], |
| setLiteral, |
| setLiteral.typeArgument, |
| inferredSpreadTypes, |
| inferredConditionTypes); |
| } |
| } |
| |
| DartType inferredType = |
| new InterfaceType(inferrer.coreTypes.setClass, inferredTypesForSet); |
| return new ExpressionInferenceResult(inferredType); |
| } |
| if (canBeSet && canBeMap && node.entries.isNotEmpty) { |
| node.parent.replaceChild( |
| node, |
| inferrer.helper.buildProblem( |
| messageCantDisambiguateNotEnoughInformation, |
| node.fileOffset, |
| 1)); |
| return const ExpressionInferenceResult(const BottomType()); |
| } |
| if (!canBeSet && !canBeMap) { |
| if (!inferrer.isTopLevel) { |
| node.parent.replaceChild( |
| node, |
| inferrer.helper.buildProblem( |
| messageCantDisambiguateAmbiguousInformation, |
| node.fileOffset, |
| 1)); |
| } |
| return const ExpressionInferenceResult(const BottomType()); |
| } |
| inferrer.typeSchemaEnvironment.inferGenericFunctionOrType( |
| mapType, |
| mapClass.typeParameters, |
| formalTypes, |
| actualTypes, |
| typeContext, |
| inferredTypes); |
| inferredKeyType = inferredTypes[0]; |
| inferredValueType = inferredTypes[1]; |
| inferrer.instrumentation?.record( |
| inferrer.uri, |
| node.fileOffset, |
| 'typeArgs', |
| new InstrumentationValueForTypeArgs( |
| [inferredKeyType, inferredValueType])); |
| node.keyType = inferredKeyType; |
| node.valueType = inferredValueType; |
| } |
| if (typeChecksNeeded) { |
| for (int i = 0; i < node.entries.length; ++i) { |
| checkMapEntry( |
| node.entries[i], |
| node, |
| cachedKeys[i], |
| cachedValues[i], |
| node.keyType, |
| node.valueType, |
| inferredSpreadTypes, |
| inferredConditionTypes); |
| } |
| } |
| DartType inferredType = |
| new InterfaceType(mapClass, [inferredKeyType, inferredValueType]); |
| if (!inferrer.isTopLevel) { |
| SourceLibraryBuilder library = inferrer.library; |
| // Either both [_declaredKeyType] and [_declaredValueType] are omitted or |
| // none of them, so we may just check one. |
| if (inferenceNeeded) { |
| library.checkBoundsInMapLiteral( |
| node, inferrer.typeSchemaEnvironment, inferrer.helper.uri, |
| inferred: true); |
| } |
| } |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| @override |
| ExpressionInferenceResult visitMethodInvocation( |
| covariant MethodInvocationImpl node, DartType typeContext) { |
| if (node.name.name == 'unary-' && |
| node.arguments.types.isEmpty && |
| node.arguments.positional.isEmpty && |
| node.arguments.named.isEmpty) { |
| // Replace integer literals in a double context with the corresponding |
| // double literal if it's exact. For double literals, the negation is |
| // folded away. In any non-double context, or if there is no exact |
| // double value, then the corresponding integer literal is left. The |
| // negation is not folded away so that platforms with web literals can |
| // distinguish between (non-negated) 0x8000000000000000 represented as |
| // integer literal -9223372036854775808 which should be a positive number, |
| // and negated 9223372036854775808 represented as |
| // -9223372036854775808.unary-() which should be a negative number. |
| if (node.receiver is IntJudgment) { |
| IntJudgment receiver = node.receiver; |
| if (inferrer.isDoubleContext(typeContext)) { |
| double doubleValue = receiver.asDouble(negated: true); |
| if (doubleValue != null) { |
| node.parent.replaceChild(node, |
| new DoubleLiteral(doubleValue)..fileOffset = node.fileOffset); |
| DartType inferredType = |
| inferrer.coreTypes.doubleRawType(inferrer.library.nonNullable); |
| return new ExpressionInferenceResult(inferredType); |
| } |
| } |
| Expression error = checkWebIntLiteralsErrorIfUnexact( |
| inferrer, receiver.value, receiver.literal, receiver.fileOffset); |
| if (error != null) { |
| node.parent.replaceChild(node, error); |
| return const ExpressionInferenceResult(const BottomType()); |
| } |
| } else if (node.receiver is ShadowLargeIntLiteral) { |
| ShadowLargeIntLiteral receiver = node.receiver; |
| if (!receiver.isParenthesized) { |
| if (inferrer.isDoubleContext(typeContext)) { |
| double doubleValue = receiver.asDouble(negated: true); |
| if (doubleValue != null) { |
| node.parent.replaceChild(node, |
| new DoubleLiteral(doubleValue)..fileOffset = node.fileOffset); |
| DartType inferredType = inferrer.coreTypes |
| .doubleRawType(inferrer.library.nonNullable); |
| return new ExpressionInferenceResult(inferredType); |
| } |
| } |
| int intValue = receiver.asInt64(negated: true); |
| if (intValue == null) { |
| Expression error = inferrer.helper.buildProblem( |
| templateIntegerLiteralIsOutOfRange |
| .withArguments(receiver.literal), |
| receiver.fileOffset, |
| receiver.literal.length); |
| node.parent.replaceChild(node, error); |
| return const ExpressionInferenceResult(const BottomType()); |
| } |
| if (intValue != null) { |
| Expression error = checkWebIntLiteralsErrorIfUnexact( |
| inferrer, intValue, receiver.literal, receiver.fileOffset); |
| if (error != null) { |
| node.parent.replaceChild(node, error); |
| return const ExpressionInferenceResult(const BottomType()); |
| } |
| node.receiver = new IntLiteral(-intValue) |
| ..fileOffset = node.receiver.fileOffset |
| ..parent = node; |
| } |
| } |
| } |
| } |
| ExpressionInferenceResult result = |
| inferrer.inferMethodInvocation(node, typeContext); |
| return new ExpressionInferenceResult( |
| result.inferredType, result.replacement); |
| } |
| |
| ExpressionInferenceResult visitNamedFunctionExpressionJudgment( |
| NamedFunctionExpressionJudgment node, DartType typeContext) { |
| DartType inferredType = inferrer |
| .inferExpression(node.variable.initializer, typeContext, true) |
| .inferredType; |
| node.variable.type = inferredType; |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| @override |
| ExpressionInferenceResult visitNot(Not node, DartType typeContext) { |
| InterfaceType boolType = |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable); |
| DartType inferredType = inferrer |
| .inferExpression(node.operand, boolType, !inferrer.isTopLevel) |
| .inferredType; |
| inferrer.ensureAssignable( |
| boolType, inferredType, node.operand, node.fileOffset); |
| return new ExpressionInferenceResult(boolType); |
| } |
| |
| @override |
| ExpressionInferenceResult visitNullCheck( |
| NullCheck node, DartType typeContext) { |
| // TODO(johnniwinther): Should the typeContext for the operand be |
| // `Nullable(typeContext)`? |
| DartType inferredType = inferrer |
| .inferExpression(node.operand, typeContext, !inferrer.isTopLevel) |
| .inferredType; |
| // TODO(johnniwinther): Check that the inferred type is potentially |
| // nullable. |
| // TODO(johnniwinther): Return `NonNull(inferredType)`. |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| ExpressionInferenceResult visitNullAwareMethodInvocation( |
| NullAwareMethodInvocation node, DartType typeContext) { |
| inferrer.inferStatement(node.variable); |
| ExpressionInferenceResult readResult = inferrer.inferExpression( |
| node.invocation, typeContext, true, |
| isVoidAllowed: true); |
| Member equalsMember = inferrer |
| .findInterfaceMember(node.variable.type, equalsName, node.fileOffset) |
| .member; |
| |
| DartType inferredType = readResult.inferredType; |
| |
| Expression replacement; |
| MethodInvocation equalsNull = createEqualsNull( |
| node.fileOffset, |
| new VariableGet(node.variable)..fileOffset = node.fileOffset, |
| equalsMember); |
| ConditionalExpression condition = new ConditionalExpression( |
| equalsNull, |
| new NullLiteral()..fileOffset = node.fileOffset, |
| node.invocation, |
| inferredType); |
| node.replaceWith(replacement = new Let(node.variable, condition) |
| ..fileOffset = node.fileOffset); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitNullAwarePropertyGet( |
| NullAwarePropertyGet node, DartType typeContext) { |
| inferrer.inferStatement(node.variable); |
| ExpressionInferenceResult readResult = |
| inferrer.inferExpression(node.read, const UnknownType(), true); |
| Member equalsMember = inferrer |
| .findInterfaceMember(node.variable.type, equalsName, node.fileOffset) |
| .member; |
| |
| DartType inferredType = readResult.inferredType; |
| |
| Expression replacement; |
| MethodInvocation equalsNull = createEqualsNull( |
| node.fileOffset, |
| new VariableGet(node.variable)..fileOffset = node.fileOffset, |
| equalsMember); |
| ConditionalExpression condition = new ConditionalExpression( |
| equalsNull, |
| new NullLiteral()..fileOffset = node.fileOffset, |
| node.read, |
| inferredType); |
| node.replaceWith(replacement = new Let(node.variable, condition) |
| ..fileOffset = node.fileOffset); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitNullAwarePropertySet( |
| NullAwarePropertySet node, DartType typeContext) { |
| inferrer.inferStatement(node.variable); |
| ExpressionInferenceResult writeResult = |
| inferrer.inferExpression(node.write, typeContext, true); |
| Member equalsMember = inferrer |
| .findInterfaceMember(node.variable.type, equalsName, node.fileOffset) |
| .member; |
| |
| DartType inferredType = writeResult.inferredType; |
| |
| Expression replacement; |
| MethodInvocation equalsNull = createEqualsNull( |
| node.fileOffset, |
| new VariableGet(node.variable)..fileOffset = node.fileOffset, |
| equalsMember); |
| ConditionalExpression condition = new ConditionalExpression( |
| equalsNull, |
| new NullLiteral()..fileOffset = node.fileOffset, |
| node.write, |
| inferredType); |
| node.replaceWith(replacement = new Let(node.variable, condition) |
| ..fileOffset = node.fileOffset); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitNullAwareExtension( |
| NullAwareExtension node, DartType typeContext) { |
| inferrer.inferStatement(node.variable); |
| ExpressionInferenceResult expressionResult = |
| inferrer.inferExpression(node.expression, const UnknownType(), true); |
| Member equalsMember = inferrer |
| .findInterfaceMember(node.variable.type, equalsName, node.fileOffset) |
| .member; |
| |
| DartType inferredType = expressionResult.inferredType; |
| |
| Expression replacement; |
| MethodInvocation equalsNull = createEqualsNull( |
| node.fileOffset, |
| new VariableGet(node.variable)..fileOffset = node.fileOffset, |
| equalsMember); |
| ConditionalExpression condition = new ConditionalExpression( |
| equalsNull, |
| new NullLiteral()..fileOffset = node.fileOffset, |
| node.expression, |
| inferredType); |
| node.replaceWith(replacement = new Let(node.variable, condition) |
| ..fileOffset = node.fileOffset); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitStaticPostIncDec( |
| StaticPostIncDec node, DartType typeContext) { |
| inferrer.inferStatement(node.read); |
| inferrer.inferStatement(node.write); |
| DartType inferredType = node.read.type; |
| Expression replacement = node.replace(); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitSuperPostIncDec( |
| SuperPostIncDec node, DartType typeContext) { |
| inferrer.inferStatement(node.read); |
| inferrer.inferStatement(node.write); |
| DartType inferredType = node.read.type; |
| Expression replacement = node.replace(); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitLocalPostIncDec( |
| LocalPostIncDec node, DartType typeContext) { |
| inferrer.inferStatement(node.read); |
| inferrer.inferStatement(node.write); |
| DartType inferredType = node.read.type; |
| Expression replacement = node.replace(); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitPropertyPostIncDec( |
| PropertyPostIncDec node, DartType typeContext) { |
| inferrer.inferStatement(node.variable); |
| inferrer.inferStatement(node.read); |
| inferrer.inferStatement(node.write); |
| DartType inferredType = node.read.type; |
| Expression replacement = node.replace(); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitCompoundPropertySet( |
| CompoundPropertySet node, DartType typeContext) { |
| inferrer.inferStatement(node.variable); |
| ExpressionInferenceResult writeResult = inferrer |
| .inferExpression(node.write, typeContext, true, isVoidAllowed: true); |
| Expression replacement = node.replace(); |
| return new ExpressionInferenceResult(writeResult.inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitIfNullPropertySet( |
| IfNullPropertySet node, DartType typeContext) { |
| inferrer.inferStatement(node.variable); |
| ExpressionInferenceResult readResult = inferrer.inferExpression( |
| node.read, const UnknownType(), true, |
| isVoidAllowed: true); |
| ExpressionInferenceResult writeResult = inferrer |
| .inferExpression(node.write, typeContext, true, isVoidAllowed: true); |
| Member equalsMember = inferrer |
| .findInterfaceMember( |
| readResult.inferredType, equalsName, node.fileOffset) |
| .member; |
| |
| DartType inferredType = inferrer.typeSchemaEnvironment |
| .getStandardUpperBound( |
| readResult.inferredType, writeResult.inferredType); |
| |
| Expression replacement; |
| if (node.forEffect) { |
| // Encode `o.a ??= b` as: |
| // |
| // let v1 = o in v1.a == null ? v1.a = b : null |
| // |
| MethodInvocation equalsNull = |
| createEqualsNull(node.fileOffset, node.read, equalsMember); |
| ConditionalExpression conditional = new ConditionalExpression( |
| equalsNull, |
| node.write, |
| new NullLiteral()..fileOffset = node.fileOffset, |
| inferredType) |
| ..fileOffset = node.fileOffset; |
| node.replaceWith(replacement = |
| new Let(node.variable, conditional..fileOffset = node.fileOffset)); |
| } else { |
| // Encode `o.a ??= b` as: |
| // |
| // let v1 = o in let v2 = v1.a in v2 == null ? v1.a = b : v2 |
| // |
| VariableDeclaration readVariable = |
| createVariable(node.read, readResult.inferredType); |
| MethodInvocation equalsNull = createEqualsNull( |
| node.fileOffset, createVariableGet(readVariable), equalsMember); |
| ConditionalExpression conditional = new ConditionalExpression( |
| equalsNull, node.write, createVariableGet(readVariable), inferredType) |
| ..fileOffset = node.fileOffset; |
| node.replaceWith(replacement = |
| new Let(node.variable, createLet(readVariable, conditional)) |
| ..fileOffset = node.fileOffset); |
| } |
| |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitIfNullSet( |
| IfNullSet node, DartType typeContext) { |
| ExpressionInferenceResult readResult = |
| inferrer.inferExpression(node.read, const UnknownType(), true); |
| ExpressionInferenceResult writeResult = inferrer |
| .inferExpression(node.write, typeContext, true, isVoidAllowed: true); |
| Member equalsMember = inferrer |
| .findInterfaceMember( |
| readResult.inferredType, equalsName, node.fileOffset) |
| .member; |
| |
| DartType inferredType = inferrer.typeSchemaEnvironment |
| .getStandardUpperBound( |
| readResult.inferredType, writeResult.inferredType); |
| |
| Expression replacement; |
| if (node.forEffect) { |
| // Encode `a ??= b` as: |
| // |
| // a == null ? a = b : null |
| // |
| MethodInvocation equalsNull = |
| createEqualsNull(node.fileOffset, node.read, equalsMember); |
| node.replaceWith(replacement = new ConditionalExpression( |
| equalsNull, |
| node.write, |
| new NullLiteral()..fileOffset = node.fileOffset, |
| inferredType) |
| ..fileOffset = node.fileOffset); |
| } else { |
| // Encode `a ??= b` as: |
| // |
| // let v1 = a in v1 == null ? a = b : v1 |
| // |
| VariableDeclaration readVariable = |
| createVariable(node.read, readResult.inferredType); |
| MethodInvocation equalsNull = createEqualsNull( |
| node.fileOffset, createVariableGet(readVariable), equalsMember); |
| ConditionalExpression conditional = new ConditionalExpression( |
| equalsNull, node.write, createVariableGet(readVariable), inferredType) |
| ..fileOffset = node.fileOffset; |
| node.replaceWith(replacement = new Let(readVariable, conditional) |
| ..fileOffset = node.fileOffset); |
| } |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitIndexSet(IndexSet node, DartType typeContext) { |
| ExpressionInferenceResult receiverResult = inferrer.inferExpression( |
| node.receiver, const UnknownType(), true, |
| isVoidAllowed: true); |
| DartType receiverType = receiverResult.inferredType; |
| VariableDeclaration receiverVariable = |
| createVariable(node.receiver, receiverType); |
| |
| ObjectAccessTarget indexSetTarget = inferrer.findInterfaceMember( |
| receiverType, indexSetName, node.fileOffset, |
| includeExtensionMethods: true); |
| |
| DartType indexType = inferrer.getIndexKeyType(indexSetTarget, receiverType); |
| DartType valueType = |
| inferrer.getIndexSetValueType(indexSetTarget, receiverType); |
| |
| ExpressionInferenceResult indexResult = inferrer |
| .inferExpression(node.index, indexType, true, isVoidAllowed: true); |
| |
| inferrer.ensureAssignable( |
| indexType, indexResult.inferredType, node.index, node.index.fileOffset); |
| |
| VariableDeclaration indexVariable = |
| createVariable(node.index, indexResult.inferredType); |
| |
| ExpressionInferenceResult valueResult = inferrer |
| .inferExpression(node.value, valueType, true, isVoidAllowed: true); |
| inferrer.ensureAssignable( |
| valueType, valueResult.inferredType, node.value, node.value.fileOffset); |
| VariableDeclaration valueVariable = |
| createVariable(node.value, valueResult.inferredType); |
| |
| // The inferred type is that inferred type of the value expression and not |
| // the type of the value parameter. |
| DartType inferredType = valueResult.inferredType; |
| |
| Expression replacement; |
| Expression assignment; |
| if (indexSetTarget.isMissing) { |
| assignment = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments('[]=', receiverType), |
| node.fileOffset, |
| '[]='.length); |
| } else if (indexSetTarget.isExtensionMember) { |
| assert(indexSetTarget.extensionMethodKind != ProcedureKind.Setter); |
| assignment = new StaticInvocation( |
| indexSetTarget.member, |
| new Arguments(<Expression>[ |
| createVariableGet(receiverVariable), |
| createVariableGet(indexVariable), |
| createVariableGet(valueVariable) |
| ], types: indexSetTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.fileOffset) |
| ..fileOffset = node.fileOffset; |
| } else { |
| assignment = new MethodInvocation( |
| createVariableGet(receiverVariable), |
| indexSetName, |
| new Arguments(<Expression>[ |
| createVariableGet(indexVariable), |
| createVariableGet(valueVariable) |
| ]) |
| ..fileOffset = node.fileOffset, |
| indexSetTarget.member) |
| ..fileOffset = node.fileOffset; |
| } |
| VariableDeclaration assignmentVariable = |
| createVariable(assignment, const VoidType()); |
| node.replaceWith(replacement = new Let( |
| receiverVariable, |
| createLet( |
| indexVariable, |
| createLet( |
| valueVariable, |
| createLet( |
| assignmentVariable, createVariableGet(valueVariable)))) |
| ..fileOffset = node.fileOffset)); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitSuperIndexSet( |
| SuperIndexSet node, DartType typeContext) { |
| ObjectAccessTarget indexSetTarget = node.setter != null |
| ? new ObjectAccessTarget.interfaceMember(node.setter) |
| : const ObjectAccessTarget.missing(); |
| |
| DartType indexType = |
| inferrer.getIndexKeyType(indexSetTarget, inferrer.thisType); |
| DartType valueType = |
| inferrer.getIndexSetValueType(indexSetTarget, inferrer.thisType); |
| |
| ExpressionInferenceResult indexResult = inferrer |
| .inferExpression(node.index, indexType, true, isVoidAllowed: true); |
| |
| inferrer.ensureAssignable( |
| indexType, indexResult.inferredType, node.index, node.index.fileOffset); |
| |
| VariableDeclaration indexVariable = |
| createVariable(node.index, indexResult.inferredType); |
| |
| ExpressionInferenceResult valueResult = inferrer |
| .inferExpression(node.value, valueType, true, isVoidAllowed: true); |
| inferrer.ensureAssignable( |
| valueType, valueResult.inferredType, node.value, node.value.fileOffset); |
| VariableDeclaration valueVariable = |
| createVariable(node.value, valueResult.inferredType); |
| |
| // The inferred type is that inferred type of the value expression and not |
| // the type of the value parameter. |
| DartType inferredType = valueResult.inferredType; |
| |
| Expression replacement; |
| Expression assignment; |
| if (indexSetTarget.isMissing) { |
| assignment = inferrer.helper.buildProblem( |
| templateSuperclassHasNoMethod.withArguments(indexSetName.name), |
| node.fileOffset, |
| noLength); |
| } else { |
| assert(indexSetTarget.isInstanceMember); |
| inferrer.instrumentation?.record(inferrer.uri, node.fileOffset, 'target', |
| new InstrumentationValueForMember(node.setter)); |
| assignment = new SuperMethodInvocation( |
| indexSetName, |
| new Arguments(<Expression>[ |
| createVariableGet(indexVariable), |
| createVariableGet(valueVariable) |
| ]) |
| ..fileOffset = node.fileOffset, |
| indexSetTarget.member) |
| ..fileOffset = node.fileOffset; |
| } |
| VariableDeclaration assignmentVariable = |
| createVariable(assignment, const VoidType()); |
| node.replaceWith(replacement = new Let( |
| indexVariable, |
| createLet(valueVariable, |
| createLet(assignmentVariable, createVariableGet(valueVariable)))) |
| ..fileOffset = node.fileOffset); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitExtensionIndexSet( |
| ExtensionIndexSet node, DartType typeContext) { |
| ExpressionInferenceResult receiverResult = inferrer.inferExpression( |
| node.receiver, const UnknownType(), true, |
| isVoidAllowed: false); |
| |
| List<DartType> extensionTypeArguments = |
| inferrer.computeExtensionTypeArgument(node.extension, |
| node.explicitTypeArguments, receiverResult.inferredType); |
| |
| DartType receiverType = inferrer.getExtensionReceiverType( |
| node.extension, extensionTypeArguments); |
| |
| inferrer.ensureAssignable(receiverType, receiverResult.inferredType, |
| node.receiver, node.receiver.fileOffset); |
| |
| VariableDeclaration receiverVariable = |
| createVariable(node.receiver, receiverType); |
| |
| ObjectAccessTarget target = new ExtensionAccessTarget( |
| node.setter, null, ProcedureKind.Operator, extensionTypeArguments); |
| |
| DartType indexType = inferrer.getIndexKeyType(target, receiverType); |
| DartType valueType = inferrer.getIndexSetValueType(target, receiverType); |
| |
| ExpressionInferenceResult indexResult = inferrer |
| .inferExpression(node.index, indexType, true, isVoidAllowed: true); |
| |
| inferrer.ensureAssignable( |
| indexType, indexResult.inferredType, node.index, node.index.fileOffset); |
| |
| VariableDeclaration indexVariable = |
| createVariable(node.index, indexResult.inferredType); |
| |
| ExpressionInferenceResult valueResult = inferrer |
| .inferExpression(node.value, valueType, true, isVoidAllowed: true); |
| inferrer.ensureAssignable( |
| valueType, valueResult.inferredType, node.value, node.value.fileOffset); |
| VariableDeclaration valueVariable = |
| createVariable(node.value, valueResult.inferredType); |
| |
| // The inferred type is that inferred type of the value expression and not |
| // the type of the value parameter. |
| DartType inferredType = valueResult.inferredType; |
| |
| Expression replacement; |
| Expression assignment; |
| if (target.isMissing) { |
| assignment = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments( |
| indexSetName.name, receiverType), |
| node.fileOffset, |
| noLength); |
| } else { |
| assert(target.isExtensionMember); |
| assignment = new StaticInvocation( |
| target.member, |
| new Arguments(<Expression>[ |
| createVariableGet(receiverVariable), |
| createVariableGet(indexVariable), |
| createVariableGet(valueVariable) |
| ], types: target.inferredExtensionTypeArguments) |
| ..fileOffset = node.fileOffset) |
| ..fileOffset = node.fileOffset; |
| } |
| VariableDeclaration assignmentVariable = |
| createVariable(assignment, const VoidType()); |
| node.replaceWith(replacement = new Let( |
| receiverVariable, |
| createLet( |
| indexVariable, |
| createLet( |
| valueVariable, |
| createLet( |
| assignmentVariable, createVariableGet(valueVariable))))) |
| ..fileOffset = node.fileOffset); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitIfNullIndexSet( |
| IfNullIndexSet node, DartType typeContext) { |
| ExpressionInferenceResult receiverResult = inferrer.inferExpression( |
| node.receiver, const UnknownType(), true, |
| isVoidAllowed: true); |
| DartType receiverType = receiverResult.inferredType; |
| VariableDeclaration receiverVariable; |
| Expression readReceiver; |
| Expression writeReceiver; |
| if (node.readOnlyReceiver) { |
| readReceiver = node.receiver; |
| writeReceiver = readReceiver.accept<TreeNode>(new CloneVisitor()); |
| } else { |
| receiverVariable = createVariable(node.receiver, receiverType); |
| readReceiver = createVariableGet(receiverVariable); |
| writeReceiver = createVariableGet(receiverVariable); |
| } |
| |
| ObjectAccessTarget readTarget = inferrer.findInterfaceMember( |
| receiverType, indexGetName, node.readOffset, |
| includeExtensionMethods: true); |
| |
| MethodContravarianceCheckKind checkKind = |
| inferrer.preCheckInvocationContravariance(receiverType, readTarget, |
| isThisReceiver: node.receiver is ThisExpression); |
| |
| DartType readType = inferrer.getReturnType(readTarget, receiverType); |
| DartType readIndexType = inferrer.getIndexKeyType(readTarget, receiverType); |
| |
| Member equalsMember = inferrer |
| .findInterfaceMember(readType, equalsName, node.testOffset) |
| .member; |
| |
| ObjectAccessTarget writeTarget = inferrer.findInterfaceMember( |
| receiverType, indexSetName, node.writeOffset, |
| includeExtensionMethods: true); |
| |
| DartType writeIndexType = |
| inferrer.getIndexKeyType(writeTarget, receiverType); |
| DartType valueType = |
| inferrer.getIndexSetValueType(writeTarget, receiverType); |
| |
| ExpressionInferenceResult indexResult = inferrer |
| .inferExpression(node.index, readIndexType, true, isVoidAllowed: true); |
| |
| VariableDeclaration indexVariable = |
| createVariable(node.index, indexResult.inferredType); |
| |
| Expression readIndex = createVariableGet(indexVariable); |
| readIndex = inferrer.ensureAssignable(readIndexType, |
| indexResult.inferredType, readIndex, readIndex.fileOffset) ?? |
| readIndex; |
| |
| Expression writeIndex = createVariableGet(indexVariable); |
| writeIndex = inferrer.ensureAssignable(writeIndexType, |
| indexResult.inferredType, writeIndex, writeIndex.fileOffset) ?? |
| writeIndex; |
| |
| ExpressionInferenceResult valueResult = inferrer |
| .inferExpression(node.value, valueType, true, isVoidAllowed: true); |
| inferrer.ensureAssignable( |
| valueType, valueResult.inferredType, node.value, node.value.fileOffset); |
| |
| DartType inferredType = inferrer.typeSchemaEnvironment |
| .getStandardUpperBound(readType, valueResult.inferredType); |
| |
| Expression read; |
| |
| if (readTarget.isMissing) { |
| read = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments('[]', receiverType), |
| node.readOffset, |
| '[]'.length); |
| } else if (readTarget.isExtensionMember) { |
| read = new StaticInvocation( |
| readTarget.member, |
| new Arguments(<Expression>[ |
| readReceiver, |
| readIndex, |
| ], types: readTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.readOffset) |
| ..fileOffset = node.readOffset; |
| } else { |
| read = new MethodInvocation( |
| readReceiver, |
| indexGetName, |
| new Arguments(<Expression>[ |
| readIndex, |
| ]) |
| ..fileOffset = node.readOffset, |
| readTarget.member) |
| ..fileOffset = node.readOffset; |
| |
| if (checkKind == MethodContravarianceCheckKind.checkMethodReturn) { |
| if (inferrer.instrumentation != null) { |
| inferrer.instrumentation.record(inferrer.uri, node.readOffset, |
| 'checkReturn', new InstrumentationValueForType(readType)); |
| } |
| read = new AsExpression(read, readType) |
| ..isTypeError = true |
| ..fileOffset = node.readOffset; |
| } |
| } |
| |
| VariableDeclaration valueVariable; |
| Expression valueExpression; |
| if (node.forEffect) { |
| valueExpression = node.value; |
| } else { |
| valueVariable = createVariable(node.value, valueResult.inferredType); |
| valueExpression = createVariableGet(valueVariable); |
| } |
| |
| Expression write; |
| |
| if (writeTarget.isMissing) { |
| write = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments('[]=', receiverType), |
| node.writeOffset, |
| '[]='.length); |
| } else if (writeTarget.isExtensionMember) { |
| assert(writeTarget.extensionMethodKind != ProcedureKind.Setter); |
| write = new StaticInvocation( |
| writeTarget.member, |
| new Arguments( |
| <Expression>[writeReceiver, writeIndex, valueExpression], |
| types: writeTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.writeOffset) |
| ..fileOffset = node.writeOffset; |
| } else { |
| write = new MethodInvocation( |
| writeReceiver, |
| indexSetName, |
| new Arguments(<Expression>[writeIndex, valueExpression]) |
| ..fileOffset = node.writeOffset, |
| writeTarget.member) |
| ..fileOffset = node.writeOffset; |
| } |
| |
| Expression inner; |
| if (node.forEffect) { |
| // Encode `Extension(o)[a] ??= b`, if `node.readOnlyReceiver` is false, |
| // as: |
| // |
| // let receiverVariable = o in |
| // let indexVariable = a in |
| // receiverVariable[indexVariable] == null |
| // ? receiverVariable.[]=(indexVariable, b) : null |
| // |
| // and if `node.readOnlyReceiver` is true as: |
| // |
| // let indexVariable = a in |
| // o[indexVariable] == null ? o.[]=(indexVariable, b) : null |
| // |
| MethodInvocation equalsNull = |
| createEqualsNull(node.testOffset, read, equalsMember); |
| ConditionalExpression conditional = new ConditionalExpression(equalsNull, |
| write, new NullLiteral()..fileOffset = node.testOffset, inferredType) |
| ..fileOffset = node.testOffset; |
| inner = createLet(indexVariable, conditional); |
| } else { |
| // Encode `Extension(o)[a] ??= b` as, if `node.readOnlyReceiver` is false, |
| // as: |
| // |
| // let receiverVariable = o in |
| // let indexVariable = a in |
| // let readVariable = receiverVariable[indexVariable] in |
| // readVariable == null |
| // ? (let valueVariable = b in |
| // let writeVariable = |
| // receiverVariable.[]=(indexVariable, valueVariable) in |
| // valueVariable) |
| // : readVariable |
| // |
| // and if `node.readOnlyReceiver` is true as: |
| // |
| // let indexVariable = a in |
| // let readVariable = o[indexVariable] in |
| // readVariable == null |
| // ? (let valueVariable = b in |
| // let writeVariable = o.[]=(indexVariable, valueVariable) in |
| // valueVariable) |
| // : readVariable |
| // |
| // |
| assert(valueVariable != null); |
| |
| VariableDeclaration readVariable = createVariable(read, readType); |
| MethodInvocation equalsNull = createEqualsNull( |
| node.testOffset, createVariableGet(readVariable), equalsMember); |
| VariableDeclaration writeVariable = |
| createVariable(write, const VoidType()); |
| ConditionalExpression conditional = new ConditionalExpression( |
| equalsNull, |
| createLet(valueVariable, |
| createLet(writeVariable, createVariableGet(valueVariable))), |
| createVariableGet(readVariable), |
| inferredType) |
| ..fileOffset = node.fileOffset; |
| inner = createLet(indexVariable, createLet(readVariable, conditional)); |
| } |
| |
| Expression replacement; |
| if (receiverVariable != null) { |
| node.replaceWith(replacement = new Let(receiverVariable, inner) |
| ..fileOffset = node.fileOffset); |
| } else { |
| node.replaceWith(replacement = inner); |
| } |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitIfNullSuperIndexSet( |
| IfNullSuperIndexSet node, DartType typeContext) { |
| ObjectAccessTarget readTarget = node.getter != null |
| ? new ObjectAccessTarget.interfaceMember(node.getter) |
| : const ObjectAccessTarget.missing(); |
| |
| DartType readType = inferrer.getReturnType(readTarget, inferrer.thisType); |
| DartType readIndexType = |
| inferrer.getIndexKeyType(readTarget, inferrer.thisType); |
| |
| Member equalsMember = inferrer |
| .findInterfaceMember(readType, equalsName, node.testOffset) |
| .member; |
| |
| ObjectAccessTarget writeTarget = node.setter != null |
| ? new ObjectAccessTarget.interfaceMember(node.setter) |
| : const ObjectAccessTarget.missing(); |
| |
| DartType writeIndexType = |
| inferrer.getIndexKeyType(writeTarget, inferrer.thisType); |
| DartType valueType = |
| inferrer.getIndexSetValueType(writeTarget, inferrer.thisType); |
| |
| ExpressionInferenceResult indexResult = inferrer |
| .inferExpression(node.index, readIndexType, true, isVoidAllowed: true); |
| |
| VariableDeclaration indexVariable = |
| createVariable(node.index, indexResult.inferredType); |
| |
| VariableGet readIndex = createVariableGet(indexVariable); |
| inferrer.ensureAssignable(readIndexType, indexResult.inferredType, |
| readIndex, readIndex.fileOffset); |
| |
| VariableGet writeIndex = createVariableGet(indexVariable); |
| inferrer.ensureAssignable(writeIndexType, indexResult.inferredType, |
| writeIndex, writeIndex.fileOffset); |
| |
| ExpressionInferenceResult valueResult = inferrer |
| .inferExpression(node.value, valueType, true, isVoidAllowed: true); |
| inferrer.ensureAssignable( |
| valueType, valueResult.inferredType, node.value, node.value.fileOffset); |
| |
| DartType inferredType = inferrer.typeSchemaEnvironment |
| .getStandardUpperBound(readType, valueResult.inferredType); |
| |
| Expression read; |
| |
| if (readTarget.isMissing) { |
| read = inferrer.helper.buildProblem( |
| templateSuperclassHasNoMethod.withArguments('[]'), |
| node.readOffset, |
| '[]'.length); |
| } else { |
| assert(readTarget.isInstanceMember); |
| inferrer.instrumentation?.record(inferrer.uri, node.readOffset, 'target', |
| new InstrumentationValueForMember(node.getter)); |
| read = new SuperMethodInvocation( |
| indexGetName, |
| new Arguments(<Expression>[ |
| readIndex, |
| ]) |
| ..fileOffset = node.readOffset, |
| readTarget.member) |
| ..fileOffset = node.readOffset; |
| } |
| |
| VariableDeclaration valueVariable; |
| Expression valueExpression; |
| if (node.forEffect) { |
| valueExpression = node.value; |
| } else { |
| valueVariable = createVariable(node.value, valueResult.inferredType); |
| valueExpression = createVariableGet(valueVariable); |
| } |
| |
| Expression write; |
| |
| if (writeTarget.isMissing) { |
| write = inferrer.helper.buildProblem( |
| templateSuperclassHasNoMethod.withArguments('[]='), |
| node.writeOffset, |
| '[]='.length); |
| } else { |
| assert(writeTarget.isInstanceMember); |
| inferrer.instrumentation?.record(inferrer.uri, node.writeOffset, 'target', |
| new InstrumentationValueForMember(node.setter)); |
| write = new SuperMethodInvocation( |
| indexSetName, |
| new Arguments( |
| <Expression>[createVariableGet(indexVariable), valueExpression]) |
| ..fileOffset = node.writeOffset, |
| writeTarget.member) |
| ..fileOffset = node.writeOffset; |
| } |
| |
| Expression replacement; |
| if (node.forEffect) { |
| // Encode `o[a] ??= b` as: |
| // |
| // let v1 = a in |
| // super[v1] == null ? super.[]=(v1, b) : null |
| // |
| MethodInvocation equalsNull = |
| createEqualsNull(node.testOffset, read, equalsMember); |
| ConditionalExpression conditional = new ConditionalExpression(equalsNull, |
| write, new NullLiteral()..fileOffset = node.testOffset, inferredType) |
| ..fileOffset = node.testOffset; |
| replacement = createLet(indexVariable, conditional); |
| } else { |
| // Encode `o[a] ??= b` as: |
| // |
| // let v1 = a in |
| // let v2 = super[v1] in |
| // v2 == null |
| // ? (let v3 = b in |
| // let _ = super.[]=(v1, v3) in |
| // v3) |
| // : v2 |
| // |
| assert(valueVariable != null); |
| |
| VariableDeclaration readVariable = createVariable(read, readType); |
| MethodInvocation equalsNull = createEqualsNull( |
| node.testOffset, createVariableGet(readVariable), equalsMember); |
| VariableDeclaration writeVariable = |
| createVariable(write, const VoidType()); |
| ConditionalExpression conditional = new ConditionalExpression( |
| equalsNull, |
| createLet(valueVariable, |
| createLet(writeVariable, createVariableGet(valueVariable))), |
| createVariableGet(readVariable), |
| inferredType) |
| ..fileOffset = node.fileOffset; |
| replacement = |
| createLet(indexVariable, createLet(readVariable, conditional)); |
| } |
| |
| node.replaceWith(replacement); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitIfNullExtensionIndexSet( |
| IfNullExtensionIndexSet node, DartType typeContext) { |
| ExpressionInferenceResult receiverResult = inferrer.inferExpression( |
| node.receiver, const UnknownType(), true, |
| isVoidAllowed: false); |
| |
| List<DartType> extensionTypeArguments = |
| inferrer.computeExtensionTypeArgument(node.extension, |
| node.explicitTypeArguments, receiverResult.inferredType); |
| |
| DartType receiverType = inferrer.getExtensionReceiverType( |
| node.extension, extensionTypeArguments); |
| |
| inferrer.ensureAssignable(receiverType, receiverResult.inferredType, |
| node.receiver, node.receiver.fileOffset); |
| |
| VariableDeclaration receiverVariable = |
| createVariable(node.receiver, receiverType); |
| |
| ObjectAccessTarget readTarget = node.getter != null |
| ? new ExtensionAccessTarget( |
| node.getter, null, ProcedureKind.Operator, extensionTypeArguments) |
| : const ObjectAccessTarget.missing(); |
| |
| DartType readType = inferrer.getReturnType(readTarget, receiverType); |
| DartType readIndexType = inferrer.getIndexKeyType(readTarget, receiverType); |
| |
| Member equalsMember = inferrer |
| .findInterfaceMember(readType, equalsName, node.testOffset) |
| .member; |
| |
| ObjectAccessTarget writeTarget = node.setter != null |
| ? new ExtensionAccessTarget( |
| node.setter, null, ProcedureKind.Operator, extensionTypeArguments) |
| : const ObjectAccessTarget.missing(); |
| |
| DartType writeIndexType = |
| inferrer.getIndexKeyType(writeTarget, receiverType); |
| DartType valueType = |
| inferrer.getIndexSetValueType(writeTarget, receiverType); |
| |
| ExpressionInferenceResult indexResult = inferrer |
| .inferExpression(node.index, readIndexType, true, isVoidAllowed: true); |
| |
| VariableDeclaration indexVariable = |
| createVariable(node.index, indexResult.inferredType); |
| |
| VariableGet readIndex = createVariableGet(indexVariable); |
| inferrer.ensureAssignable(readIndexType, indexResult.inferredType, |
| readIndex, readIndex.fileOffset); |
| |
| VariableGet writeIndex = createVariableGet(indexVariable); |
| inferrer.ensureAssignable(writeIndexType, indexResult.inferredType, |
| writeIndex, writeIndex.fileOffset); |
| |
| ExpressionInferenceResult valueResult = inferrer |
| .inferExpression(node.value, valueType, true, isVoidAllowed: true); |
| inferrer.ensureAssignable( |
| valueType, valueResult.inferredType, node.value, node.value.fileOffset); |
| |
| DartType inferredType = inferrer.typeSchemaEnvironment |
| .getStandardUpperBound(readType, valueResult.inferredType); |
| |
| Expression read; |
| |
| if (readTarget.isMissing) { |
| read = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments( |
| indexGetName.name, receiverType), |
| node.readOffset, |
| noLength); |
| } else { |
| assert(readTarget.isExtensionMember); |
| read = new StaticInvocation( |
| readTarget.member, |
| new Arguments(<Expression>[ |
| createVariableGet(receiverVariable), |
| readIndex, |
| ], types: readTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.readOffset) |
| ..fileOffset = node.readOffset; |
| } |
| |
| VariableDeclaration valueVariable; |
| Expression valueExpression; |
| if (node.forEffect) { |
| valueExpression = node.value; |
| } else { |
| valueVariable = createVariable(node.value, valueResult.inferredType); |
| valueExpression = createVariableGet(valueVariable); |
| } |
| |
| Expression write; |
| |
| if (writeTarget.isMissing) { |
| write = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments( |
| indexSetName.name, receiverType), |
| node.writeOffset, |
| noLength); |
| } else { |
| assert(writeTarget.isExtensionMember); |
| write = new StaticInvocation( |
| writeTarget.member, |
| new Arguments(<Expression>[ |
| createVariableGet(receiverVariable), |
| writeIndex, |
| valueExpression |
| ], types: writeTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.writeOffset) |
| ..fileOffset = node.writeOffset; |
| } |
| |
| Expression inner; |
| if (node.forEffect) { |
| // Encode `Extension(o)[a] ??= b` as: |
| // |
| // let receiverVariable = o; |
| // let indexVariable = a in |
| // receiverVariable[indexVariable] == null |
| // ? receiverVariable.[]=(indexVariable, b) : null |
| // |
| MethodInvocation equalsNull = |
| createEqualsNull(node.testOffset, read, equalsMember); |
| ConditionalExpression conditional = new ConditionalExpression(equalsNull, |
| write, new NullLiteral()..fileOffset = node.testOffset, inferredType) |
| ..fileOffset = node.testOffset; |
| inner = createLet(indexVariable, conditional); |
| } else { |
| // Encode `Extension(o)[a] ??= b` as: |
| // |
| // let receiverVariable = o; |
| // let indexVariable = a in |
| // let readVariable = receiverVariable[indexVariable] in |
| // readVariable == null |
| // ? (let valueVariable = b in |
| // let writeVariable = |
| // receiverVariable.[]=(indexVariable, valueVariable) in |
| // valueVariable) |
| // : readVariable |
| // |
| assert(valueVariable != null); |
| |
| VariableDeclaration readVariable = createVariable(read, readType); |
| MethodInvocation equalsNull = createEqualsNull( |
| node.testOffset, createVariableGet(readVariable), equalsMember); |
| VariableDeclaration writeVariable = |
| createVariable(write, const VoidType()); |
| ConditionalExpression conditional = new ConditionalExpression( |
| equalsNull, |
| createLet(valueVariable, |
| createLet(writeVariable, createVariableGet(valueVariable))), |
| createVariableGet(readVariable), |
| inferredType) |
| ..fileOffset = node.fileOffset; |
| inner = createLet(indexVariable, createLet(readVariable, conditional)); |
| } |
| |
| Expression replacement = new Let(receiverVariable, inner) |
| ..fileOffset = node.fileOffset; |
| |
| node.replaceWith(replacement); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| ExpressionInferenceResult visitCompoundIndexSet( |
| CompoundIndexSet node, DartType typeContext) { |
| ExpressionInferenceResult receiverResult = inferrer.inferExpression( |
| node.receiver, const UnknownType(), true, |
| isVoidAllowed: true); |
| DartType receiverType = receiverResult.inferredType; |
| VariableDeclaration receiverVariable; |
| Expression readReceiver; |
| Expression writeReceiver; |
| if (node.readOnlyReceiver) { |
| readReceiver = node.receiver; |
| writeReceiver = readReceiver.accept<TreeNode>(new CloneVisitor()); |
| } else { |
| receiverVariable = createVariable(node.receiver, receiverType); |
| readReceiver = createVariableGet(receiverVariable); |
| writeReceiver = createVariableGet(receiverVariable); |
| } |
| |
| ObjectAccessTarget readTarget = inferrer.findInterfaceMember( |
| receiverType, indexGetName, node.readOffset, |
| includeExtensionMethods: true); |
| |
| MethodContravarianceCheckKind readCheckKind = |
| inferrer.preCheckInvocationContravariance(receiverType, readTarget, |
| isThisReceiver: node.receiver is ThisExpression); |
| |
| DartType readType = inferrer.getReturnType(readTarget, receiverType); |
| DartType readIndexType = inferrer.getPositionalParameterTypeForTarget( |
| readTarget, receiverType, 0); |
| |
| ExpressionInferenceResult indexResult = inferrer |
| .inferExpression(node.index, readIndexType, true, isVoidAllowed: true); |
| VariableDeclaration indexVariable = |
| createVariable(node.index, indexResult.inferredType); |
| |
| Expression readIndex = createVariableGet(indexVariable); |
| Expression readIndexReplacement = inferrer.ensureAssignable(readIndexType, |
| indexResult.inferredType, readIndex, readIndex.fileOffset); |
| if (readIndexReplacement != null) { |
| readIndex = readIndexReplacement; |
| } |
| |
| Expression read; |
| if (readTarget.isMissing) { |
| read = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments('[]', receiverType), |
| node.readOffset, |
| '[]'.length); |
| } else if (readTarget.isExtensionMember) { |
| read = new StaticInvocation( |
| readTarget.member, |
| new Arguments(<Expression>[ |
| readReceiver, |
| readIndex, |
| ], types: readTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.readOffset) |
| ..fileOffset = node.readOffset; |
| } else { |
| read = new MethodInvocation( |
| readReceiver, |
| indexGetName, |
| new Arguments(<Expression>[ |
| readIndex, |
| ]) |
| ..fileOffset = node.readOffset, |
| readTarget.member) |
| ..fileOffset = node.readOffset; |
| if (readCheckKind == MethodContravarianceCheckKind.checkMethodReturn) { |
| if (inferrer.instrumentation != null) { |
| inferrer.instrumentation.record(inferrer.uri, node.readOffset, |
| 'checkReturn', new InstrumentationValueForType(readType)); |
| } |
| read = new AsExpression(read, readType) |
| ..isTypeError = true |
| ..fileOffset = node.readOffset; |
| } |
| } |
| |
| VariableDeclaration leftVariable; |
| Expression left; |
| if (node.forEffect) { |
| left = read; |
| } else if (node.forPostIncDec) { |
| leftVariable = createVariable(read, readType); |
| left = createVariableGet(leftVariable); |
| } else { |
| left = read; |
| } |
| |
| ObjectAccessTarget binaryTarget = inferrer.findInterfaceMember( |
| readType, node.binaryName, node.binaryOffset, |
| includeExtensionMethods: true); |
| |
| MethodContravarianceCheckKind binaryCheckKind = |
| inferrer.preCheckInvocationContravariance(readType, binaryTarget, |
| isThisReceiver: false); |
| |
| DartType binaryType = inferrer.getReturnType(binaryTarget, readType); |
| DartType rhsType = |
| inferrer.getPositionalParameterTypeForTarget(binaryTarget, readType, 0); |
| |
| ExpressionInferenceResult rhsResult = |
| inferrer.inferExpression(node.rhs, rhsType, true, isVoidAllowed: true); |
| inferrer.ensureAssignable( |
| rhsType, rhsResult.inferredType, node.rhs, node.rhs.fileOffset); |
| |
| if (inferrer.isOverloadedArithmeticOperatorAndType( |
| binaryTarget, readType)) { |
| binaryType = inferrer.typeSchemaEnvironment |
| .getTypeOfOverloadedArithmetic(readType, rhsResult.inferredType); |
| } |
| |
| Expression binary; |
| if (binaryTarget.isMissing) { |
| binary = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments(node.binaryName.name, readType), |
| node.binaryOffset, |
| node.binaryName.name.length); |
| } else if (binaryTarget.isExtensionMember) { |
| assert(binaryTarget.extensionMethodKind != ProcedureKind.Setter); |
| binary = new StaticInvocation( |
| binaryTarget.member, |
| new Arguments(<Expression>[ |
| left, |
| node.rhs, |
| ], types: binaryTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.binaryOffset) |
| ..fileOffset = node.binaryOffset; |
| } else { |
| binary = new MethodInvocation( |
| left, |
| node.binaryName, |
| new Arguments(<Expression>[ |
| node.rhs, |
| ]) |
| ..fileOffset = node.binaryOffset, |
| binaryTarget.member) |
| ..fileOffset = node.binaryOffset; |
| |
| if (binaryCheckKind == MethodContravarianceCheckKind.checkMethodReturn) { |
| if (inferrer.instrumentation != null) { |
| inferrer.instrumentation.record(inferrer.uri, node.binaryOffset, |
| 'checkReturn', new InstrumentationValueForType(readType)); |
| } |
| binary = new AsExpression(binary, binaryType) |
| ..isTypeError = true |
| ..fileOffset = node.binaryOffset; |
| } |
| } |
| |
| ObjectAccessTarget writeTarget = inferrer.findInterfaceMember( |
| receiverType, indexSetName, node.writeOffset, |
| includeExtensionMethods: true); |
| |
| DartType writeIndexType = inferrer.getPositionalParameterTypeForTarget( |
| writeTarget, receiverType, 0); |
| Expression writeIndex = createVariableGet(indexVariable); |
| Expression writeIndexReplacement = inferrer.ensureAssignable(writeIndexType, |
| indexResult.inferredType, writeIndex, writeIndex.fileOffset); |
| if (writeIndexReplacement != null) { |
| writeIndex = writeIndexReplacement; |
| } |
| |
| DartType valueType = |
| inferrer.getIndexSetValueType(writeTarget, receiverType); |
| Expression binaryReplacement = inferrer.ensureAssignable( |
| valueType, binaryType, binary, node.fileOffset); |
| if (binaryReplacement != null) { |
| binary = binaryReplacement; |
| } |
| |
| VariableDeclaration valueVariable; |
| Expression valueExpression; |
| if (node.forEffect || node.forPostIncDec) { |
| valueExpression = binary; |
| } else { |
| valueVariable = createVariable(binary, binaryType); |
| valueExpression = createVariableGet(valueVariable); |
| } |
| |
| Expression write; |
| |
| if (writeTarget.isMissing) { |
| write = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments('[]=', receiverType), |
| node.writeOffset, |
| '[]='.length); |
| } else if (writeTarget.isExtensionMember) { |
| assert(writeTarget.extensionMethodKind != ProcedureKind.Setter); |
| write = new StaticInvocation( |
| writeTarget.member, |
| new Arguments( |
| <Expression>[writeReceiver, writeIndex, valueExpression], |
| types: writeTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.writeOffset) |
| ..fileOffset = node.writeOffset; |
| } else { |
| write = new MethodInvocation( |
| writeReceiver, |
| indexSetName, |
| new Arguments(<Expression>[writeIndex, valueExpression]) |
| ..fileOffset = node.writeOffset, |
| writeTarget.member) |
| ..fileOffset = node.writeOffset; |
| } |
| |
| Expression inner; |
| if (node.forEffect) { |
| assert(leftVariable == null); |
| assert(valueVariable == null); |
| // Encode `o[a] += b` as: |
| // |
| // let v1 = o in let v2 = a in v1.[]=(v2, v1.[](v2) + b) |
| // |
| inner = createLet(indexVariable, write); |
| } else if (node.forPostIncDec) { |
| // Encode `o[a]++` as: |
| // |
| // let v1 = o in |
| // let v2 = a in |
| // let v3 = v1.[](v2) |
| // let v4 = v1.[]=(v2, c3 + b) in v3 |
| // |
| assert(leftVariable != null); |
| assert(valueVariable == null); |
| |
| VariableDeclaration writeVariable = |
| createVariable(write, const VoidType()); |
| inner = createLet( |
| indexVariable, |
| createLet(leftVariable, |
| createLet(writeVariable, createVariableGet(leftVariable)))); |
| } else { |
| // Encode `o[a] += b` as: |
| // |
| // let v1 = o in |
| // let v2 = a in |
| // let v3 = v1.[](v2) + b |
| // let v4 = v1.[]=(v2, c3) in v3 |
| // |
| assert(leftVariable == null); |
| assert(valueVariable != null); |
| |
| VariableDeclaration writeVariable = |
| createVariable(write, const VoidType()); |
| inner = createLet( |
| indexVariable, |
| createLet(valueVariable, |
| createLet(writeVariable, createVariableGet(valueVariable)))); |
| } |
| |
| Expression replacement; |
| if (receiverVariable != null) { |
| node.replaceWith(replacement = new Let(receiverVariable, inner) |
| ..fileOffset = node.fileOffset); |
| } else { |
| node.replaceWith(replacement = inner); |
| } |
| return new ExpressionInferenceResult( |
| node.forPostIncDec ? readType : binaryType, replacement); |
| } |
| |
| ExpressionInferenceResult visitNullAwareCompoundSet( |
| NullAwareCompoundSet node, DartType typeContext) { |
| ExpressionInferenceResult receiverResult = inferrer.inferExpression( |
| node.receiver, const UnknownType(), true, |
| isVoidAllowed: true); |
| DartType receiverType = receiverResult.inferredType; |
| VariableDeclaration receiverVariable = |
| createVariable(node.receiver, receiverType); |
| Expression readReceiver = createVariableGet(receiverVariable); |
| Expression writeReceiver = createVariableGet(receiverVariable); |
| |
| Member equalsMember = inferrer |
| .findInterfaceMember(receiverType, equalsName, node.receiver.fileOffset) |
| .member; |
| |
| ObjectAccessTarget readTarget = inferrer.findInterfaceMember( |
| receiverType, node.propertyName, node.readOffset, |
| includeExtensionMethods: true); |
| |
| MethodContravarianceCheckKind readCheckKind = |
| inferrer.preCheckInvocationContravariance(receiverType, readTarget, |
| isThisReceiver: node.receiver is ThisExpression); |
| |
| DartType readType = inferrer.getGetterType(readTarget, receiverType); |
| |
| Expression read; |
| if (readTarget.isMissing) { |
| read = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments( |
| node.propertyName.name, receiverType), |
| node.readOffset, |
| node.propertyName.name.length); |
| } else if (readTarget.isExtensionMember) { |
| read = new StaticInvocation( |
| readTarget.member, |
| new Arguments(<Expression>[ |
| readReceiver, |
| ], types: readTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.readOffset) |
| ..fileOffset = node.readOffset; |
| } else { |
| read = new PropertyGet(readReceiver, node.propertyName, readTarget.member) |
| ..fileOffset = node.readOffset; |
| if (readCheckKind == MethodContravarianceCheckKind.checkMethodReturn) { |
| if (inferrer.instrumentation != null) { |
| inferrer.instrumentation.record(inferrer.uri, node.readOffset, |
| 'checkReturn', new InstrumentationValueForType(readType)); |
| } |
| read = new AsExpression(read, readType) |
| ..isTypeError = true |
| ..fileOffset = node.readOffset; |
| } |
| } |
| |
| VariableDeclaration leftVariable; |
| Expression left; |
| if (node.forEffect) { |
| left = read; |
| } else if (node.forPostIncDec) { |
| leftVariable = createVariable(read, readType); |
| left = createVariableGet(leftVariable); |
| } else { |
| left = read; |
| } |
| |
| ObjectAccessTarget binaryTarget = inferrer.findInterfaceMember( |
| readType, node.binaryName, node.binaryOffset, |
| includeExtensionMethods: true); |
| |
| MethodContravarianceCheckKind binaryCheckKind = |
| inferrer.preCheckInvocationContravariance(readType, binaryTarget, |
| isThisReceiver: false); |
| |
| DartType binaryType = inferrer.getReturnType(binaryTarget, readType); |
| DartType rhsType = |
| inferrer.getPositionalParameterTypeForTarget(binaryTarget, readType, 0); |
| |
| ExpressionInferenceResult rhsResult = |
| inferrer.inferExpression(node.rhs, rhsType, true, isVoidAllowed: true); |
| inferrer.ensureAssignable( |
| rhsType, rhsResult.inferredType, node.rhs, node.rhs.fileOffset); |
| |
| if (inferrer.isOverloadedArithmeticOperatorAndType( |
| binaryTarget, readType)) { |
| binaryType = inferrer.typeSchemaEnvironment |
| .getTypeOfOverloadedArithmetic(readType, rhsResult.inferredType); |
| } |
| |
| Expression binary; |
| if (binaryTarget.isMissing) { |
| binary = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments(node.binaryName.name, readType), |
| node.binaryOffset, |
| node.binaryName.name.length); |
| } else if (binaryTarget.isExtensionMember) { |
| binary = new StaticInvocation( |
| binaryTarget.member, |
| new Arguments(<Expression>[ |
| left, |
| node.rhs, |
| ], types: binaryTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.binaryOffset) |
| ..fileOffset = node.binaryOffset; |
| } else { |
| binary = new MethodInvocation( |
| left, |
| node.binaryName, |
| new Arguments(<Expression>[ |
| node.rhs, |
| ]) |
| ..fileOffset = node.binaryOffset, |
| binaryTarget.member) |
| ..fileOffset = node.binaryOffset; |
| |
| if (binaryCheckKind == MethodContravarianceCheckKind.checkMethodReturn) { |
| if (inferrer.instrumentation != null) { |
| inferrer.instrumentation.record(inferrer.uri, node.binaryOffset, |
| 'checkReturn', new InstrumentationValueForType(readType)); |
| } |
| binary = new AsExpression(binary, binaryType) |
| ..isTypeError = true |
| ..fileOffset = node.binaryOffset; |
| } |
| } |
| |
| ObjectAccessTarget writeTarget = inferrer.findInterfaceMember( |
| receiverType, node.propertyName, node.writeOffset, |
| setter: true, includeExtensionMethods: true); |
| |
| DartType valueType = inferrer.getSetterType(writeTarget, receiverType); |
| Expression binaryReplacement = inferrer.ensureAssignable( |
| valueType, binaryType, binary, node.fileOffset); |
| if (binaryReplacement != null) { |
| binary = binaryReplacement; |
| } |
| |
| VariableDeclaration valueVariable; |
| Expression valueExpression; |
| if (node.forEffect || node.forPostIncDec) { |
| valueExpression = binary; |
| } else { |
| valueVariable = createVariable(binary, binaryType); |
| valueExpression = createVariableGet(valueVariable); |
| } |
| |
| Expression write; |
| |
| if (writeTarget.isMissing) { |
| write = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments( |
| node.propertyName.name, receiverType), |
| node.writeOffset, |
| node.propertyName.name.length); |
| } else if (writeTarget.isExtensionMember) { |
| write = new StaticInvocation( |
| writeTarget.member, |
| new Arguments(<Expression>[writeReceiver, valueExpression], |
| types: writeTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.writeOffset) |
| ..fileOffset = node.writeOffset; |
| } else { |
| write = new PropertySet( |
| writeReceiver, node.propertyName, valueExpression, writeTarget.member) |
| ..fileOffset = node.writeOffset; |
| } |
| |
| DartType resultType = node.forPostIncDec ? readType : binaryType; |
| |
| Expression replacement; |
| if (node.forEffect) { |
| assert(leftVariable == null); |
| assert(valueVariable == null); |
| // Encode `receiver?.propertyName binaryName= rhs` as: |
| // |
| // let receiverVariable = receiver in |
| // receiverVariable == null ? null : |
| // receiverVariable.propertyName = |
| // receiverVariable.propertyName + rhs |
| // |
| |
| MethodInvocation equalsNull = createEqualsNull( |
| receiverVariable.fileOffset, |
| createVariableGet(receiverVariable), |
| equalsMember); |
| ConditionalExpression condition = new ConditionalExpression(equalsNull, |
| new NullLiteral()..fileOffset = node.readOffset, write, resultType); |
| replacement = createLet(receiverVariable, condition); |
| } else if (node.forPostIncDec) { |
| // Encode `receiver?.propertyName binaryName= rhs` from a postfix |
| // expression like `o?.a++` as: |
| // |
| // let receiverVariable = receiver in |
| // receiverVariable == null ? null : |
| // let leftVariable = receiverVariable.propertyName in |
| // let writeVariable = |
| // receiverVariable.propertyName = |
| // leftVariable binaryName rhs in |
| // leftVariable |
| // |
| assert(leftVariable != null); |
| assert(valueVariable == null); |
| |
| VariableDeclaration writeVariable = |
| createVariable(write, const VoidType()); |
| MethodInvocation equalsNull = createEqualsNull( |
| receiverVariable.fileOffset, |
| createVariableGet(receiverVariable), |
| equalsMember); |
| ConditionalExpression condition = new ConditionalExpression( |
| equalsNull, |
| new NullLiteral()..fileOffset = node.readOffset, |
| createLet(leftVariable, |
| createLet(writeVariable, createVariableGet(leftVariable))), |
| resultType); |
| replacement = createLet(receiverVariable, condition); |
| } else { |
| // Encode `receiver?.propertyName binaryName= rhs` as: |
| // |
| // let receiverVariable = receiver in |
| // receiverVariable == null ? null : |
| // let leftVariable = receiverVariable.propertyName in |
| // let valueVariable = leftVariable binaryName rhs in |
| // let writeVariable = |
| // receiverVariable.propertyName = valueVariable in |
| // valueVariable |
| // |
| // TODO(johnniwinther): Do we need the `leftVariable` in this case? |
| assert(leftVariable == null); |
| assert(valueVariable != null); |
| |
| VariableDeclaration writeVariable = |
| createVariable(write, const VoidType()); |
| MethodInvocation equalsNull = createEqualsNull( |
| receiverVariable.fileOffset, |
| createVariableGet(receiverVariable), |
| equalsMember); |
| ConditionalExpression condition = new ConditionalExpression( |
| equalsNull, |
| new NullLiteral()..fileOffset = node.readOffset, |
| createLet(valueVariable, |
| createLet(writeVariable, createVariableGet(valueVariable))), |
| resultType); |
| replacement = createLet(receiverVariable, condition); |
| } |
| |
| node.replaceWith(replacement); |
| return new ExpressionInferenceResult(resultType, replacement); |
| } |
| |
| ExpressionInferenceResult visitCompoundSuperIndexSet( |
| CompoundSuperIndexSet node, DartType typeContext) { |
| ObjectAccessTarget readTarget = node.getter != null |
| ? new ObjectAccessTarget.interfaceMember(node.getter) |
| : const ObjectAccessTarget.missing(); |
| |
| DartType readType = inferrer.getReturnType(readTarget, inferrer.thisType); |
| DartType readIndexType = inferrer.getPositionalParameterTypeForTarget( |
| readTarget, inferrer.thisType, 0); |
| |
| ExpressionInferenceResult indexResult = inferrer |
| .inferExpression(node.index, readIndexType, true, isVoidAllowed: true); |
| VariableDeclaration indexVariable = |
| createVariable(node.index, indexResult.inferredType); |
| |
| Expression readIndex = createVariableGet(indexVariable); |
| Expression readIndexReplacement = inferrer.ensureAssignable(readIndexType, |
| indexResult.inferredType, readIndex, readIndex.fileOffset); |
| if (readIndexReplacement != null) { |
| readIndex = readIndexReplacement; |
| } |
| |
| Expression read; |
| if (readTarget.isMissing) { |
| read = inferrer.helper.buildProblem( |
| templateSuperclassHasNoMethod.withArguments('[]'), |
| node.readOffset, |
| '[]'.length); |
| } else { |
| assert(readTarget.isInstanceMember); |
| inferrer.instrumentation?.record(inferrer.uri, node.readOffset, 'target', |
| new InstrumentationValueForMember(node.getter)); |
| read = new SuperMethodInvocation( |
| indexGetName, |
| new Arguments(<Expression>[ |
| readIndex, |
| ]) |
| ..fileOffset = node.readOffset, |
| readTarget.member) |
| ..fileOffset = node.readOffset; |
| } |
| |
| VariableDeclaration leftVariable; |
| Expression left; |
| if (node.forEffect) { |
| left = read; |
| } else if (node.forPostIncDec) { |
| leftVariable = createVariable(read, readType); |
| left = createVariableGet(leftVariable); |
| } else { |
| left = read; |
| } |
| |
| ObjectAccessTarget binaryTarget = inferrer.findInterfaceMember( |
| readType, node.binaryName, node.binaryOffset, |
| includeExtensionMethods: true); |
| |
| MethodContravarianceCheckKind binaryCheckKind = |
| inferrer.preCheckInvocationContravariance(readType, binaryTarget, |
| isThisReceiver: false); |
| |
| DartType binaryType = inferrer.getReturnType(binaryTarget, readType); |
| DartType rhsType = |
| inferrer.getPositionalParameterTypeForTarget(binaryTarget, readType, 0); |
| |
| ExpressionInferenceResult rhsResult = |
| inferrer.inferExpression(node.rhs, rhsType, true, isVoidAllowed: true); |
| inferrer.ensureAssignable( |
| rhsType, rhsResult.inferredType, node.rhs, node.rhs.fileOffset); |
| |
| if (inferrer.isOverloadedArithmeticOperatorAndType( |
| binaryTarget, readType)) { |
| binaryType = inferrer.typeSchemaEnvironment |
| .getTypeOfOverloadedArithmetic(readType, rhsResult.inferredType); |
| } |
| |
| Expression binary; |
| if (binaryTarget.isMissing) { |
| binary = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments(node.binaryName.name, readType), |
| node.binaryOffset, |
| node.binaryName.name.length); |
| } else if (binaryTarget.isExtensionMember) { |
| binary = new StaticInvocation( |
| binaryTarget.member, |
| new Arguments(<Expression>[ |
| left, |
| node.rhs, |
| ], types: binaryTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.binaryOffset) |
| ..fileOffset = node.binaryOffset; |
| } else { |
| binary = new MethodInvocation( |
| left, |
| node.binaryName, |
| new Arguments(<Expression>[ |
| node.rhs, |
| ]) |
| ..fileOffset = node.binaryOffset, |
| binaryTarget.member) |
| ..fileOffset = node.binaryOffset; |
| |
| if (binaryCheckKind == MethodContravarianceCheckKind.checkMethodReturn) { |
| if (inferrer.instrumentation != null) { |
| inferrer.instrumentation.record(inferrer.uri, node.binaryOffset, |
| 'checkReturn', new InstrumentationValueForType(readType)); |
| } |
| binary = new AsExpression(binary, binaryType) |
| ..isTypeError = true |
| ..fileOffset = node.binaryOffset; |
| } |
| } |
| |
| ObjectAccessTarget writeTarget = node.setter != null |
| ? new ObjectAccessTarget.interfaceMember(node.setter) |
| : const ObjectAccessTarget.missing(); |
| |
| DartType writeIndexType = inferrer.getPositionalParameterTypeForTarget( |
| writeTarget, inferrer.thisType, 0); |
| Expression writeIndex = createVariableGet(indexVariable); |
| Expression writeIndexReplacement = inferrer.ensureAssignable(writeIndexType, |
| indexResult.inferredType, writeIndex, writeIndex.fileOffset); |
| if (writeIndexReplacement != null) { |
| writeIndex = writeIndexReplacement; |
| } |
| |
| DartType valueType = |
| inferrer.getIndexSetValueType(writeTarget, inferrer.thisType); |
| Expression binaryReplacement = inferrer.ensureAssignable( |
| valueType, binaryType, binary, node.fileOffset); |
| if (binaryReplacement != null) { |
| binary = binaryReplacement; |
| } |
| |
| VariableDeclaration valueVariable; |
| Expression valueExpression; |
| if (node.forEffect || node.forPostIncDec) { |
| valueExpression = binary; |
| } else { |
| valueVariable = createVariable(binary, binaryType); |
| valueExpression = createVariableGet(valueVariable); |
| } |
| |
| Expression write; |
| |
| if (writeTarget.isMissing) { |
| write = inferrer.helper.buildProblem( |
| templateSuperclassHasNoMethod.withArguments('[]='), |
| node.writeOffset, |
| '[]='.length); |
| } else { |
| assert(writeTarget.isInstanceMember); |
| inferrer.instrumentation?.record(inferrer.uri, node.writeOffset, 'target', |
| new InstrumentationValueForMember(node.setter)); |
| write = new SuperMethodInvocation( |
| indexSetName, |
| new Arguments(<Expression>[writeIndex, valueExpression]) |
| ..fileOffset = node.writeOffset, |
| writeTarget.member) |
| ..fileOffset = node.writeOffset; |
| } |
| |
| Expression replacement; |
| if (node.forEffect) { |
| assert(leftVariable == null); |
| assert(valueVariable == null); |
| // Encode `super[a] += b` as: |
| // |
| // let v1 = a in super.[]=(v1, super.[](v1) + b) |
| // |
| replacement = createLet(indexVariable, write); |
| } else if (node.forPostIncDec) { |
| // Encode `super[a]++` as: |
| // |
| // let v2 = a in |
| // let v3 = v1.[](v2) |
| // let v4 = v1.[]=(v2, v3 + 1) in v3 |
| // |
| assert(leftVariable != null); |
| assert(valueVariable == null); |
| |
| VariableDeclaration writeVariable = |
| createVariable(write, const VoidType()); |
| replacement = createLet( |
| indexVariable, |
| createLet(leftVariable, |
| createLet(writeVariable, createVariableGet(leftVariable)))); |
| } else { |
| // Encode `super[a] += b` as: |
| // |
| // let v1 = o in |
| // let v2 = a in |
| // let v3 = v1.[](v2) + b |
| // let v4 = v1.[]=(v2, c3) in v3 |
| // |
| assert(leftVariable == null); |
| assert(valueVariable != null); |
| |
| VariableDeclaration writeVariable = |
| createVariable(write, const VoidType()); |
| replacement = createLet( |
| indexVariable, |
| createLet(valueVariable, |
| createLet(writeVariable, createVariableGet(valueVariable)))); |
| } |
| |
| node.replaceWith(replacement); |
| return new ExpressionInferenceResult( |
| node.forPostIncDec ? readType : binaryType, replacement); |
| } |
| |
| ExpressionInferenceResult visitCompoundExtensionIndexSet( |
| CompoundExtensionIndexSet node, DartType typeContext) { |
| ExpressionInferenceResult receiverResult = inferrer.inferExpression( |
| node.receiver, const UnknownType(), true, |
| isVoidAllowed: false); |
| |
| List<DartType> extensionTypeArguments = |
| inferrer.computeExtensionTypeArgument(node.extension, |
| node.explicitTypeArguments, receiverResult.inferredType); |
| |
| ObjectAccessTarget readTarget = node.getter != null |
| ? new ExtensionAccessTarget( |
| node.getter, null, ProcedureKind.Operator, extensionTypeArguments) |
| : const ObjectAccessTarget.missing(); |
| |
| DartType receiverType = inferrer.getPositionalParameterTypeForTarget( |
| readTarget, receiverResult.inferredType, 0); |
| |
| inferrer.ensureAssignable(receiverType, receiverResult.inferredType, |
| node.receiver, node.receiver.fileOffset); |
| |
| VariableDeclaration receiverVariable = |
| createVariable(node.receiver, receiverType); |
| |
| DartType readType = inferrer.getReturnType(readTarget, receiverType); |
| DartType readIndexType = inferrer.getPositionalParameterTypeForTarget( |
| readTarget, receiverType, 0); |
| |
| ExpressionInferenceResult indexResult = inferrer |
| .inferExpression(node.index, readIndexType, true, isVoidAllowed: true); |
| VariableDeclaration indexVariable = |
| createVariable(node.index, indexResult.inferredType); |
| |
| Expression readIndex = createVariableGet(indexVariable); |
| Expression readIndexReplacement = inferrer.ensureAssignable(readIndexType, |
| indexResult.inferredType, readIndex, readIndex.fileOffset); |
| if (readIndexReplacement != null) { |
| readIndex = readIndexReplacement; |
| } |
| |
| Expression read; |
| if (readTarget.isMissing) { |
| read = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments( |
| indexGetName.name, receiverType), |
| node.readOffset, |
| noLength); |
| } else { |
| assert(readTarget.isExtensionMember); |
| read = new StaticInvocation( |
| readTarget.member, |
| new Arguments(<Expression>[ |
| createVariableGet(receiverVariable), |
| readIndex, |
| ], types: readTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.readOffset) |
| ..fileOffset = node.readOffset; |
| } |
| |
| VariableDeclaration leftVariable; |
| Expression left; |
| if (node.forEffect) { |
| left = read; |
| } else if (node.forPostIncDec) { |
| leftVariable = createVariable(read, readType); |
| left = createVariableGet(leftVariable); |
| } else { |
| left = read; |
| } |
| |
| ObjectAccessTarget binaryTarget = inferrer.findInterfaceMember( |
| readType, node.binaryName, node.binaryOffset, |
| includeExtensionMethods: true); |
| |
| MethodContravarianceCheckKind binaryCheckKind = |
| inferrer.preCheckInvocationContravariance(readType, binaryTarget, |
| isThisReceiver: false); |
| |
| DartType binaryType = inferrer.getReturnType(binaryTarget, readType); |
| DartType rhsType = |
| inferrer.getPositionalParameterTypeForTarget(binaryTarget, readType, 0); |
| |
| ExpressionInferenceResult rhsResult = |
| inferrer.inferExpression(node.rhs, rhsType, true, isVoidAllowed: true); |
| inferrer.ensureAssignable( |
| rhsType, rhsResult.inferredType, node.rhs, node.rhs.fileOffset); |
| |
| if (inferrer.isOverloadedArithmeticOperatorAndType( |
| binaryTarget, readType)) { |
| binaryType = inferrer.typeSchemaEnvironment |
| .getTypeOfOverloadedArithmetic(readType, rhsResult.inferredType); |
| } |
| |
| Expression binary; |
| if (binaryTarget.isMissing) { |
| binary = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments(node.binaryName.name, readType), |
| node.binaryOffset, |
| node.binaryName.name.length); |
| } else if (binaryTarget.isExtensionMember) { |
| binary = new StaticInvocation( |
| binaryTarget.member, |
| new Arguments(<Expression>[ |
| left, |
| node.rhs, |
| ], types: binaryTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.binaryOffset) |
| ..fileOffset = node.binaryOffset; |
| } else { |
| binary = new MethodInvocation( |
| left, |
| node.binaryName, |
| new Arguments(<Expression>[ |
| node.rhs, |
| ]) |
| ..fileOffset = node.binaryOffset, |
| binaryTarget.member) |
| ..fileOffset = node.binaryOffset; |
| |
| if (binaryCheckKind == MethodContravarianceCheckKind.checkMethodReturn) { |
| if (inferrer.instrumentation != null) { |
| inferrer.instrumentation.record(inferrer.uri, node.binaryOffset, |
| 'checkReturn', new InstrumentationValueForType(readType)); |
| } |
| binary = new AsExpression(binary, binaryType) |
| ..isTypeError = true |
| ..fileOffset = node.binaryOffset; |
| } |
| } |
| |
| ObjectAccessTarget writeTarget = node.setter != null |
| ? new ExtensionAccessTarget( |
| node.setter, null, ProcedureKind.Operator, extensionTypeArguments) |
| : const ObjectAccessTarget.missing(); |
| |
| DartType writeIndexType = inferrer.getPositionalParameterTypeForTarget( |
| writeTarget, receiverType, 0); |
| Expression writeIndex = createVariableGet(indexVariable); |
| Expression writeIndexReplacement = inferrer.ensureAssignable(writeIndexType, |
| indexResult.inferredType, writeIndex, writeIndex.fileOffset); |
| if (writeIndexReplacement != null) { |
| writeIndex = writeIndexReplacement; |
| } |
| |
| DartType valueType = |
| inferrer.getIndexSetValueType(writeTarget, inferrer.thisType); |
| Expression binaryReplacement = inferrer.ensureAssignable( |
| valueType, binaryType, binary, node.fileOffset); |
| if (binaryReplacement != null) { |
| binary = binaryReplacement; |
| } |
| |
| VariableDeclaration valueVariable; |
| Expression valueExpression; |
| if (node.forEffect || node.forPostIncDec) { |
| valueExpression = binary; |
| } else { |
| valueVariable = createVariable(binary, binaryType); |
| valueExpression = createVariableGet(valueVariable); |
| } |
| |
| Expression write; |
| |
| if (writeTarget.isMissing) { |
| write = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments( |
| indexSetName.name, receiverType), |
| node.writeOffset, |
| noLength); |
| } else { |
| assert(writeTarget.isExtensionMember); |
| write = new StaticInvocation( |
| writeTarget.member, |
| new Arguments(<Expression>[ |
| createVariableGet(receiverVariable), |
| writeIndex, |
| valueExpression |
| ], types: writeTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.writeOffset) |
| ..fileOffset = node.writeOffset; |
| } |
| |
| Expression inner; |
| if (node.forEffect) { |
| assert(leftVariable == null); |
| assert(valueVariable == null); |
| // Encode `Extension(o)[a] += b` as: |
| // |
| // let receiverVariable = o in |
| // let indexVariable = a in |
| // receiverVariable.[]=(receiverVariable, o.[](indexVariable) + b) |
| // |
| inner = createLet(indexVariable, write); |
| } else if (node.forPostIncDec) { |
| // Encode `Extension(o)[a]++` as: |
| // |
| // let receiverVariable = o in |
| // let indexVariable = a in |
| // let leftVariable = receiverVariable.[](indexVariable) |
| // let writeVariable = |
| // receiverVariable.[]=(indexVariable, leftVariable + 1) in |
| // leftVariable |
| // |
| assert(leftVariable != null); |
| assert(valueVariable == null); |
| |
| VariableDeclaration writeVariable = |
| createVariable(write, const VoidType()); |
| inner = createLet( |
| indexVariable, |
| createLet(leftVariable, |
| createLet(writeVariable, createVariableGet(leftVariable)))); |
| } else { |
| // Encode `Extension(o)[a] += b` as: |
| // |
| // let receiverVariable = o in |
| // let indexVariable = a in |
| // let valueVariable = receiverVariable.[](indexVariable) + b |
| // let writeVariable = |
| // receiverVariable.[]=(indexVariable, valueVariable) in |
| // valueVariable |
| // |
| assert(leftVariable == null); |
| assert(valueVariable != null); |
| |
| VariableDeclaration writeVariable = |
| createVariable(write, const VoidType()); |
| inner = createLet( |
| indexVariable, |
| createLet(valueVariable, |
| createLet(writeVariable, createVariableGet(valueVariable)))); |
| } |
| |
| Expression replacement = new Let(receiverVariable, inner) |
| ..fileOffset = node.fileOffset; |
| |
| node.replaceWith(replacement); |
| return new ExpressionInferenceResult( |
| node.forPostIncDec ? readType : binaryType, replacement); |
| } |
| |
| @override |
| ExpressionInferenceResult visitNullLiteral( |
| NullLiteral node, DartType typeContext) { |
| return new ExpressionInferenceResult(inferrer.coreTypes.nullType); |
| } |
| |
| @override |
| ExpressionInferenceResult visitLet(Let node, DartType typeContext) { |
| DartType variableType = node.variable.type; |
| inferrer.inferExpression(node.variable.initializer, variableType, true, |
| isVoidAllowed: true); |
| ExpressionInferenceResult result = inferrer |
| .inferExpression(node.body, typeContext, true, isVoidAllowed: true); |
| DartType inferredType = result.inferredType; |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| @override |
| ExpressionInferenceResult visitPropertySet( |
| covariant PropertySetImpl node, DartType typeContext) { |
| DartType receiverType = inferrer |
| .inferExpression(node.receiver, const UnknownType(), true, |
| isVoidAllowed: false) |
| .inferredType; |
| ObjectAccessTarget target = |
| inferrer.findPropertySetMember(receiverType, node); |
| DartType writeContext = inferrer.getSetterType(target, receiverType); |
| ExpressionInferenceResult rhsResult = inferrer.inferExpression( |
| node.value, writeContext ?? const UnknownType(), true, |
| isVoidAllowed: true); |
| DartType rhsType = rhsResult.inferredType; |
| inferrer.ensureAssignable( |
| writeContext, rhsType, node.value, node.fileOffset, |
| isVoidAllowed: writeContext is VoidType); |
| Expression replacement; |
| if (target.isExtensionMember) { |
| if (node.forEffect) { |
| replacement = new StaticInvocation( |
| target.member, |
| new Arguments(<Expression>[node.receiver, node.value], |
| types: target.inferredExtensionTypeArguments) |
| ..fileOffset = node.fileOffset) |
| ..fileOffset = node.fileOffset; |
| } else { |
| Expression receiver; |
| VariableDeclaration receiverVariable; |
| if (node.readOnlyReceiver) { |
| receiver = node.receiver; |
| } else { |
| receiverVariable = createVariable(node.receiver, receiverType); |
| receiver = createVariableGet(receiverVariable); |
| } |
| VariableDeclaration valueVariable = createVariable(node.value, rhsType); |
| VariableDeclaration assignmentVariable = createVariable( |
| new StaticInvocation( |
| target.member, |
| new Arguments( |
| <Expression>[receiver, createVariableGet(valueVariable)], |
| types: target.inferredExtensionTypeArguments) |
| ..fileOffset = node.fileOffset) |
| ..fileOffset = node.fileOffset, |
| const VoidType()); |
| replacement = createLet(valueVariable, |
| createLet(assignmentVariable, createVariableGet(valueVariable))); |
| if (receiverVariable != null) { |
| replacement = createLet(receiverVariable, replacement); |
| } |
| replacement..fileOffset = node.fileOffset; |
| } |
| node.replaceWith(replacement); |
| } |
| return new ExpressionInferenceResult(rhsType, replacement); |
| } |
| |
| ExpressionInferenceResult visitNullAwareIfNullSet( |
| NullAwareIfNullSet node, DartType typeContext) { |
| ExpressionInferenceResult receiverResult = inferrer.inferExpression( |
| node.receiver, const UnknownType(), true, |
| isVoidAllowed: false); |
| DartType receiverType = receiverResult.inferredType; |
| VariableDeclaration receiverVariable = |
| createVariable(node.receiver, receiverType); |
| Expression readReceiver = createVariableGet(receiverVariable); |
| Expression writeReceiver = createVariableGet(receiverVariable); |
| |
| Member receiverEqualsMember = inferrer |
| .findInterfaceMember(receiverType, equalsName, node.receiver.fileOffset) |
| .member; |
| |
| ObjectAccessTarget readTarget = inferrer.findInterfaceMember( |
| receiverType, node.name, node.readOffset, |
| includeExtensionMethods: true); |
| |
| MethodContravarianceCheckKind readCheckKind = |
| inferrer.preCheckInvocationContravariance(receiverType, readTarget, |
| isThisReceiver: node.receiver is ThisExpression); |
| |
| DartType readType = inferrer.getGetterType(readTarget, receiverType); |
| |
| Member readEqualsMember = inferrer |
| .findInterfaceMember(readType, equalsName, node.testOffset) |
| .member; |
| |
| Expression read; |
| if (readTarget.isMissing) { |
| read = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments(node.name.name, receiverType), |
| node.readOffset, |
| node.name.name.length); |
| } else if (readTarget.isExtensionMember) { |
| read = new StaticInvocation( |
| readTarget.member, |
| new Arguments(<Expression>[ |
| readReceiver, |
| ], types: readTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.readOffset) |
| ..fileOffset = node.readOffset; |
| } else { |
| read = new PropertyGet(readReceiver, node.name, readTarget.member) |
| ..fileOffset = node.readOffset; |
| if (readCheckKind == MethodContravarianceCheckKind.checkMethodReturn) { |
| if (inferrer.instrumentation != null) { |
| inferrer.instrumentation.record(inferrer.uri, node.readOffset, |
| 'checkReturn', new InstrumentationValueForType(readType)); |
| } |
| read = new AsExpression(read, readType) |
| ..isTypeError = true |
| ..fileOffset = node.readOffset; |
| } |
| } |
| |
| VariableDeclaration readVariable; |
| if (!node.forEffect) { |
| readVariable = createVariable(read, readType); |
| read = createVariableGet(readVariable); |
| } |
| |
| ObjectAccessTarget writeTarget = inferrer.findInterfaceMember( |
| receiverType, node.name, node.writeOffset, |
| setter: true, includeExtensionMethods: true); |
| |
| DartType valueType = inferrer.getSetterType(writeTarget, receiverType); |
| |
| ExpressionInferenceResult valueResult = inferrer |
| .inferExpression(node.value, valueType, true, isVoidAllowed: true); |
| inferrer.ensureAssignable( |
| valueType, valueResult.inferredType, node.value, node.value.fileOffset); |
| |
| Expression write; |
| |
| if (writeTarget.isMissing) { |
| write = inferrer.helper.buildProblem( |
| templateUndefinedMethod.withArguments(node.name.name, receiverType), |
| node.writeOffset, |
| node.name.name.length); |
| } else if (writeTarget.isExtensionMember) { |
| if (node.forEffect) { |
| write = new StaticInvocation( |
| writeTarget.member, |
| new Arguments(<Expression>[writeReceiver, node.value], |
| types: writeTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.writeOffset) |
| ..fileOffset = node.writeOffset; |
| } else { |
| VariableDeclaration valueVariable = |
| createVariable(node.value, valueResult.inferredType); |
| VariableDeclaration assignmentVariable = createVariable( |
| new StaticInvocation( |
| writeTarget.member, |
| new Arguments(<Expression>[ |
| writeReceiver, |
| createVariableGet(valueVariable) |
| ], types: writeTarget.inferredExtensionTypeArguments) |
| ..fileOffset = node.writeOffset) |
| ..fileOffset = node.writeOffset, |
| const VoidType()); |
| write = createLet(valueVariable, |
| createLet(assignmentVariable, createVariableGet(valueVariable))) |
| ..fileOffset = node.writeOffset; |
| } |
| } else { |
| write = new PropertySet( |
| writeReceiver, node.name, node.value, writeTarget.member) |
| ..fileOffset = node.writeOffset; |
| } |
| |
| DartType inferredType = inferrer.typeSchemaEnvironment |
| .getStandardUpperBound(readType, valueResult.inferredType); |
| |
| Expression replacement; |
| if (node.forEffect) { |
| assert(readVariable == null); |
| // Encode `receiver?.name ??= value` as: |
| // |
| // let receiverVariable = receiver in |
| // receiverVariable == null ? null : |
| // (receiverVariable.name == null ? |
| // receiverVariable.name = value : null) |
| // |
| |
| MethodInvocation receiverEqualsNull = createEqualsNull( |
| receiverVariable.fileOffset, |
| createVariableGet(receiverVariable), |
| receiverEqualsMember); |
| MethodInvocation readEqualsNull = |
| createEqualsNull(node.readOffset, read, readEqualsMember); |
| ConditionalExpression innerCondition = new ConditionalExpression( |
| readEqualsNull, |
| write, |
| new NullLiteral()..fileOffset = node.writeOffset, |
| inferredType); |
| ConditionalExpression outerCondition = new ConditionalExpression( |
| receiverEqualsNull, |
| new NullLiteral()..fileOffset = node.readOffset, |
| innerCondition, |
| inferredType); |
| replacement = createLet(receiverVariable, outerCondition); |
| } else { |
| // Encode `receiver?.name ??= value` as: |
| // |
| // let receiverVariable = receiver in |
| // receiverVariable == null ? null : |
| // (let readVariable = receiverVariable.name in |
| // readVariable == null ? |
| // receiverVariable.name = value : readVariable) |
| // |
| assert(readVariable != null); |
| |
| MethodInvocation receiverEqualsNull = createEqualsNull( |
| receiverVariable.fileOffset, |
| createVariableGet(receiverVariable), |
| receiverEqualsMember); |
| MethodInvocation readEqualsNull = |
| createEqualsNull(receiverVariable.fileOffset, read, readEqualsMember); |
| ConditionalExpression innerCondition = new ConditionalExpression( |
| readEqualsNull, write, createVariableGet(readVariable), inferredType); |
| ConditionalExpression outerCondition = new ConditionalExpression( |
| receiverEqualsNull, |
| new NullLiteral()..fileOffset = node.readOffset, |
| createLet(readVariable, innerCondition), |
| inferredType); |
| replacement = createLet(receiverVariable, outerCondition); |
| } |
| |
| node.replaceWith(replacement); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| @override |
| ExpressionInferenceResult visitPropertyGet( |
| PropertyGet node, DartType typeContext) { |
| return inferrer.inferPropertyGet( |
| node, node.receiver, node.fileOffset, typeContext, node); |
| } |
| |
| @override |
| void visitRedirectingInitializer(RedirectingInitializer node) { |
| List<TypeParameter> classTypeParameters = |
| node.target.enclosingClass.typeParameters; |
| List<DartType> typeArguments = |
| new List<DartType>(classTypeParameters.length); |
| for (int i = 0; i < typeArguments.length; i++) { |
| typeArguments[i] = new TypeParameterType(classTypeParameters[i]); |
| } |
| ArgumentsImpl.setNonInferrableArgumentTypes(node.arguments, typeArguments); |
| inferrer.inferInvocation(null, node.fileOffset, |
| node.target.function.thisFunctionType, node.arguments, |
| returnType: node.target.enclosingClass.thisType, |
| skipTypeArgumentInference: true); |
| ArgumentsImpl.removeNonInferrableArgumentTypes(node.arguments); |
| } |
| |
| @override |
| ExpressionInferenceResult visitRethrow(Rethrow node, DartType typeContext) { |
| return const ExpressionInferenceResult(const BottomType()); |
| } |
| |
| @override |
| void visitReturnStatement(covariant ReturnStatementImpl node) { |
| ClosureContext closureContext = inferrer.closureContext; |
| DartType typeContext = !closureContext.isGenerator |
| ? closureContext.returnOrYieldContext |
| : const UnknownType(); |
| DartType inferredType; |
| if (node.expression != null) { |
| inferredType = inferrer |
| .inferExpression(node.expression, typeContext, true, |
| isVoidAllowed: true) |
| .inferredType; |
| } else { |
| inferredType = inferrer.coreTypes.nullType; |
| } |
| closureContext.handleReturn(inferrer, node, inferredType, node.isArrow); |
| } |
| |
| @override |
| ExpressionInferenceResult visitSetLiteral( |
| SetLiteral node, DartType typeContext) { |
| Class setClass = inferrer.coreTypes.setClass; |
| InterfaceType setType = setClass.thisType; |
| List<DartType> inferredTypes; |
| DartType inferredTypeArgument; |
| List<DartType> formalTypes; |
| List<DartType> actualTypes; |
| bool inferenceNeeded = node.typeArgument is ImplicitTypeArgument; |
| bool typeChecksNeeded = !inferrer.isTopLevel; |
| Map<TreeNode, DartType> inferredSpreadTypes; |
| Map<Expression, DartType> inferredConditionTypes; |
| if (inferenceNeeded || typeChecksNeeded) { |
| formalTypes = []; |
| actualTypes = []; |
| inferredSpreadTypes = new Map<TreeNode, DartType>.identity(); |
| inferredConditionTypes = new Map<Expression, DartType>.identity(); |
| } |
| if (inferenceNeeded) { |
| inferredTypes = [const UnknownType()]; |
| inferrer.typeSchemaEnvironment.inferGenericFunctionOrType(setType, |
| setClass.typeParameters, null, null, typeContext, inferredTypes, |
| isConst: node.isConst); |
| inferredTypeArgument = inferredTypes[0]; |
| } else { |
| inferredTypeArgument = node.typeArgument; |
| } |
| if (inferenceNeeded || typeChecksNeeded) { |
| for (int i = 0; i < node.expressions.length; ++i) { |
| DartType type = inferElement( |
| node.expressions[i], |
| node, |
| inferredTypeArgument, |
| inferredSpreadTypes, |
| inferredConditionTypes, |
| inferenceNeeded, |
| typeChecksNeeded); |
| actualTypes.add(type); |
| if (inferenceNeeded) { |
| formalTypes.add(setType.typeArguments[0]); |
| } |
| } |
| } |
| if (inferenceNeeded) { |
| inferrer.typeSchemaEnvironment.inferGenericFunctionOrType( |
| setType, |
| setClass.typeParameters, |
| formalTypes, |
| actualTypes, |
| typeContext, |
| inferredTypes); |
| inferredTypeArgument = inferredTypes[0]; |
| inferrer.instrumentation?.record( |
| inferrer.uri, |
| node.fileOffset, |
| 'typeArgs', |
| new InstrumentationValueForTypeArgs([inferredTypeArgument])); |
| node.typeArgument = inferredTypeArgument; |
| } |
| if (typeChecksNeeded) { |
| for (int i = 0; i < node.expressions.length; i++) { |
| checkElement(node.expressions[i], node, node.typeArgument, |
| inferredSpreadTypes, inferredConditionTypes); |
| } |
| } |
| DartType inferredType = new InterfaceType(setClass, [inferredTypeArgument]); |
| if (!inferrer.isTopLevel) { |
| SourceLibraryBuilder library = inferrer.library; |
| if (inferenceNeeded) { |
| library.checkBoundsInSetLiteral( |
| node, inferrer.typeSchemaEnvironment, inferrer.helper.uri, |
| inferred: true); |
| } |
| |
| if (!library.loader.target.backendTarget.supportsSetLiterals) { |
| inferrer.helper.transformSetLiterals = true; |
| } |
| } |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| @override |
| ExpressionInferenceResult visitStaticSet( |
| StaticSet node, DartType typeContext) { |
| Member writeMember = node.target; |
| DartType writeContext = writeMember.setterType; |
| TypeInferenceEngine.resolveInferenceNode(writeMember); |
| ExpressionInferenceResult rhsResult = inferrer.inferExpression( |
| node.value, writeContext ?? const UnknownType(), true, |
| isVoidAllowed: true); |
| DartType rhsType = rhsResult.inferredType; |
| inferrer.ensureAssignable( |
| writeContext, rhsType, node.value, node.fileOffset, |
| isVoidAllowed: writeContext is VoidType); |
| return new ExpressionInferenceResult(rhsType); |
| } |
| |
| @override |
| ExpressionInferenceResult visitStaticGet( |
| StaticGet node, DartType typeContext) { |
| Member target = node.target; |
| TypeInferenceEngine.resolveInferenceNode(target); |
| DartType type = target.getterType; |
| if (target is Procedure && target.kind == ProcedureKind.Method) { |
| type = inferrer.instantiateTearOff(type, typeContext, node); |
| } |
| return new ExpressionInferenceResult(type); |
| } |
| |
| @override |
| ExpressionInferenceResult visitStaticInvocation( |
| StaticInvocation node, DartType typeContext) { |
| FunctionType calleeType = node.target != null |
| ? node.target.function.functionType |
| : new FunctionType([], const DynamicType()); |
| bool hadExplicitTypeArguments = |
| getExplicitTypeArguments(node.arguments) != null; |
| DartType inferredType = inferrer.inferInvocation( |
| typeContext, node.fileOffset, calleeType, node.arguments); |
| if (!inferrer.isTopLevel && |
| !hadExplicitTypeArguments && |
| node.target != null) { |
| inferrer.library.checkBoundsInStaticInvocation( |
| node, inferrer.typeSchemaEnvironment, inferrer.helper.uri, |
| inferred: true); |
| } |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| @override |
| ExpressionInferenceResult visitStringConcatenation( |
| StringConcatenation node, DartType typeContext) { |
| if (!inferrer.isTopLevel) { |
| for (Expression expression in node.expressions) { |
| inferrer.inferExpression( |
| expression, const UnknownType(), !inferrer.isTopLevel, |
| isVoidAllowed: false); |
| } |
| } |
| return new ExpressionInferenceResult( |
| inferrer.coreTypes.stringRawType(inferrer.library.nonNullable)); |
| } |
| |
| @override |
| ExpressionInferenceResult visitStringLiteral( |
| StringLiteral node, DartType typeContext) { |
| return new ExpressionInferenceResult( |
| inferrer.coreTypes.stringRawType(inferrer.library.nonNullable)); |
| } |
| |
| @override |
| void visitSuperInitializer(SuperInitializer node) { |
| Substitution substitution = Substitution.fromSupertype( |
| inferrer.classHierarchy.getClassAsInstanceOf( |
| inferrer.thisType.classNode, node.target.enclosingClass)); |
| inferrer.inferInvocation( |
| null, |
| node.fileOffset, |
| substitution.substituteType( |
| node.target.function.thisFunctionType.withoutTypeParameters), |
| node.arguments, |
| returnType: inferrer.thisType, |
| skipTypeArgumentInference: true); |
| } |
| |
| @override |
| ExpressionInferenceResult visitSuperMethodInvocation( |
| SuperMethodInvocation node, DartType typeContext) { |
| if (node.interfaceTarget != null) { |
| inferrer.instrumentation?.record(inferrer.uri, node.fileOffset, 'target', |
| new InstrumentationValueForMember(node.interfaceTarget)); |
| } |
| ExpressionInferenceResult result = inferrer.inferSuperMethodInvocation( |
| node, |
| typeContext, |
| node.interfaceTarget != null |
| ? new ObjectAccessTarget.interfaceMember(node.interfaceTarget) |
| : const ObjectAccessTarget.unresolved()); |
| return new ExpressionInferenceResult(result.inferredType); |
| } |
| |
| @override |
| ExpressionInferenceResult visitSuperPropertyGet( |
| SuperPropertyGet node, DartType typeContext) { |
| if (node.interfaceTarget != null) { |
| inferrer.instrumentation?.record(inferrer.uri, node.fileOffset, 'target', |
| new InstrumentationValueForMember(node.interfaceTarget)); |
| } |
| return inferrer.inferSuperPropertyGet( |
| node, |
| typeContext, |
| node.interfaceTarget != null |
| ? new ObjectAccessTarget.interfaceMember(node.interfaceTarget) |
| : const ObjectAccessTarget.unresolved()); |
| } |
| |
| @override |
| ExpressionInferenceResult visitSuperPropertySet( |
| SuperPropertySet node, DartType typeContext) { |
| DartType receiverType = inferrer.classHierarchy.getTypeAsInstanceOf( |
| inferrer.thisType, inferrer.thisType.classNode.supertype.classNode); |
| |
| ObjectAccessTarget writeTarget = inferrer.findInterfaceMember( |
| receiverType, node.name, node.fileOffset, |
| setter: true, instrumented: true); |
| if (writeTarget.isInstanceMember) { |
| node.interfaceTarget = writeTarget.member; |
| } |
| DartType writeContext = inferrer.getSetterType(writeTarget, receiverType); |
| ExpressionInferenceResult rhsResult = inferrer.inferExpression( |
| node.value, writeContext ?? const UnknownType(), true, |
| isVoidAllowed: true); |
| DartType rhsType = rhsResult.inferredType; |
| inferrer.ensureAssignable( |
| writeContext, rhsType, node.value, node.fileOffset, |
| isVoidAllowed: writeContext is VoidType); |
| |
| return new ExpressionInferenceResult(rhsType); |
| } |
| |
| @override |
| void visitSwitchStatement(SwitchStatement node) { |
| DartType expressionType = inferrer |
| .inferExpression(node.expression, const UnknownType(), true, |
| isVoidAllowed: false) |
| .inferredType; |
| |
| for (SwitchCase switchCase in node.cases) { |
| for (Expression caseExpression in switchCase.expressions) { |
| ExpressionInferenceResult caseExpressionResult = |
| inferrer.inferExpression(caseExpression, expressionType, true, |
| isVoidAllowed: false); |
| if (caseExpressionResult.replacement != null) { |
| caseExpression = caseExpressionResult.replacement; |
| } |
| DartType caseExpressionType = caseExpressionResult.inferredType; |
| |
| // Check whether the expression type is assignable to the case |
| // expression type. |
| if (!inferrer.isAssignable(expressionType, caseExpressionType)) { |
| inferrer.helper.addProblem( |
| templateSwitchExpressionNotAssignable.withArguments( |
| expressionType, caseExpressionType), |
| caseExpression.fileOffset, |
| noLength, |
| context: [ |
| messageSwitchExpressionNotAssignableCause.withLocation( |
| inferrer.uri, node.expression.fileOffset, noLength) |
| ]); |
| } |
| } |
| inferrer.inferStatement(switchCase.body); |
| } |
| } |
| |
| @override |
| ExpressionInferenceResult visitSymbolLiteral( |
| SymbolLiteral node, DartType typeContext) { |
| DartType inferredType = |
| inferrer.coreTypes.symbolRawType(inferrer.library.nonNullable); |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| ExpressionInferenceResult visitThisExpression( |
| ThisExpression node, DartType typeContext) { |
| return new ExpressionInferenceResult(inferrer.thisType); |
| } |
| |
| @override |
| ExpressionInferenceResult visitThrow(Throw node, DartType typeContext) { |
| inferrer.inferExpression( |
| node.expression, const UnknownType(), !inferrer.isTopLevel, |
| isVoidAllowed: false); |
| return const ExpressionInferenceResult(const BottomType()); |
| } |
| |
| void visitCatch(Catch node) { |
| inferrer.inferStatement(node.body); |
| } |
| |
| @override |
| void visitTryCatch(TryCatch node) { |
| inferrer.inferStatement(node.body); |
| for (Catch catch_ in node.catches) { |
| visitCatch(catch_); |
| } |
| } |
| |
| @override |
| void visitTryFinally(TryFinally node) { |
| inferrer.inferStatement(node.body); |
| inferrer.inferStatement(node.finalizer); |
| } |
| |
| @override |
| ExpressionInferenceResult visitTypeLiteral( |
| TypeLiteral node, DartType typeContext) { |
| DartType inferredType = |
| inferrer.coreTypes.typeRawType(inferrer.library.nonNullable); |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| @override |
| ExpressionInferenceResult visitVariableSet( |
| VariableSet node, DartType typeContext) { |
| DartType writeContext = node.variable.type; |
| ExpressionInferenceResult rhsResult = inferrer.inferExpression( |
| node.value, writeContext ?? const UnknownType(), true, |
| isVoidAllowed: true); |
| DartType rhsType = rhsResult.inferredType; |
| inferrer.ensureAssignable( |
| writeContext, rhsType, node.value, node.fileOffset, |
| isVoidAllowed: writeContext is VoidType); |
| return new ExpressionInferenceResult(rhsType); |
| } |
| |
| @override |
| void visitVariableDeclaration(covariant VariableDeclarationImpl node) { |
| DartType declaredType = |
| node._implicitlyTyped ? const UnknownType() : node.type; |
| DartType inferredType; |
| DartType initializerType; |
| if (node.initializer != null) { |
| ExpressionInferenceResult initializerResult = inferrer.inferExpression( |
| node.initializer, |
| declaredType, |
| !inferrer.isTopLevel || node._implicitlyTyped, |
| isVoidAllowed: true); |
| initializerType = initializerResult.inferredType; |
| inferredType = inferrer.inferDeclarationType(initializerType); |
| } else { |
| inferredType = const DynamicType(); |
| } |
| if (node._implicitlyTyped) { |
| inferrer.instrumentation?.record(inferrer.uri, node.fileOffset, 'type', |
| new InstrumentationValueForType(inferredType)); |
| node.type = inferredType; |
| } |
| if (node.initializer != null) { |
| Expression replacedInitializer = inferrer.ensureAssignable( |
| node.type, initializerType, node.initializer, node.fileOffset, |
| isVoidAllowed: node.type is VoidType); |
| if (replacedInitializer != null) { |
| node.initializer = replacedInitializer; |
| } |
| } |
| if (!inferrer.isTopLevel) { |
| SourceLibraryBuilder library = inferrer.library; |
| if (node._implicitlyTyped) { |
| library.checkBoundsInVariableDeclaration( |
| node, inferrer.typeSchemaEnvironment, inferrer.helper.uri, |
| inferred: true); |
| } |
| } |
| } |
| |
| @override |
| ExpressionInferenceResult visitVariableGet( |
| covariant VariableGetImpl node, DartType typeContext) { |
| VariableDeclarationImpl variable = node.variable; |
| bool mutatedInClosure = variable._mutatedInClosure; |
| DartType declaredOrInferredType = variable.type; |
| |
| DartType promotedType = inferrer.typePromoter |
| .computePromotedType(node._fact, node._scope, mutatedInClosure); |
| if (promotedType != null) { |
| inferrer.instrumentation?.record(inferrer.uri, node.fileOffset, |
| 'promotedType', new InstrumentationValueForType(promotedType)); |
| } |
| node.promotedType = promotedType; |
| DartType type = promotedType ?? declaredOrInferredType; |
| if (variable._isLocalFunction) { |
| type = inferrer.instantiateTearOff(type, typeContext, node); |
| } |
| return new ExpressionInferenceResult(type); |
| } |
| |
| @override |
| void visitWhileStatement(WhileStatement node) { |
| InterfaceType expectedType = |
| inferrer.coreTypes.boolRawType(inferrer.library.nonNullable); |
| DartType conditionType = inferrer |
| .inferExpression(node.condition, expectedType, !inferrer.isTopLevel, |
| isVoidAllowed: false) |
| .inferredType; |
| inferrer.ensureAssignable( |
| expectedType, conditionType, node.condition, node.condition.fileOffset); |
| inferrer.inferStatement(node.body); |
| } |
| |
| @override |
| void visitYieldStatement(YieldStatement node) { |
| ClosureContext closureContext = inferrer.closureContext; |
| DartType inferredType; |
| if (closureContext.isGenerator) { |
| DartType typeContext = closureContext.returnOrYieldContext; |
| if (node.isYieldStar && typeContext != null) { |
| typeContext = inferrer.wrapType( |
| typeContext, |
| closureContext.isAsync |
| ? inferrer.coreTypes.streamClass |
| : inferrer.coreTypes.iterableClass); |
| } |
| inferredType = inferrer |
| .inferExpression(node.expression, typeContext, true, |
| isVoidAllowed: true) |
| .inferredType; |
| } else { |
| inferredType = inferrer |
| .inferExpression(node.expression, const UnknownType(), true, |
| isVoidAllowed: true) |
| .inferredType; |
| } |
| closureContext.handleYield(inferrer, node.isYieldStar, inferredType, |
| node.expression, node.fileOffset); |
| } |
| |
| @override |
| ExpressionInferenceResult visitLoadLibrary( |
| covariant LoadLibraryImpl node, DartType typeContext) { |
| DartType inferredType = |
| inferrer.typeSchemaEnvironment.futureType(const DynamicType()); |
| if (node.arguments != null) { |
| FunctionType calleeType = new FunctionType([], inferredType); |
| inferrer.inferInvocation( |
| typeContext, node.fileOffset, calleeType, node.arguments); |
| } |
| return new ExpressionInferenceResult(inferredType); |
| } |
| |
| ExpressionInferenceResult visitLoadLibraryTearOff( |
| LoadLibraryTearOff node, DartType typeContext) { |
| DartType inferredType = new FunctionType( |
| [], inferrer.typeSchemaEnvironment.futureType(const DynamicType())); |
| Expression replacement = node.replace(); |
| return new ExpressionInferenceResult(inferredType, replacement); |
| } |
| |
| @override |
| ExpressionInferenceResult visitCheckLibraryIsLoaded( |
| CheckLibraryIsLoaded node, DartType typeContext) { |
| // TODO(dmitryas): Figure out the suitable nullability for that. |
| return new ExpressionInferenceResult( |
| inferrer.coreTypes.objectRawType(inferrer.library.nullable)); |
| } |
| } |