| // Copyright (c) 2016, 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. |
| |
| library kernel.type_checker; |
| |
| import 'ast.dart'; |
| import 'class_hierarchy.dart'; |
| import 'core_types.dart'; |
| import 'type_algebra.dart'; |
| import 'type_environment.dart'; |
| |
| /// Performs type checking on the kernel IR. |
| /// |
| /// A concrete subclass of [TypeChecker] must implement [checkAssignable] and |
| /// [fail] in order to deal with subtyping requirements and error handling. |
| abstract class TypeChecker { |
| final CoreTypes coreTypes; |
| final ClassHierarchy hierarchy; |
| final bool ignoreSdk; |
| final TypeEnvironment environment; |
| Library? currentLibrary; |
| InterfaceType? currentThisType; |
| |
| TypeChecker(this.coreTypes, this.hierarchy, {this.ignoreSdk: true}) |
| : environment = new TypeEnvironment(coreTypes, hierarchy); |
| |
| void checkComponent(Component component) { |
| for (Library library in component.libraries) { |
| if (ignoreSdk && library.importUri.scheme == 'dart') continue; |
| for (Class class_ in library.classes) { |
| hierarchy.forEachOverridePair(class_, |
| (Member ownMember, Member superMember, bool isSetter) { |
| checkOverride(class_, ownMember, superMember, isSetter); |
| }); |
| } |
| } |
| TypeCheckingVisitor visitor = |
| new TypeCheckingVisitor(this, environment, hierarchy); |
| for (Library library in component.libraries) { |
| currentLibrary = library; |
| if (ignoreSdk && library.importUri.scheme == 'dart') continue; |
| for (Class class_ in library.classes) { |
| currentThisType = coreTypes.thisInterfaceType( |
| class_, class_.enclosingLibrary.nonNullable); |
| for (Field field in class_.fields) { |
| visitor.visitField(field); |
| } |
| for (Constructor constructor in class_.constructors) { |
| visitor.visitConstructor(constructor); |
| } |
| for (Procedure procedure in class_.procedures) { |
| visitor.visitProcedure(procedure); |
| } |
| } |
| currentThisType = null; |
| for (Procedure procedure in library.procedures) { |
| visitor.visitProcedure(procedure); |
| } |
| for (Field field in library.fields) { |
| visitor.visitField(field); |
| } |
| currentLibrary = null; |
| } |
| } |
| |
| DartType getterType(Class host, Member member) { |
| Supertype hostType = |
| hierarchy.getClassAsInstanceOf(host, member.enclosingClass!)!; |
| Substitution substitution = Substitution.fromSupertype(hostType); |
| return substitution.substituteType(member.getterType); |
| } |
| |
| DartType setterType(Class host, Member member) { |
| Supertype hostType = |
| hierarchy.getClassAsInstanceOf(host, member.enclosingClass!)!; |
| Substitution substitution = Substitution.fromSupertype(hostType); |
| return substitution.substituteType(member.setterType, contravariant: true); |
| } |
| |
| /// Check that [ownMember] of [host] can override [superMember]. |
| void checkOverride( |
| Class host, Member ownMember, Member superMember, bool isSetter) { |
| if (isSetter) { |
| checkAssignable(ownMember, setterType(host, superMember), |
| setterType(host, ownMember)); |
| } else { |
| checkAssignable(ownMember, getterType(host, ownMember), |
| getterType(host, superMember)); |
| } |
| } |
| |
| /// Check that [from] is a subtype of [to]. |
| /// |
| /// [where] is an AST node indicating roughly where the check is required. |
| void checkAssignable(TreeNode where, DartType from, DartType to); |
| |
| /// Checks that [expression], which has type [from], can be assigned to [to]. |
| /// |
| /// Should return a downcast if necessary, or [expression] if no cast is |
| /// needed. |
| Expression checkAndDowncastExpression( |
| Expression expression, DartType from, DartType to) { |
| checkAssignable(expression, from, to); |
| return expression; |
| } |
| |
| /// Check unresolved invocation (one that has no interfaceTarget) |
| /// and report an error if necessary. |
| void checkUnresolvedInvocation(DartType receiver, TreeNode where) { |
| // By default we ignore unresolved method invocations. |
| } |
| |
| /// Indicates that type checking failed. |
| void fail(TreeNode where, String message); |
| } |
| |
| class TypeCheckingVisitor |
| implements |
| ExpressionVisitor<DartType>, |
| StatementVisitor<void>, |
| MemberVisitor<void>, |
| InitializerVisitor<void> { |
| final TypeChecker checker; |
| final TypeEnvironment environment; |
| final ClassHierarchy hierarchy; |
| |
| CoreTypes get coreTypes => environment.coreTypes; |
| Library? get currentLibrary => checker.currentLibrary; |
| Class? get currentClass => checker.currentThisType?.classNode; |
| InterfaceType? get currentThisType => checker.currentThisType; |
| |
| DartType? currentReturnType; |
| DartType? currentYieldType; |
| AsyncMarker currentAsyncMarker = AsyncMarker.Sync; |
| |
| TypeCheckingVisitor(this.checker, this.environment, this.hierarchy); |
| |
| void checkAssignable(TreeNode where, DartType from, DartType to) { |
| checker.checkAssignable(where, from, to); |
| } |
| |
| void checkUnresolvedInvocation(DartType receiver, TreeNode where) { |
| checker.checkUnresolvedInvocation(receiver, where); |
| } |
| |
| Expression checkAndDowncastExpression(Expression from, DartType to) { |
| TreeNode? parent = from.parent; |
| DartType type = visitExpression(from); |
| Expression result = checker.checkAndDowncastExpression(from, type, to); |
| result.parent = parent; |
| return result; |
| } |
| |
| void checkExpressionNoDowncast(Expression expression, DartType to) { |
| checkAssignable(expression, visitExpression(expression), to); |
| } |
| |
| void fail(TreeNode node, String message) { |
| checker.fail(node, message); |
| } |
| |
| DartType visitExpression(Expression node) => node.accept(this); |
| |
| void visitStatement(Statement node) { |
| node.accept(this); |
| } |
| |
| void visitInitializer(Initializer node) { |
| node.accept(this); |
| } |
| |
| @override |
| TreeNode defaultMember(Member node) => throw 'Unused'; |
| |
| @override |
| DartType defaultBasicLiteral(BasicLiteral node) { |
| return defaultExpression(node); |
| } |
| |
| @override |
| DartType defaultExpression(Expression node) { |
| throw 'Unexpected expression ${node.runtimeType}'; |
| } |
| |
| @override |
| TreeNode defaultStatement(Statement node) { |
| throw 'Unexpected statement ${node.runtimeType}'; |
| } |
| |
| @override |
| TreeNode defaultInitializer(Initializer node) { |
| throw 'Unexpected initializer ${node.runtimeType}'; |
| } |
| |
| @override |
| void visitField(Field node) { |
| if (node.initializer != null) { |
| node.initializer = |
| checkAndDowncastExpression(node.initializer!, node.type); |
| } |
| } |
| |
| @override |
| void visitConstructor(Constructor node) { |
| currentReturnType = null; |
| currentYieldType = null; |
| node.initializers.forEach(visitInitializer); |
| handleFunctionNode(node.function); |
| } |
| |
| @override |
| void visitProcedure(Procedure node) { |
| currentReturnType = _getInternalReturnType(node.function); |
| currentYieldType = _getYieldType(node.function); |
| handleFunctionNode(node.function); |
| } |
| |
| @override |
| void visitRedirectingFactory(RedirectingFactory node) { |
| currentReturnType = null; |
| currentYieldType = null; |
| } |
| |
| void handleFunctionNode(FunctionNode node) { |
| AsyncMarker oldAsyncMarker = currentAsyncMarker; |
| currentAsyncMarker = node.asyncMarker; |
| node.positionalParameters |
| .skip(node.requiredParameterCount) |
| .forEach(handleOptionalParameter); |
| node.namedParameters.forEach(handleOptionalParameter); |
| if (node.body != null) { |
| visitStatement(node.body!); |
| } |
| currentAsyncMarker = oldAsyncMarker; |
| } |
| |
| void handleNestedFunctionNode(FunctionNode node) { |
| DartType? oldReturn = currentReturnType; |
| DartType? oldYield = currentYieldType; |
| currentReturnType = _getInternalReturnType(node); |
| currentYieldType = _getYieldType(node); |
| handleFunctionNode(node); |
| currentReturnType = oldReturn; |
| currentYieldType = oldYield; |
| } |
| |
| void handleOptionalParameter(VariableDeclaration parameter) { |
| if (parameter.initializer != null) { |
| // Default parameter values cannot be downcast. |
| checkExpressionNoDowncast(parameter.initializer!, parameter.type); |
| } |
| } |
| |
| Substitution getReceiverType( |
| TreeNode access, Expression receiver, Member member) { |
| DartType type = visitExpression(receiver); |
| Class superclass = member.enclosingClass!; |
| if (superclass.supertype == null) { |
| return Substitution.empty; // Members on Object are always accessible. |
| } |
| while (type is TypeParameterType) { |
| type = type.bound; |
| } |
| if (type is NeverType || type is NullType || type is InvalidType) { |
| // The bottom type is a subtype of all types, so it should be allowed. |
| return Substitution.bottomForClass(superclass); |
| } |
| if (type is InterfaceType) { |
| // The receiver type should implement the interface declaring the member. |
| List<DartType>? upcastTypeArguments = |
| hierarchy.getTypeArgumentsAsInstanceOf(type, superclass); |
| if (upcastTypeArguments != null) { |
| return Substitution.fromPairs( |
| superclass.typeParameters, upcastTypeArguments); |
| } |
| } |
| if (type is FunctionType && superclass == coreTypes.functionClass) { |
| assert(type.typeParameters.isEmpty); |
| return Substitution.empty; |
| } |
| // Note that we do not allow 'dynamic' here. Dynamic calls should not |
| // have a declared interface target. |
| fail(access, '$member is not accessible on a receiver of type $type'); |
| return Substitution.bottomForClass(superclass); // Continue type checking. |
| } |
| |
| Substitution getSuperReceiverType(Member member) { |
| return Substitution.fromSupertype( |
| hierarchy.getClassAsInstanceOf(currentClass!, member.enclosingClass!)!); |
| } |
| |
| DartType handleCall(Arguments arguments, DartType functionType, |
| {Substitution receiver: Substitution.empty, |
| List<TypeParameter>? typeParameters}) { |
| if (functionType is FunctionType) { |
| typeParameters ??= functionType.typeParameters; |
| if (arguments.positional.length < functionType.requiredParameterCount) { |
| fail(arguments, 'Too few positional arguments'); |
| return NeverType.fromNullability(currentLibrary!.nonNullable); |
| } |
| if (arguments.positional.length > |
| functionType.positionalParameters.length) { |
| fail(arguments, 'Too many positional arguments'); |
| return NeverType.fromNullability(currentLibrary!.nonNullable); |
| } |
| List<DartType> typeArguments = arguments.types; |
| if (typeArguments.length != typeParameters.length) { |
| fail(arguments, 'Wrong number of type arguments'); |
| return NeverType.fromNullability(currentLibrary!.nonNullable); |
| } |
| Substitution substitution = _instantiateFunction( |
| typeParameters, typeArguments, arguments, |
| receiverSubstitution: receiver); |
| for (int i = 0; i < arguments.positional.length; ++i) { |
| DartType expectedType = substitution.substituteType( |
| functionType.positionalParameters[i], |
| contravariant: true); |
| arguments.positional[i] = |
| checkAndDowncastExpression(arguments.positional[i], expectedType); |
| } |
| for (int i = 0; i < arguments.named.length; ++i) { |
| NamedExpression argument = arguments.named[i]; |
| bool found = false; |
| for (int j = 0; j < functionType.namedParameters.length; ++j) { |
| if (argument.name == functionType.namedParameters[j].name) { |
| DartType expectedType = substitution.substituteType( |
| functionType.namedParameters[j].type, |
| contravariant: true); |
| argument.value = |
| checkAndDowncastExpression(argument.value, expectedType); |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| fail(argument.value, 'Unexpected named parameter: ${argument.name}'); |
| return NeverType.fromNullability(currentLibrary!.nonNullable); |
| } |
| } |
| return substitution.substituteType(functionType.returnType); |
| } else { |
| // Note: attempting to resolve .call() on [functionType] could lead to an |
| // infinite regress, so just assume `dynamic`. |
| return const DynamicType(); |
| } |
| } |
| |
| DartType? _getInternalReturnType(FunctionNode function) { |
| switch (function.asyncMarker) { |
| case AsyncMarker.Sync: |
| return function.returnType; |
| |
| case AsyncMarker.Async: |
| Class container = coreTypes.futureClass; |
| DartType returnType = function.returnType; |
| if (returnType is InterfaceType && returnType.classNode == container) { |
| return returnType.typeArguments.single; |
| } |
| return const DynamicType(); |
| |
| case AsyncMarker.SyncStar: |
| case AsyncMarker.AsyncStar: |
| return null; |
| |
| case AsyncMarker.SyncYielding: |
| // The SyncStar transform wraps the original function body twice, |
| // where the inner most function returns bool. |
| TreeNode? parent = function.parent; |
| while (parent is! FunctionNode) { |
| parent = parent!.parent; |
| } |
| FunctionNode enclosingFunction = parent; |
| if (enclosingFunction.dartAsyncMarker == AsyncMarker.Sync) { |
| parent = enclosingFunction.parent; |
| while (parent is! FunctionNode) { |
| parent = parent!.parent; |
| } |
| enclosingFunction = parent; |
| if (enclosingFunction.dartAsyncMarker == AsyncMarker.SyncStar) { |
| return coreTypes.boolLegacyRawType; |
| } |
| } |
| return null; |
| |
| default: |
| throw 'Unexpected async marker: ${function.asyncMarker}'; |
| } |
| } |
| |
| DartType? _getYieldType(FunctionNode function) { |
| switch (function.asyncMarker) { |
| case AsyncMarker.Sync: |
| case AsyncMarker.Async: |
| return null; |
| |
| case AsyncMarker.SyncStar: |
| case AsyncMarker.AsyncStar: |
| Class container = function.asyncMarker == AsyncMarker.SyncStar |
| ? coreTypes.iterableClass |
| : coreTypes.streamClass; |
| DartType returnType = function.returnType; |
| if (returnType is InterfaceType && returnType.classNode == container) { |
| return returnType.typeArguments.single; |
| } |
| return const DynamicType(); |
| |
| case AsyncMarker.SyncYielding: |
| return function.returnType; |
| |
| default: |
| throw 'Unexpected async marker: ${function.asyncMarker}'; |
| } |
| } |
| |
| Substitution _instantiateFunction(List<TypeParameter> typeParameters, |
| List<DartType> typeArguments, TreeNode where, |
| {Substitution? receiverSubstitution}) { |
| Substitution instantiation = |
| Substitution.fromPairs(typeParameters, typeArguments); |
| Substitution substitution = receiverSubstitution == null |
| ? instantiation |
| : Substitution.combine(receiverSubstitution, instantiation); |
| for (int i = 0; i < typeParameters.length; ++i) { |
| DartType argument = typeArguments[i]; |
| DartType bound = substitution.substituteType(typeParameters[i].bound); |
| checkAssignable(where, argument, bound); |
| } |
| return substitution; |
| } |
| |
| @override |
| DartType visitAsExpression(AsExpression node) { |
| visitExpression(node.operand); |
| return node.type; |
| } |
| |
| @override |
| DartType visitAwaitExpression(AwaitExpression node) { |
| return environment.flatten(visitExpression(node.operand)); |
| } |
| |
| @override |
| DartType visitBoolLiteral(BoolLiteral node) { |
| return environment.coreTypes.boolLegacyRawType; |
| } |
| |
| @override |
| DartType visitConditionalExpression(ConditionalExpression node) { |
| node.condition = checkAndDowncastExpression( |
| node.condition, environment.coreTypes.boolLegacyRawType); |
| node.then = checkAndDowncastExpression(node.then, node.staticType); |
| node.otherwise = |
| checkAndDowncastExpression(node.otherwise, node.staticType); |
| return node.staticType; |
| } |
| |
| @override |
| DartType visitConstructorInvocation(ConstructorInvocation node) { |
| Constructor target = node.target; |
| Arguments arguments = node.arguments; |
| Class class_ = target.enclosingClass; |
| handleCall( |
| arguments, |
| target.function |
| .computeThisFunctionType(class_.enclosingLibrary.nonNullable), |
| typeParameters: class_.typeParameters); |
| return new InterfaceType( |
| target.enclosingClass, currentLibrary!.nonNullable, arguments.types); |
| } |
| |
| @override |
| DartType visitDoubleLiteral(DoubleLiteral node) { |
| return environment.coreTypes.doubleLegacyRawType; |
| } |
| |
| @override |
| DartType visitFunctionExpression(FunctionExpression node) { |
| handleNestedFunctionNode(node.function); |
| return node.function.computeThisFunctionType(currentLibrary!.nonNullable); |
| } |
| |
| @override |
| DartType visitIntLiteral(IntLiteral node) { |
| return environment.coreTypes.intLegacyRawType; |
| } |
| |
| @override |
| DartType visitInvalidExpression(InvalidExpression node) { |
| // Don't type check `node.expression`. |
| return const InvalidType(); |
| } |
| |
| @override |
| DartType visitIsExpression(IsExpression node) { |
| visitExpression(node.operand); |
| return environment.coreTypes.boolLegacyRawType; |
| } |
| |
| @override |
| DartType visitLet(Let node) { |
| DartType value = visitExpression(node.variable.initializer!); |
| if (node.variable.type is DynamicType) { |
| node.variable.type = value; |
| } |
| return visitExpression(node.body); |
| } |
| |
| @override |
| DartType visitBlockExpression(BlockExpression node) { |
| visitStatement(node.body); |
| return visitExpression(node.value); |
| } |
| |
| @override |
| DartType visitInstantiation(Instantiation node) { |
| DartType type = visitExpression(node.expression); |
| if (type is InvalidType || type is NeverType) { |
| return type; |
| } |
| if (type is! FunctionType) { |
| fail(node, 'Not a function type: $type'); |
| return NeverType.fromNullability(currentLibrary!.nonNullable); |
| } |
| FunctionType functionType = type; |
| if (functionType.typeParameters.length != node.typeArguments.length) { |
| fail(node, 'Wrong number of type arguments'); |
| return NeverType.fromNullability(currentLibrary!.nonNullable); |
| } |
| return _instantiateFunction( |
| functionType.typeParameters, node.typeArguments, node) |
| .substituteType(functionType.withoutTypeParameters); |
| } |
| |
| @override |
| DartType visitConstructorTearOff(ConstructorTearOff node) { |
| return node.function.computeFunctionType(Nullability.nonNullable); |
| } |
| |
| @override |
| DartType visitRedirectingFactoryTearOff(RedirectingFactoryTearOff node) { |
| return node.function.computeFunctionType(Nullability.nonNullable); |
| } |
| |
| @override |
| DartType visitTypedefTearOff(TypedefTearOff node) { |
| DartType type = visitExpression(node.expression); |
| if (type is InvalidType || type is NeverType) { |
| return type; |
| } |
| if (type is! FunctionType) { |
| fail(node, 'Not a function type: $type'); |
| return NeverType.fromNullability(currentLibrary!.nonNullable); |
| } |
| FunctionType functionType = type; |
| if (functionType.typeParameters.length != node.typeArguments.length) { |
| fail(node, 'Wrong number of type arguments'); |
| return NeverType.fromNullability(currentLibrary!.nonNullable); |
| } |
| FreshTypeParameters freshTypeParameters = |
| getFreshTypeParameters(node.typeParameters); |
| FunctionType result = freshTypeParameters.substitute(_instantiateFunction( |
| functionType.typeParameters, node.typeArguments, node) |
| .substituteType(functionType.withoutTypeParameters)) as FunctionType; |
| return new FunctionType(result.positionalParameters, result.returnType, |
| result.declaredNullability, |
| namedParameters: result.namedParameters, |
| typeParameters: freshTypeParameters.freshTypeParameters, |
| requiredParameterCount: result.requiredParameterCount, |
| typedefType: null); |
| } |
| |
| @override |
| DartType visitListLiteral(ListLiteral node) { |
| for (int i = 0; i < node.expressions.length; ++i) { |
| node.expressions[i] = |
| checkAndDowncastExpression(node.expressions[i], node.typeArgument); |
| } |
| return environment.listType(node.typeArgument, currentLibrary!.nonNullable); |
| } |
| |
| @override |
| DartType visitSetLiteral(SetLiteral node) { |
| for (int i = 0; i < node.expressions.length; ++i) { |
| node.expressions[i] = |
| checkAndDowncastExpression(node.expressions[i], node.typeArgument); |
| } |
| return environment.setType(node.typeArgument, currentLibrary!.nonNullable); |
| } |
| |
| @override |
| DartType visitLogicalExpression(LogicalExpression node) { |
| node.left = checkAndDowncastExpression( |
| node.left, environment.coreTypes.boolLegacyRawType); |
| node.right = checkAndDowncastExpression( |
| node.right, environment.coreTypes.boolLegacyRawType); |
| return environment.coreTypes.boolLegacyRawType; |
| } |
| |
| @override |
| DartType visitMapLiteral(MapLiteral node) { |
| for (MapLiteralEntry entry in node.entries) { |
| entry.key = checkAndDowncastExpression(entry.key, node.keyType); |
| entry.value = checkAndDowncastExpression(entry.value, node.valueType); |
| } |
| return environment.mapType( |
| node.keyType, node.valueType, currentLibrary!.nonNullable); |
| } |
| |
| DartType handleDynamicCall(DartType receiver, Arguments arguments) { |
| arguments.positional.forEach(visitExpression); |
| arguments.named.forEach((NamedExpression n) => visitExpression(n.value)); |
| return const DynamicType(); |
| } |
| |
| DartType handleFunctionCall( |
| TreeNode access, FunctionType function, Arguments arguments) { |
| if (function.requiredParameterCount > arguments.positional.length) { |
| fail(access, 'Too few positional arguments'); |
| return NeverType.fromNullability(currentLibrary!.nonNullable); |
| } |
| if (function.positionalParameters.length < arguments.positional.length) { |
| fail(access, 'Too many positional arguments'); |
| return NeverType.fromNullability(currentLibrary!.nonNullable); |
| } |
| if (function.typeParameters.length != arguments.types.length) { |
| fail(access, 'Wrong number of type arguments'); |
| return NeverType.fromNullability(currentLibrary!.nonNullable); |
| } |
| Substitution instantiation = |
| Substitution.fromPairs(function.typeParameters, arguments.types); |
| for (int i = 0; i < arguments.positional.length; ++i) { |
| DartType expectedType = instantiation.substituteType( |
| function.positionalParameters[i], |
| contravariant: true); |
| arguments.positional[i] = |
| checkAndDowncastExpression(arguments.positional[i], expectedType); |
| } |
| for (int i = 0; i < arguments.named.length; ++i) { |
| NamedExpression argument = arguments.named[i]; |
| DartType? parameterType = function.getNamedParameter(argument.name); |
| if (parameterType != null) { |
| DartType expectedType = |
| instantiation.substituteType(parameterType, contravariant: true); |
| argument.value = |
| checkAndDowncastExpression(argument.value, expectedType); |
| } else { |
| fail(argument.value, 'Unexpected named parameter: ${argument.name}'); |
| return NeverType.fromNullability(currentLibrary!.nonNullable); |
| } |
| } |
| return instantiation.substituteType(function.returnType); |
| } |
| |
| @override |
| DartType visitNot(Not node) { |
| visitExpression(node.operand); |
| return environment.coreTypes.boolLegacyRawType; |
| } |
| |
| @override |
| DartType visitNullCheck(NullCheck node) { |
| // TODO(johnniwinther): Return `NonNull(visitExpression(types))`. |
| return visitExpression(node.operand); |
| } |
| |
| @override |
| DartType visitNullLiteral(NullLiteral node) { |
| return const NullType(); |
| } |
| |
| @override |
| DartType visitRethrow(Rethrow node) { |
| return NeverType.fromNullability(currentLibrary!.nonNullable); |
| } |
| |
| @override |
| DartType visitStaticGet(StaticGet node) { |
| return node.target.getterType; |
| } |
| |
| @override |
| DartType visitStaticInvocation(StaticInvocation node) { |
| return handleCall(node.arguments, node.target.getterType); |
| } |
| |
| @override |
| DartType visitStaticSet(StaticSet node) { |
| DartType value = visitExpression(node.value); |
| checkAssignable(node.value, value, node.target.setterType); |
| return value; |
| } |
| |
| @override |
| DartType visitStringConcatenation(StringConcatenation node) { |
| node.expressions.forEach(visitExpression); |
| return environment.coreTypes.stringLegacyRawType; |
| } |
| |
| @override |
| DartType visitListConcatenation(ListConcatenation node) { |
| DartType type = environment.iterableType( |
| node.typeArgument, currentLibrary!.nonNullable); |
| for (Expression part in node.lists) { |
| DartType partType = visitExpression(part); |
| checkAssignable(node, type, partType); |
| } |
| return type; |
| } |
| |
| @override |
| DartType visitSetConcatenation(SetConcatenation node) { |
| DartType type = environment.iterableType( |
| node.typeArgument, currentLibrary!.nonNullable); |
| for (Expression part in node.sets) { |
| DartType partType = visitExpression(part); |
| checkAssignable(node, type, partType); |
| } |
| return type; |
| } |
| |
| @override |
| DartType visitMapConcatenation(MapConcatenation node) { |
| DartType type = environment.mapType( |
| node.keyType, node.valueType, currentLibrary!.nonNullable); |
| for (Expression part in node.maps) { |
| DartType partType = visitExpression(part); |
| checkAssignable(node, type, partType); |
| } |
| return type; |
| } |
| |
| @override |
| DartType visitInstanceCreation(InstanceCreation node) { |
| Substitution substitution = Substitution.fromPairs( |
| node.classNode.typeParameters, node.typeArguments); |
| node.fieldValues.forEach((Reference fieldRef, Expression value) { |
| DartType fieldType = substitution.substituteType(fieldRef.asField.type); |
| DartType valueType = visitExpression(value); |
| checkAssignable(node, fieldType, valueType); |
| }); |
| return new InterfaceType( |
| node.classNode, currentLibrary!.nonNullable, node.typeArguments); |
| } |
| |
| @override |
| DartType visitFileUriExpression(FileUriExpression node) { |
| return visitExpression(node.expression); |
| } |
| |
| @override |
| DartType visitStringLiteral(StringLiteral node) { |
| return environment.coreTypes.stringLegacyRawType; |
| } |
| |
| @override |
| DartType visitSuperMethodInvocation(SuperMethodInvocation node) { |
| Member? target = node.interfaceTarget; |
| if (target == null) { |
| checkUnresolvedInvocation(currentThisType!, node); |
| return handleDynamicCall(currentThisType!, node.arguments); |
| } else { |
| return handleCall(node.arguments, target.superGetterType, |
| receiver: getSuperReceiverType(target)); |
| } |
| } |
| |
| @override |
| DartType visitSuperPropertyGet(SuperPropertyGet node) { |
| Member? target = node.interfaceTarget; |
| if (target == null) { |
| checkUnresolvedInvocation(currentThisType!, node); |
| return const DynamicType(); |
| } else { |
| Substitution receiver = getSuperReceiverType(target); |
| return receiver.substituteType(target.superGetterType); |
| } |
| } |
| |
| @override |
| DartType visitSuperPropertySet(SuperPropertySet node) { |
| Member? target = node.interfaceTarget; |
| DartType value = visitExpression(node.value); |
| if (target != null) { |
| Substitution receiver = getSuperReceiverType(target); |
| checkAssignable(node.value, value, |
| receiver.substituteType(target.superSetterType, contravariant: true)); |
| } else { |
| checkUnresolvedInvocation(currentThisType!, node); |
| } |
| return value; |
| } |
| |
| @override |
| DartType visitSymbolLiteral(SymbolLiteral node) { |
| return environment.coreTypes.symbolLegacyRawType; |
| } |
| |
| @override |
| DartType visitThisExpression(ThisExpression node) { |
| return currentThisType!; |
| } |
| |
| @override |
| DartType visitThrow(Throw node) { |
| visitExpression(node.expression); |
| return NeverType.fromNullability(currentLibrary!.nonNullable); |
| } |
| |
| @override |
| DartType visitTypeLiteral(TypeLiteral node) { |
| return environment.coreTypes.typeLegacyRawType; |
| } |
| |
| @override |
| DartType visitVariableGet(VariableGet node) { |
| return node.promotedType ?? node.variable.type; |
| } |
| |
| @override |
| DartType visitVariableSet(VariableSet node) { |
| DartType value = visitExpression(node.value); |
| checkAssignable(node.value, value, node.variable.type); |
| return value; |
| } |
| |
| @override |
| DartType visitLoadLibrary(LoadLibrary node) { |
| return environment.futureType( |
| const DynamicType(), currentLibrary!.nonNullable); |
| } |
| |
| @override |
| DartType visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) { |
| return environment.coreTypes.objectLegacyRawType; |
| } |
| |
| @override |
| DartType visitConstantExpression(ConstantExpression node) { |
| return node.type; |
| } |
| |
| @override |
| void visitAssertStatement(AssertStatement node) { |
| visitExpression(node.condition); |
| if (node.message != null) { |
| visitExpression(node.message!); |
| } |
| } |
| |
| @override |
| void visitBlock(Block node) { |
| node.statements.forEach(visitStatement); |
| } |
| |
| @override |
| void visitAssertBlock(AssertBlock node) { |
| node.statements.forEach(visitStatement); |
| } |
| |
| @override |
| void visitBreakStatement(BreakStatement node) {} |
| |
| @override |
| void visitContinueSwitchStatement(ContinueSwitchStatement node) {} |
| |
| @override |
| void visitDoStatement(DoStatement node) { |
| visitStatement(node.body); |
| node.condition = checkAndDowncastExpression( |
| node.condition, environment.coreTypes.boolLegacyRawType); |
| } |
| |
| @override |
| void visitEmptyStatement(EmptyStatement node) {} |
| |
| @override |
| void visitExpressionStatement(ExpressionStatement node) { |
| visitExpression(node.expression); |
| } |
| |
| @override |
| void visitForInStatement(ForInStatement node) { |
| DartType iterable = visitExpression(node.iterable); |
| // TODO(asgerf): Store interface targets on for-in loops or desugar them, |
| // instead of doing the ad-hoc resolution here. |
| if (node.isAsync) { |
| checkAssignable(node, getStreamElementType(iterable), node.variable.type); |
| } else { |
| checkAssignable( |
| node, getIterableElementType(iterable), node.variable.type); |
| } |
| visitStatement(node.body); |
| } |
| |
| static final Name iteratorName = new Name('iterator'); |
| static final Name currentName = new Name('current'); |
| |
| DartType getIterableElementType(DartType iterable) { |
| if (iterable is InterfaceType) { |
| Member? iteratorGetter = |
| hierarchy.getInterfaceMember(iterable.classNode, iteratorName); |
| if (iteratorGetter == null) return const DynamicType(); |
| List<DartType> castedIterableArguments = |
| hierarchy.getTypeArgumentsAsInstanceOf( |
| iterable, iteratorGetter.enclosingClass!)!; |
| DartType iteratorType = Substitution.fromPairs( |
| iteratorGetter.enclosingClass!.typeParameters, |
| castedIterableArguments) |
| .substituteType(iteratorGetter.getterType); |
| if (iteratorType is InterfaceType) { |
| Member? currentGetter = |
| hierarchy.getInterfaceMember(iteratorType.classNode, currentName); |
| if (currentGetter == null) return const DynamicType(); |
| List<DartType> castedIteratorTypeArguments = |
| hierarchy.getTypeArgumentsAsInstanceOf( |
| iteratorType, currentGetter.enclosingClass!)!; |
| return Substitution.fromPairs( |
| currentGetter.enclosingClass!.typeParameters, |
| castedIteratorTypeArguments) |
| .substituteType(currentGetter.getterType); |
| } |
| } |
| return const DynamicType(); |
| } |
| |
| DartType getStreamElementType(DartType stream) { |
| if (stream is InterfaceType) { |
| List<DartType>? asStreamArguments = |
| hierarchy.getTypeArgumentsAsInstanceOf(stream, coreTypes.streamClass); |
| if (asStreamArguments == null) return const DynamicType(); |
| return asStreamArguments.single; |
| } |
| return const DynamicType(); |
| } |
| |
| @override |
| void visitForStatement(ForStatement node) { |
| node.variables.forEach(visitVariableDeclaration); |
| if (node.condition != null) { |
| node.condition = checkAndDowncastExpression( |
| node.condition!, environment.coreTypes.boolLegacyRawType); |
| } |
| node.updates.forEach(visitExpression); |
| visitStatement(node.body); |
| } |
| |
| @override |
| void visitFunctionDeclaration(FunctionDeclaration node) { |
| handleNestedFunctionNode(node.function); |
| } |
| |
| @override |
| void visitIfStatement(IfStatement node) { |
| node.condition = checkAndDowncastExpression( |
| node.condition, environment.coreTypes.boolLegacyRawType); |
| visitStatement(node.then); |
| if (node.otherwise != null) { |
| visitStatement(node.otherwise!); |
| } |
| } |
| |
| @override |
| void visitLabeledStatement(LabeledStatement node) { |
| visitStatement(node.body); |
| } |
| |
| @override |
| void visitReturnStatement(ReturnStatement node) { |
| Expression? expression = node.expression; |
| if (expression != null) { |
| if (currentReturnType == null) { |
| fail(node, 'Return of a value from void method'); |
| } else { |
| DartType type = visitExpression(expression); |
| if (currentAsyncMarker == AsyncMarker.Async) { |
| type = environment.flatten(type); |
| } |
| checkAssignable(expression, type, currentReturnType!); |
| } |
| } |
| } |
| |
| @override |
| void visitSwitchStatement(SwitchStatement node) { |
| visitExpression(node.expression); |
| for (SwitchCase switchCase in node.cases) { |
| switchCase.expressions.forEach(visitExpression); |
| visitStatement(switchCase.body); |
| } |
| } |
| |
| @override |
| void visitTryCatch(TryCatch node) { |
| visitStatement(node.body); |
| for (Catch catchClause in node.catches) { |
| visitStatement(catchClause.body); |
| } |
| } |
| |
| @override |
| void visitTryFinally(TryFinally node) { |
| visitStatement(node.body); |
| visitStatement(node.finalizer); |
| } |
| |
| @override |
| void visitVariableDeclaration(VariableDeclaration node) { |
| if (node.initializer != null) { |
| node.initializer = |
| checkAndDowncastExpression(node.initializer!, node.type); |
| } |
| } |
| |
| @override |
| void visitWhileStatement(WhileStatement node) { |
| node.condition = checkAndDowncastExpression( |
| node.condition, environment.coreTypes.boolLegacyRawType); |
| visitStatement(node.body); |
| } |
| |
| @override |
| void visitYieldStatement(YieldStatement node) { |
| if (node.isYieldStar) { |
| Class container = currentAsyncMarker == AsyncMarker.AsyncStar |
| ? coreTypes.streamClass |
| : coreTypes.iterableClass; |
| DartType type = visitExpression(node.expression); |
| List<DartType>? asContainerArguments = type is InterfaceType |
| ? hierarchy.getTypeArgumentsAsInstanceOf(type, container) |
| : null; |
| if (asContainerArguments != null) { |
| checkAssignable( |
| node.expression, asContainerArguments[0], currentYieldType!); |
| } else if (type is! InvalidType && type is! NeverType) { |
| fail(node.expression, '$type is not an instance of $container'); |
| } |
| } else { |
| node.expression = |
| checkAndDowncastExpression(node.expression, currentYieldType!); |
| } |
| } |
| |
| @override |
| void visitFieldInitializer(FieldInitializer node) { |
| node.value = checkAndDowncastExpression(node.value, node.field.type); |
| } |
| |
| @override |
| void visitRedirectingInitializer(RedirectingInitializer node) { |
| handleCall(node.arguments, node.target.getterType, |
| typeParameters: const <TypeParameter>[]); |
| } |
| |
| @override |
| void visitSuperInitializer(SuperInitializer node) { |
| handleCall(node.arguments, node.target.getterType, |
| typeParameters: const <TypeParameter>[], |
| receiver: getSuperReceiverType(node.target)); |
| } |
| |
| @override |
| void visitLocalInitializer(LocalInitializer node) { |
| visitVariableDeclaration(node.variable); |
| } |
| |
| @override |
| void visitAssertInitializer(AssertInitializer node) { |
| visitAssertStatement(node.statement); |
| } |
| |
| @override |
| void visitInvalidInitializer(InvalidInitializer node) {} |
| |
| @override |
| DartType visitDynamicGet(DynamicGet node) { |
| DartType receiverType = visitExpression(node.receiver); |
| checkUnresolvedInvocation(receiverType, node); |
| switch (node.kind) { |
| case DynamicAccessKind.Dynamic: |
| return const DynamicType(); |
| case DynamicAccessKind.Never: |
| return new NeverType.internal(currentLibrary!.nonNullable); |
| case DynamicAccessKind.Invalid: |
| case DynamicAccessKind.Unresolved: |
| return const InvalidType(); |
| } |
| } |
| |
| @override |
| DartType visitDynamicInvocation(DynamicInvocation node) { |
| DartType receiverType = visitExpression(node.receiver); |
| checkUnresolvedInvocation(receiverType, node); |
| node.arguments.positional.forEach(visitExpression); |
| node.arguments.named |
| .forEach((NamedExpression n) => visitExpression(n.value)); |
| switch (node.kind) { |
| case DynamicAccessKind.Dynamic: |
| return const DynamicType(); |
| case DynamicAccessKind.Never: |
| return new NeverType.internal(currentLibrary!.nonNullable); |
| case DynamicAccessKind.Invalid: |
| case DynamicAccessKind.Unresolved: |
| return const InvalidType(); |
| } |
| } |
| |
| @override |
| DartType visitDynamicSet(DynamicSet node) { |
| DartType value = visitExpression(node.value); |
| final DartType receiver = visitExpression(node.receiver); |
| checkUnresolvedInvocation(receiver, node); |
| return value; |
| } |
| |
| @override |
| DartType visitEqualsCall(EqualsCall node) { |
| visitExpression(node.left); |
| visitExpression(node.right); |
| // TODO(johnniwinther): Return Never as type for equals call on Never. |
| return environment.coreTypes.boolLegacyRawType; |
| } |
| |
| @override |
| DartType visitEqualsNull(EqualsNull node) { |
| visitExpression(node.expression); |
| return environment.coreTypes.boolLegacyRawType; |
| } |
| |
| @override |
| DartType visitFunctionInvocation(FunctionInvocation node) { |
| DartType receiverType = visitExpression(node.receiver); |
| checkUnresolvedInvocation(receiverType, node); |
| node.arguments.positional.forEach(visitExpression); |
| node.arguments.named |
| .forEach((NamedExpression n) => visitExpression(n.value)); |
| return node.functionType?.returnType ?? const DynamicType(); |
| } |
| |
| @override |
| DartType visitInstanceGet(InstanceGet node) { |
| Substitution receiver = |
| getReceiverType(node, node.receiver, node.interfaceTarget); |
| return receiver.substituteType(node.interfaceTarget.getterType); |
| } |
| |
| @override |
| DartType visitInstanceInvocation(InstanceInvocation node) { |
| // TODO(johnniwinther): Use embedded static type. |
| Member target = node.interfaceTarget; |
| if (target is Procedure && |
| environment.isSpecialCasedBinaryOperator(target)) { |
| assert(node.arguments.positional.length == 1); |
| DartType receiver = visitExpression(node.receiver); |
| DartType argument = visitExpression(node.arguments.positional[0]); |
| return environment.getTypeOfSpecialCasedBinaryOperator( |
| receiver, argument); |
| } else { |
| visitExpression(node.receiver); |
| return handleCall(node.arguments, target.getterType, |
| receiver: getReceiverType(node, node.receiver, node.interfaceTarget)); |
| } |
| } |
| |
| @override |
| DartType visitInstanceGetterInvocation(InstanceGetterInvocation node) { |
| // TODO(johnniwinther): Use embedded static type. |
| Member target = node.interfaceTarget; |
| assert( |
| !(target is Procedure && |
| environment.isSpecialCasedBinaryOperator(target)), |
| "Unexpected instance getter invocation target: $target"); |
| visitExpression(node.receiver); |
| return handleCall(node.arguments, target.getterType, |
| receiver: getReceiverType(node, node.receiver, node.interfaceTarget)); |
| } |
| |
| @override |
| DartType visitInstanceSet(InstanceSet node) { |
| DartType value = visitExpression(node.value); |
| Substitution receiver = |
| getReceiverType(node, node.receiver, node.interfaceTarget); |
| checkAssignable( |
| node.value, |
| value, |
| receiver.substituteType(node.interfaceTarget.setterType, |
| contravariant: true)); |
| return value; |
| } |
| |
| @override |
| DartType visitInstanceTearOff(InstanceTearOff node) { |
| Substitution receiver = |
| getReceiverType(node, node.receiver, node.interfaceTarget); |
| return receiver.substituteType(node.interfaceTarget.getterType); |
| } |
| |
| @override |
| DartType visitLocalFunctionInvocation(LocalFunctionInvocation node) { |
| checkUnresolvedInvocation(node.functionType, node); |
| node.arguments.positional.forEach(visitExpression); |
| node.arguments.named |
| .forEach((NamedExpression n) => visitExpression(n.value)); |
| return node.functionType.returnType; |
| } |
| |
| @override |
| DartType visitStaticTearOff(StaticTearOff node) { |
| return node.target.getterType; |
| } |
| |
| @override |
| DartType visitFunctionTearOff(FunctionTearOff node) { |
| DartType receiverType = visitExpression(node.receiver); |
| checkUnresolvedInvocation(receiverType, node); |
| // TODO(johnniwinther): Return the correct result type. |
| return const DynamicType(); |
| } |
| } |