| // 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'; |
| import 'clone.dart'; |
| |
| enum TypeKind { Reference, Integer, Double } |
| |
| class Instantiation { |
| final Class klass; |
| final List<TypeKind> types; |
| |
| Class node; |
| |
| Instantiation(this.klass, this.types); |
| |
| bool operator == (other) { |
| return other is Instantiation && |
| this.klass == other.klass && |
| _listEquals(this.types, other.types); |
| } |
| |
| int get hashCode => klass.hashCode ^ _listHash(types); |
| |
| static bool _listEquals(List<TypeKind> a, List<TypeKind> b) { |
| if (a.length != b.length) return false; |
| for (var i = 0; i < a.length; i++) { |
| if (a[i] != b[i]) return false; |
| } |
| return true; |
| } |
| |
| static int _listHash(List<TypeKind> a) { |
| int result = a.length; |
| for (var i = 0; i < a.length; i++) { |
| result |= a[i].hashCode; |
| } |
| return result; |
| } |
| |
| } |
| |
| /// Performs strong-mode 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; |
| TypeEnvironment environment; |
| |
| TypeChecker(this.coreTypes, this.hierarchy) { |
| environment = new TypeEnvironment(coreTypes, hierarchy); |
| } |
| |
| DartType ensureNoDynamic(DartType type) { |
| if (type == const DynamicType()) { |
| return environment.objectType; |
| } else if (type is InterfaceType && type.typeArguments.isNotEmpty) { |
| return new InterfaceType(type.classNode, type.typeArguments.map(ensureNoDynamic).toList()); |
| } else if (type is FunctionType) { |
| return new FunctionType(type.positionalParameters.map(ensureNoDynamic).toList(), ensureNoDynamic(type.returnType), |
| namedParameters: type.namedParameters, typeParameters: type.typeParameters, requiredParameterCount: type.requiredParameterCount); |
| } else { |
| return type; |
| } |
| } |
| |
| void checkSignature(FunctionNode node) { |
| for (VariableDeclaration v in node.positionalParameters) { |
| v.type = ensureNoDynamic(v.type); |
| } |
| |
| for (VariableDeclaration v in node.namedParameters) { |
| v.type = ensureNoDynamic(v.type); |
| } |
| |
| node.returnType = ensureNoDynamic(node.returnType); |
| } |
| |
| List<Field> checkSignatures(Program program) { |
| final List<Field> dynamicFields = <Field>[]; |
| |
| handleField(Field field) { |
| if (field.type == const DynamicType()) { |
| if ((field.initializer == null || field.initializer is NullLiteral)) { |
| field.type = environment.objectType; |
| } else { |
| dynamicFields.add(field); |
| } |
| } |
| } |
| |
| int processed = 0; |
| for (var library in program.libraries) { |
| processed++; |
| print( |
| 'Processing ${library.importUri} (${processed} of ${program.libraries.length})'); |
| int nclass = 0; |
| for (var class_ in library.classes) { |
| nclass++; |
| print('${nclass}/${library.classes.length}: ${class_}'); |
| for (var constructor in class_.constructors) { |
| checkSignature(constructor.function); |
| } |
| for (var procedure in class_.procedures) { |
| checkSignature(procedure.function); |
| } |
| |
| class_.fields.forEach(handleField); |
| } |
| |
| for (var procedure in library.procedures) { |
| checkSignature(procedure.function); |
| } |
| |
| library.fields.forEach(handleField); |
| } |
| |
| return dynamicFields; |
| } |
| |
| void checkClass(TypeCheckingVisitor visitor, Class klass) { |
| environment.thisType = klass.thisType; |
| for (var field in klass.fields) { |
| visitor.visitField(field); |
| } |
| for (var constructor in klass.constructors) { |
| visitor.visitConstructor(constructor); |
| } |
| for (var procedure in klass.procedures) { |
| if (procedure.function.typeParameters.isNotEmpty) { |
| print('Skipping ${procedure}'); |
| continue; |
| } |
| visitor.visitProcedure(procedure); |
| } |
| } |
| |
| void checkProgram(Program program) { |
| final dynamicFields = checkSignatures(program); |
| |
| for (var library in program.libraries) { |
| if (library.importUri.scheme == 'dart') continue; |
| for (var class_ in library.classes) { |
| hierarchy.forEachOverridePair(class_, |
| (Member ownMember, Member superMember, bool isSetter) { |
| checkOverride(class_, ownMember, superMember, isSetter); |
| }); |
| } |
| } |
| |
| var visitor = new TypeCheckingVisitor(this, environment); |
| var worklist = dynamicFields; |
| |
| var changed; |
| do { |
| var current = worklist; |
| worklist = <Field>[]; |
| changed = false; |
| for (var field in current) { |
| final parent = field.parent; |
| if (parent is Class) { |
| environment.thisType = parent.thisType; |
| } else { |
| environment.thisType = null; |
| } |
| try { |
| visitor.visitField(field); |
| changed = true; |
| } catch (e) { |
| worklist.add(field); |
| } |
| } |
| |
| if (!changed && worklist.isNotEmpty) { |
| throw "Haha"; |
| } |
| } while (worklist.isNotEmpty); |
| |
| for (var field in dynamicFields) { |
| if (field.type == const DynamicType()) { |
| fail(field, "Field has type dynamic"); |
| } |
| } |
| |
| var processed = 0; |
| for (var library in program.libraries) { |
| processed++; |
| print( |
| 'Processing ${library.importUri} (${processed} of ${program.libraries.length})'); |
| int nclass = 0; |
| for (var klass in library.classes) { |
| nclass++; |
| if (klass.typeParameters.isNotEmpty) { |
| print('Skipping ${klass}'); |
| continue; |
| } |
| print('${nclass}/${library.classes.length}: ${klass}'); |
| checkClass(visitor, klass); |
| } |
| environment.thisType = null; |
| for (var procedure in library.procedures) { |
| if (procedure.function.typeParameters.isNotEmpty) { |
| print('Skipping ${procedure}'); |
| continue; |
| } |
| visitor.visitProcedure(procedure); |
| } |
| for (var field in library.fields) { |
| visitor.visitField(field); |
| } |
| } |
| } |
| |
| DartType getterType(Class host, Member member) { |
| var hostType = hierarchy.getClassAsInstanceOf(host, member.enclosingClass); |
| var substitution = Substitution.fromSupertype(hostType); |
| return substitution.substituteType(member.getterType); |
| } |
| |
| DartType setterType(Class host, Member member) { |
| var hostType = hierarchy.getClassAsInstanceOf(host, member.enclosingClass); |
| var substitution = Substitution.fromSupertype(hostType); |
| return substitution.substituteType(member.setterType, contravariant: true); |
| } |
| |
| 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; |
| } |
| |
| /// Indicates that type checking failed. |
| void fail(TreeNode where, String message); |
| |
| bool isNumeric(DartType type); |
| |
| bool isInteger(DartType type); |
| |
| bool isDouble(DartType type); |
| |
| TypeKind toTypeKind(TreeNode where, DartType type); |
| |
| DartType leastUpperBound(TreeNode where, DartType a, DartType b); |
| } |
| |
| enum SelectorKind { |
| Method, |
| Getter, |
| Setter, |
| } |
| |
| class Selector { |
| final SelectorKind kind; |
| final Name name; |
| |
| Selector(this.kind, this.name); |
| |
| factory Selector.fromProcedure(Procedure procedure) { |
| SelectorKind kind; |
| switch (procedure.kind) { |
| case ProcedureKind.Getter: |
| kind = SelectorKind.Getter; |
| break; |
| case ProcedureKind.Setter: |
| kind = SelectorKind.Setter; |
| break; |
| case ProcedureKind.Operator: |
| case ProcedureKind.Method: |
| case ProcedureKind.Factory: |
| kind = SelectorKind.Method; |
| break; |
| } |
| return new Selector(kind, procedure.name); |
| } |
| |
| Selector get asGetter => new Selector(SelectorKind.Getter, name); |
| |
| Selector get asMethod => new Selector(SelectorKind.Method, name); |
| |
| Selector modifyKindToAccess(Member target) { |
| if (target is Procedure) { |
| if (kind == SelectorKind.Method && target.kind == ProcedureKind.Getter) { |
| return asGetter; // This is a call-though-getter. |
| } else if (kind == SelectorKind.Getter && |
| target.kind == ProcedureKind.Method) { |
| return asMethod; // This is a tear-off. |
| } |
| } else if (target is Field) { |
| if (kind == SelectorKind.Method) { |
| return asGetter; // This is a call-though-field. |
| } |
| } |
| return this; |
| } |
| |
| int get hashCode => kind.hashCode ^ name.hashCode; |
| |
| bool operator ==(other) => |
| other is Selector && other.kind == kind && other.name == name; |
| |
| String toString() { |
| if (kind == SelectorKind.Getter) return 'get:${name}'; |
| if (kind == SelectorKind.Setter) return 'set:${name}'; |
| assert(kind == SelectorKind.Method); |
| return '$name'; |
| } |
| } |
| |
| class LookupCache { |
| final Map<Class, Map<Selector, Member>> lookupCache = |
| <Class, Map<Selector, Member>>{}; |
| |
| final Map<Class, Map<Selector, Member>> interfaceLookupCache = |
| <Class, Map<Selector, Member>>{}; |
| |
| Member lookupSelector(Class klass, Selector selector) { |
| return lookupCache |
| .putIfAbsent(klass, () => <Selector, Member>{}) |
| .putIfAbsent(selector, () { |
| while (klass != null) { |
| for (final Member member in klass.members) { |
| if (member.name == selector.name) { |
| if (member is Field) { |
| // A field shadows getter/call and setter iff non-final/non-const. |
| if (selector.kind == SelectorKind.Method) { |
| ensureIsFunctionType(member.type, 'call-through-field usage'); |
| return member; |
| } |
| if (selector.kind == SelectorKind.Getter) { |
| return member; |
| } |
| if (selector.kind == SelectorKind.Setter && |
| (!member.isFinal && !member.isConst)) { |
| return member; |
| } |
| } else if (member is Procedure) { |
| // A procedure shadows getter/call but not setter. |
| if (selector.kind == SelectorKind.Getter && |
| member.kind == ProcedureKind.Method) { |
| ensureProcedureCanBeTornOff(member); |
| return member; |
| } |
| if (selector.kind == SelectorKind.Getter && |
| member.kind == ProcedureKind.Getter) { |
| return member; |
| } |
| if (selector.kind == SelectorKind.Setter && |
| member.kind == ProcedureKind.Setter) { |
| return member; |
| } |
| if (selector.kind == SelectorKind.Method && |
| (member.kind == ProcedureKind.Method || |
| member.kind == ProcedureKind.Operator || |
| member.kind == ProcedureKind.Factory)) { |
| return member; |
| } |
| if (selector.kind == SelectorKind.Method && |
| (member.kind == ProcedureKind.Getter)) { |
| ensureIsFunctionType( |
| member.function.returnType, 'call-through-field usage'); |
| return member; |
| } |
| } else { |
| throw "UNREACHABLE"; |
| } |
| } |
| } |
| klass = klass.superclass; |
| } |
| return null; |
| }); |
| } |
| |
| Member lookupInterfaceSelector(Class klass, Selector selector) { |
| return interfaceLookupCache |
| .putIfAbsent(klass, () => <Selector, Member>{}) |
| .putIfAbsent(selector, () { |
| Member member = lookupSelector(klass, selector); |
| if (member != null) return member; |
| |
| for (final Supertype superType in klass.implementedTypes) { |
| member = lookupInterfaceSelector(superType.classNode, selector); |
| if (member != null) return member; |
| } |
| return null; |
| }); |
| } |
| |
| void ensureProcedureCanBeTornOff(Procedure procedure) { |
| final FunctionNode function = procedure.function; |
| if (function.requiredParameterCount != |
| function.positionalParameters.length || |
| function.namedParameters.isNotEmpty) { |
| throw 'No tear-off support for functions with optional parameters!'; |
| } |
| } |
| |
| void ensureIsFunctionType(DartType type, String msg) { |
| if (type is! FunctionType) { |
| throw 'Expected a [FunctionType] for $msg but got "$type".'; |
| } |
| } |
| } |
| |
| class AssignmentFinder extends RecursiveVisitor { |
| final VariableDeclaration decl; |
| |
| AssignmentFinder(this.decl); |
| |
| bool found = false; |
| |
| @override |
| void visitVariableSet(VariableSet node) { |
| if (node.variable == decl) { |
| found = true; |
| } else { |
| super.visitVariableSet(node); |
| } |
| } |
| |
| static bool hasAssignmentsTo(VariableDeclaration decl, TreeNode node) { |
| final finder = new AssignmentFinder(decl); |
| node.accept(finder); |
| return finder.found; |
| } |
| } |
| |
| |
| |
| class TypeCheckingVisitor |
| implements |
| ExpressionVisitor<DartType>, |
| StatementVisitor<Null>, |
| MemberVisitor<Null>, |
| InitializerVisitor<Null> { |
| final TypeChecker checker; |
| final TypeEnvironment environment; |
| |
| CoreTypes get coreTypes => environment.coreTypes; |
| ClassHierarchy get hierarchy => environment.hierarchy; |
| Class get currentClass => environment.thisType.classNode; |
| |
| TypeCheckingVisitor(this.checker, this.environment); |
| |
| void checkAssignable(TreeNode where, DartType from, DartType to) { |
| checker.checkAssignable(where, from, to); |
| } |
| |
| Expression checkAndDowncastExpression(Expression from, DartType to) { |
| if (isUntypedLambda(from)) { |
| handleUntypedLambda(from, to, new Set<TypeParameter>()); |
| } |
| |
| var parent = from.parent; |
| var type = visitExpression(from); |
| var result = checker.checkAndDowncastExpression(from, type, to); |
| result.parent = parent; |
| return result; |
| } |
| |
| Expression checkAndDowncastExpressionFrom( |
| Expression from, DartType fromType, DartType to) { |
| var parent = from.parent; |
| var result = checker.checkAndDowncastExpression(from, fromType, 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); |
| } |
| |
| defaultMember(Member node) => throw 'Unused'; |
| |
| DartType defaultBasicLiteral(BasicLiteral node) { |
| return defaultExpression(node); |
| } |
| |
| DartType defaultExpression(Expression node) { |
| throw 'Unexpected expression ${node.runtimeType}'; |
| } |
| |
| defaultStatement(Statement node) { |
| throw 'Unexpected statement ${node.runtimeType}'; |
| } |
| |
| defaultInitializer(Initializer node) { |
| throw 'Unexpected initializer ${node.runtimeType}'; |
| } |
| |
| visitField(Field node) { |
| if (node.type == const DynamicType() && node.initializer == null) { |
| fail(node, "Field ${node} has dynamic type and no initializer"); |
| return; |
| } |
| |
| if (node.type == const DynamicType()) { |
| node.type = visitExpression(node.initializer); |
| } |
| |
| if (node.initializer != null) { |
| node.initializer = |
| checkAndDowncastExpression(node.initializer, node.type); |
| } |
| } |
| |
| visitConstructor(Constructor node) { |
| environment.returnType = null; |
| environment.yieldType = null; |
| node.initializers.forEach(visitInitializer); |
| handleFunctionNode(node.function); |
| } |
| |
| visitProcedure(Procedure node) { |
| environment.returnType = _getInternalReturnType(node.function); |
| environment.yieldType = _getYieldType(node.function); |
| handleFunctionNode(node.function); |
| } |
| |
| void handleFunctionNode(FunctionNode node) { |
| var oldAsyncMarker = environment.currentAsyncMarker; |
| environment.currentAsyncMarker = node.asyncMarker; |
| node.positionalParameters |
| .skip(node.requiredParameterCount) |
| .forEach(handleOptionalParameter); |
| node.namedParameters.forEach(handleOptionalParameter); |
| if (node.body != null) { |
| visitStatement(node.body); |
| } |
| environment.currentAsyncMarker = oldAsyncMarker; |
| } |
| |
| void handleNestedFunctionNode(FunctionNode node) { |
| var oldReturn = environment.returnType; |
| var oldYield = environment.yieldType; |
| var oldCanInferReturnType = environment.canInferReturnType; |
| environment.returnType = _getInternalReturnType(node); |
| environment.yieldType = _getYieldType(node); |
| environment.canInferReturnType = node.parent is FunctionExpression && |
| environment.returnType == const DynamicType(); |
| handleFunctionNode(node); |
| if (environment.canInferReturnType) { |
| if (environment.returnType != const DynamicType()) { |
| node.returnType = environment.returnType; |
| } |
| } |
| environment.returnType = oldReturn; |
| environment.yieldType = oldYield; |
| environment.canInferReturnType = oldCanInferReturnType; |
| } |
| |
| void fixupInitializer(VariableDeclaration node) { |
| if (node.initializer is NullLiteral) { |
| switch (checker.toTypeKind(node, node.type)) { |
| case TypeKind.Integer: |
| node.initializer = new IntLiteral(0); |
| break; |
| case TypeKind.Double: |
| node.initializer = new DoubleLiteral(0.0); |
| break; |
| case TypeKind.Reference: |
| return; |
| } |
| node.initializer.parent = node; |
| } |
| } |
| |
| void handleOptionalParameter(VariableDeclaration parameter) { |
| fixupInitializer(parameter); |
| |
| if (parameter.initializer != null) { |
| // Default parameter values cannot be downcast. |
| checkExpressionNoDowncast(parameter.initializer, parameter.type); |
| } |
| } |
| |
| Substitution getReceiverType( |
| TreeNode access, Expression receiver, Member member) { |
| return getReceiverTypeImpl( |
| visitExpression(receiver), access, receiver, member); |
| } |
| |
| Substitution getReceiverTypeImpl( |
| DartType type, TreeNode access, Expression receiver, Member member) { |
| Class superclass = member.enclosingClass; |
| if (superclass.supertype == null) { |
| return Substitution.empty; // Members on Object are always accessible. |
| } |
| while (type is TypeParameterType) { |
| type = (type as TypeParameterType).parameter.bound; |
| } |
| if (type is BottomType) { |
| // 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. |
| var upcastType = hierarchy.getTypeAsInstanceOf(type, superclass); |
| if (upcastType != null) { |
| return Substitution.fromInterfaceType(upcastType); |
| } |
| } |
| 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)); |
| } |
| |
| static bool isUntypedLambda(TreeNode node) { |
| return node is FunctionExpression && |
| node.function.functionType.positionalParameters |
| .any((t) => t == const DynamicType()); |
| } |
| |
| // If function expression has untyped parameters then it infers their type |
| // based on the expectedType which is expected to be a FunctionType. |
| handleUntypedLambda(FunctionExpression arg, DartType expectedType, |
| Set<TypeParameter> typeParameters) { |
| if (!(expectedType is FunctionType && |
| expectedType.positionalParameters.length == |
| arg.function.positionalParameters.length)) { |
| fail(arg, |
| 'Expected ${expectedType}, got function expression with untyped parameters.'); |
| } |
| |
| for (var i = 0; i < arg.function.positionalParameters.length; i++) { |
| final param_ = arg.function.positionalParameters[i]; |
| if (param_.type != const DynamicType()) continue; |
| |
| final paramType_ = (expectedType as FunctionType).positionalParameters[i]; |
| |
| if (containsTypeVariable(paramType_, typeParameters)) { |
| fail(arg, |
| 'Expected ${expectedType}, got function expression with untyped parameters.'); |
| } |
| |
| arg.function.positionalParameters[i].type = paramType_; |
| if (paramType_ == const DynamicType()) { |
| fail(arg, 'Failed to infer type for the parameter'); |
| } |
| } |
| } |
| |
| void tryInferTypeArguments( |
| FunctionNode function, |
| Substitution receiver, |
| Arguments arguments, |
| List<TypeParameter> typeParameters) { |
| // We need to infer type parameters. |
| final quantifiedVariables = new Set<TypeParameter>.from(typeParameters); |
| var foundMapping = <TypeParameter, DartType>{}; |
| for (var i = 0; i < arguments.positional.length; i++) { |
| final param = function.positionalParameters[i]; |
| final arg = arguments.positional[i]; |
| |
| final expectedType = |
| receiver.substituteType(param.type, contravariant: true); |
| |
| if (isUntypedLambda(arg)) { |
| handleUntypedLambda(arg, expectedType, quantifiedVariables); |
| } |
| |
| final gotType = visitExpression(arg); |
| DartType candidate = gotType; |
| if (expectedType is InterfaceType && candidate is InterfaceType) { |
| findInstanceOf(Class klass, InterfaceType type) { |
| while (type.classNode != klass) { |
| final sup = type.classNode.supertype; |
| if (sup == null) { |
| return null; |
| } |
| |
| final sub = Substitution.fromInterfaceType(type); |
| if (sup.classNode != klass) { |
| for (var supi in type.classNode.implementedTypes) { |
| if (supi.classNode == klass) { |
| return sub.substituteType(supi.asInterfaceType); |
| } |
| } |
| |
| for (var supi in type.classNode.implementedTypes) { |
| final res = findInstanceOf(klass, supi.asInterfaceType); |
| if (res != null) { |
| return res; |
| } |
| } |
| } |
| |
| type = sub.substituteType(sup.asInterfaceType); |
| } |
| return type; |
| } |
| |
| candidate = findInstanceOf(expectedType.classNode, candidate as InterfaceType) ?? candidate; |
| } |
| |
| var mapping = unifyTypes(expectedType, candidate, quantifiedVariables); |
| if (mapping != null) { |
| mapping.forEach((p, t1) { |
| final t2 = foundMapping[p]; |
| if (t2 == null) { |
| foundMapping[p] = t1; |
| } else if (t2 != t1) { |
| // TODO(vegorov) !!! |
| fail(arguments.positional[i], "Inferred T to be ${t1} and ${t2}."); |
| } |
| }); |
| } |
| } |
| |
| if (foundMapping.length != quantifiedVariables.length) { |
| fail(arguments, |
| "Failed to infer some type arguments: " |
| "${quantifiedVariables.where((p) => !foundMapping.containsKey(p))}"); |
| } |
| |
| arguments.types.length = typeParameters.length; |
| arguments.types.setRange( |
| 0, arguments.types.length, typeParameters.map((p) => foundMapping[p])); |
| } |
| |
| DartType handleCall(Arguments arguments, FunctionNode function, |
| {Substitution receiver: Substitution.empty, |
| List<TypeParameter> typeParameters}) { |
| final parent = arguments.parent; |
| if (parent is StaticInvocation && |
| parent.name.name == 'identical') { |
| if (arguments.named.length == 0 && arguments.positional.length == 2) { |
| var lhs = arguments.positional[0]; |
| var rhs = arguments.positional[1]; |
| |
| if (checker.toTypeKind(lhs, visitExpression(lhs)) == |
| checker.toTypeKind(rhs, visitExpression(rhs))) { |
| return environment.boolType; |
| } |
| } |
| } |
| |
| typeParameters ??= function.typeParameters; |
| if (arguments.positional.length < function.requiredParameterCount) { |
| fail(arguments, 'Too few positional arguments'); |
| return const BottomType(); |
| } |
| if (arguments.positional.length > function.positionalParameters.length) { |
| fail(arguments, 'Too many positional arguments'); |
| return const BottomType(); |
| } |
| if (arguments.types.length != typeParameters.length) { |
| if (arguments.types.isEmpty && typeParameters.isNotEmpty) { |
| tryInferTypeArguments(function, receiver, arguments, typeParameters); |
| } else { |
| fail(arguments, ''' |
| Wrong number of type arguments: |
| expected ${function.functionType} |
| found ${arguments} |
| '''); |
| return const BottomType(); |
| } |
| } else if (arguments.types.isNotEmpty && |
| arguments.types.every((t) => t == const DynamicType())) { |
| tryInferTypeArguments(function, receiver, arguments, typeParameters); |
| } |
| |
| var instantiation = Substitution.fromPairs(typeParameters, arguments.types); |
| var substitution = Substitution.combine(receiver, instantiation); |
| for (int i = 0; i < typeParameters.length; ++i) { |
| var argument = arguments.types[i]; |
| InterfaceType bound = substitution.substituteType(typeParameters[i].bound); |
| if (bound.classNode.supertype == null) { |
| // Ignore Object bound. |
| continue; |
| } |
| checkAssignable(arguments, argument, bound); |
| } |
| for (int i = 0; i < arguments.positional.length; ++i) { |
| var expectedType = substitution.substituteType( |
| function.positionalParameters[i].type, |
| contravariant: true); |
| arguments.positional[i] = |
| checkAndDowncastExpression(arguments.positional[i], expectedType); |
| } |
| for (int i = 0; i < arguments.named.length; ++i) { |
| var argument = arguments.named[i]; |
| bool found = false; |
| for (int j = 0; j < function.namedParameters.length; ++j) { |
| if (argument.name == function.namedParameters[j].name) { |
| var expectedType = substitution.substituteType( |
| function.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 const BottomType(); |
| } |
| } |
| return substitution.substituteType(function.returnType); |
| } |
| |
| 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: |
| case AsyncMarker.SyncYielding: |
| 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}'; |
| } |
| } |
| |
| @override |
| DartType visitAsExpression(AsExpression node) { |
| if (node.type == const DynamicType()) { |
| fail(node, 'dynamic is not allowed in this context'); |
| } |
| node.type = ensureNoDynamic(node.type); |
| visitExpression(node.operand); |
| return node.type; |
| } |
| |
| @override |
| DartType visitAwaitExpression(AwaitExpression node) { |
| return environment.unfutureType(visitExpression(node.operand)); |
| } |
| |
| @override |
| DartType visitBoolLiteral(BoolLiteral node) { |
| return environment.boolType; |
| } |
| |
| @override |
| DartType visitConditionalExpression(ConditionalExpression node) { |
| node.condition = |
| checkAndDowncastExpression(node.condition, environment.boolType); |
| |
| var thenType = visitExpression(node.then); |
| var otherwiseType = visitExpression(node.otherwise); |
| |
| node.staticType = checker.leastUpperBound(node, thenType, otherwiseType); |
| node.then = |
| checkAndDowncastExpressionFrom(node.then, thenType, node.staticType); |
| node.otherwise = checkAndDowncastExpressionFrom( |
| node.otherwise, otherwiseType, 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, |
| typeParameters: class_.typeParameters); |
| return new InterfaceType(target.enclosingClass, arguments.types); |
| } |
| |
| @override |
| DartType visitDirectMethodInvocation(DirectMethodInvocation node) { |
| return handleCall(node.arguments, node.target.function, |
| receiver: getReceiverType(node, node.receiver, node.target)); |
| } |
| |
| @override |
| DartType visitDirectPropertyGet(DirectPropertyGet node) { |
| var receiver = getReceiverType(node, node.receiver, node.target); |
| return receiver.substituteType(node.target.getterType); |
| } |
| |
| @override |
| DartType visitDirectPropertySet(DirectPropertySet node) { |
| var receiver = getReceiverType(node, node.receiver, node.target); |
| var value = visitExpression(node.value); |
| checkAssignable(node, value, |
| receiver.substituteType(node.target.setterType, contravariant: true)); |
| return value; |
| } |
| |
| @override |
| DartType visitDoubleLiteral(DoubleLiteral node) { |
| return environment.doubleType; |
| } |
| |
| @override |
| DartType visitFunctionExpression(FunctionExpression node) { |
| handleNestedFunctionNode(node.function); |
| return node.function.functionType; |
| } |
| |
| @override |
| DartType visitIntLiteral(IntLiteral node) { |
| return environment.intType; |
| } |
| |
| @override |
| DartType visitInvalidExpression(InvalidExpression node) { |
| return const BottomType(); |
| } |
| |
| DartType ensureNoDynamic(DartType type) { |
| if (type == const DynamicType()) { |
| return environment.objectType; |
| } else if (type is InterfaceType && type.typeArguments.isNotEmpty) { |
| return new InterfaceType(type.classNode, type.typeArguments.map(ensureNoDynamic).toList()); |
| } else if (type is FunctionType) { |
| return new FunctionType(type.positionalParameters.map(ensureNoDynamic).toList(), ensureNoDynamic(type.returnType), |
| namedParameters: type.namedParameters, typeParameters: type.typeParameters, requiredParameterCount: type.requiredParameterCount); |
| } else { |
| return type; |
| } |
| } |
| |
| @override |
| DartType visitIsExpression(IsExpression node) { |
| if (node.type == const DynamicType()) { |
| fail(node, 'dynamic is not allowed in this context'); |
| } |
| node.type = ensureNoDynamic(node.type); |
| visitExpression(node.operand); |
| return environment.boolType; |
| } |
| |
| @override |
| DartType visitLet(Let node) { |
| var value = visitExpression(node.variable.initializer); |
| if (node.variable.type is DynamicType) { |
| node.variable.type = value; |
| } |
| return visitExpression(node.body); |
| } |
| |
| @override |
| DartType visitListLiteral(ListLiteral node) { |
| if (node.typeArgument == const DynamicType()) { |
| if (node.expressions.isEmpty) { |
| fail(node, |
| 'Empty array literals must have an explicitly specified type argument'); |
| } |
| |
| // We don't permit <dynamic>[...] lists. |
| DartType typeArg = null; |
| for (var expr in node.expressions) { |
| final DartType elemType = visitExpression(expr); |
| if (typeArg == null) { |
| typeArg = elemType; |
| } else if (typeArg != elemType) { |
| typeArg = checker.leastUpperBound(expr, typeArg, elemType); |
| } |
| } |
| node.typeArgument = typeArg; |
| } else { |
| for (int i = 0; i < node.expressions.length; ++i) { |
| node.expressions[i] = |
| checkAndDowncastExpression(node.expressions[i], node.typeArgument); |
| } |
| } |
| |
| return environment.literalListType(node.typeArgument); |
| } |
| |
| @override |
| DartType visitLogicalExpression(LogicalExpression node) { |
| node.left = checkAndDowncastExpression(node.left, environment.boolType); |
| |
| final cond = node.left; |
| VariableDeclaration promoted; |
| DartType outerPromotion; |
| |
| if (node.operator == '&&') { |
| if (cond is IsExpression) { |
| final val = cond.operand; |
| if (val is VariableGet && |
| (val.variable.isFinal || !AssignmentFinder.hasAssignmentsTo(val.variable, node.right))) { |
| outerPromotion = promotions[val.variable]; |
| promotions[val.variable] = cond.type; |
| promoted = val.variable; |
| } |
| } |
| } |
| node.right = checkAndDowncastExpression(node.right, environment.boolType); |
| if (promoted != null) { |
| promotions[promoted] = outerPromotion; |
| } |
| return environment.boolType; |
| } |
| |
| @override |
| DartType visitMapLiteral(MapLiteral node) { |
| final inferKey = node.keyType == const DynamicType(); |
| final inferValue = node.valueType == const DynamicType(); |
| |
| for (var entry in node.entries) { |
| if (inferKey) { |
| final type = visitExpression(entry.key); |
| if (node.keyType == const DynamicType()) { |
| node.keyType = type; |
| } else { |
| node.keyType = checker.leastUpperBound(entry, node.keyType, type); |
| } |
| } else { |
| entry.key = checkAndDowncastExpression(entry.key, node.keyType); |
| } |
| |
| if (inferValue) { |
| final type = visitExpression(entry.value); |
| if (node.valueType == const DynamicType()) { |
| node.valueType = type; |
| } else { |
| node.valueType = checker.leastUpperBound(entry, node.valueType, type); |
| } |
| } else { |
| entry.value = checkAndDowncastExpression(entry.value, node.valueType); |
| } |
| } |
| return environment.literalMapType(node.keyType, node.valueType); |
| } |
| |
| 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 const BottomType(); |
| } |
| if (function.positionalParameters.length < arguments.positional.length) { |
| fail(access, 'Too many positional arguments'); |
| return const BottomType(); |
| } |
| if (function.typeParameters.length != arguments.types.length) { |
| fail(access, 'Wrong number of type arguments: expected ${function}, found ${arguments}'); |
| return const BottomType(); |
| } |
| var instantiation = |
| Substitution.fromPairs(function.typeParameters, arguments.types); |
| for (int i = 0; i < arguments.positional.length; ++i) { |
| var 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) { |
| var argument = arguments.named[i]; |
| bool found = false; |
| for (int j = 0; j < function.namedParameters.length; ++j) { |
| if (argument.name == function.namedParameters[j].name) { |
| var expectedType = instantiation.substituteType( |
| function.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 const BottomType(); |
| } |
| } |
| return instantiation.substituteType(function.returnType); |
| } |
| |
| final LookupCache lookupCache = new LookupCache(); |
| |
| static bool isBinaryArithmeticOperator(String name) { |
| return name == '+' || |
| name == '-' || |
| name == '*' || |
| name == 'remainder' || |
| name == '%' || |
| name == '/' || |
| name == '~/'; |
| } |
| |
| static bool isBinaryComparisonOperator(String name) { |
| return name == '==' || |
| name == '!=' || |
| name == '<' || |
| name == '>' || |
| name == '<=' || |
| name == '>='; |
| } |
| |
| void checkArguments(Arguments args, int expected) { |
| if (args.types.isNotEmpty) { |
| fail(args.parent, "Expected no type arguments"); |
| } |
| |
| if (args.named.isNotEmpty) { |
| fail(args.parent, "Expected no named arguments"); |
| } |
| |
| if (args.positional.length != expected) { |
| fail(args.parent, |
| "Expected ${expected} arguments, got ${args.positional.length}"); |
| } |
| } |
| |
| Member resolveInvocation(DartType receiver, MethodInvocation node) { |
| if (receiver is InterfaceType) { |
| return lookupCache.lookupInterfaceSelector( |
| receiver.classNode, new Selector(SelectorKind.Method, node.name)); |
| } else if (receiver is FunctionType) { |
| if (node.name.name == 'call') { |
| return environment.invokeClosure; |
| } else if (node.name.name == '==') { |
| return coreTypes.getMember('dart:core', 'Object', '=='); |
| } |
| } else if (receiver is TypeParameterType) { |
| return resolveInvocation(receiver.parameter.bound, node); |
| } |
| |
| return null; |
| } |
| |
| @override |
| DartType visitMethodInvocation(MethodInvocation node) { |
| if (isUntypedLambda(node.receiver) && node.name.name == 'call') { |
| assert(node.arguments.named.isEmpty); |
| handleUntypedLambda( |
| node.receiver, |
| new FunctionType( |
| node.arguments.positional.map(visitExpression).toList(), |
| const DynamicType()), |
| new Set<TypeParameter>()); |
| } |
| |
| final receiver = visitExpression(node.receiver); |
| |
| var target = node.interfaceTarget; |
| if (target == null) { |
| target = resolveInvocation(receiver, node); |
| if (target == null) { |
| fail(node, "Failed to resolve ${node.name} for ${receiver}"); |
| } |
| node.interfaceTarget = target; |
| } |
| |
| if (target == environment.invokeClosure) { |
| return handleFunctionCall(node, receiver, node.arguments); |
| } |
| |
| final receiverKind = checker.toTypeKind(node.receiver, receiver); |
| |
| if (receiverKind != TypeKind.Reference && |
| target.enclosingClass == coreTypes.numClass) { |
| final isArithmetic = isBinaryArithmeticOperator(node.name.name); |
| final isComparison = isBinaryComparisonOperator(node.name.name); |
| if (isArithmetic || isComparison) { |
| checkArguments(node.arguments, 1); |
| final rhs = node.arguments.positional[0]; |
| final argument = visitExpression(rhs); |
| final argumentKind = checker.toTypeKind(rhs, argument); |
| |
| if (argumentKind == TypeKind.Reference) { |
| fail(node, |
| "Second argument must be numeric: ${receiver} ${node.name.name} ${argument}"); |
| } |
| |
| var resultKind = receiverKind; |
| if (receiverKind != argumentKind) { |
| // Argument conversion is necessary. |
| final toDouble = coreTypes.getMember('dart:core', 'num', 'toDouble'); |
| if (receiverKind == TypeKind.Double) { |
| node.arguments.positional[0] = |
| new DirectMethodInvocation(rhs, toDouble, new Arguments.empty()) |
| ..parent = node.arguments; |
| } else { |
| node.receiver = new DirectMethodInvocation( |
| node.receiver, toDouble, new Arguments.empty()) |
| ..parent = node; |
| } |
| resultKind = TypeKind.Double; |
| } |
| |
| if (node.name.name == '/') { |
| resultKind = TypeKind.Double; |
| } |
| |
| return isComparison |
| ? environment.boolType |
| : (resultKind == TypeKind.Integer |
| ? environment.intType |
| : environment.doubleType); |
| } |
| |
| // fail(node, 'Numeric expression requires special rules. ${target}'); |
| } |
| |
| if (target is Procedure) { |
| if (environment.isOverloadedArithmeticOperator(target)) { |
| assert(node.arguments.positional.length == 1); |
| var argument = visitExpression(node.arguments.positional[0]); |
| return environment.getTypeOfOverloadedArithmetic(receiver, argument); |
| } else { |
| return handleCall(node.arguments, target.function, |
| receiver: getReceiverTypeImpl( |
| receiver, node, node.receiver, node.interfaceTarget)); |
| } |
| } else if (target is Field) { |
| if (target.type is! FunctionType) { |
| fail(node, 'Expression does not evaluate to function'); |
| } |
| |
| return handleFunctionCall(node, target.type, node.arguments); |
| } else { |
| fail(node, 'Unexpected target: ${target}'); |
| } |
| } |
| |
| @override |
| DartType visitPropertyGet(PropertyGet node) { |
| var target = node.interfaceTarget; |
| if (target == null) { |
| final receiver = visitExpression(node.receiver); |
| |
| if (receiver is InterfaceType) { |
| target = lookupCache.lookupInterfaceSelector( |
| receiver.classNode, new Selector(SelectorKind.Getter, node.name)); |
| if (target == null) { |
| fail(node, 'Failed to lookup ${node.name} on ${receiver}'); |
| return const BottomType(); |
| } |
| node.interfaceTarget = target; |
| } else if (receiver is FunctionType) { |
| fail(node, "Can't dispatch on FunctionType"); |
| return const BottomType(); |
| } else { |
| fail(node, "Can't dispatch on ${receiver}."); |
| return const BottomType(); |
| } |
| } |
| |
| var receiver = getReceiverType(node, node.receiver, node.interfaceTarget); |
| return receiver.substituteType(node.interfaceTarget.getterType); |
| } |
| |
| @override |
| DartType visitPropertySet(PropertySet node) { |
| var target = node.interfaceTarget; |
| if (target == null) { |
| final receiver = visitExpression(node.receiver); |
| |
| if (receiver is InterfaceType) { |
| target = lookupCache.lookupInterfaceSelector( |
| receiver.classNode, new Selector(SelectorKind.Setter, node.name)); |
| if (target == null) { |
| fail(node, 'Failed to lookup ${node.name} on ${receiver}'); |
| return const BottomType(); |
| } |
| node.interfaceTarget = target; |
| } else if (receiver is FunctionType) { |
| fail(node, "Can't dispatch on FunctionType"); |
| return const BottomType(); |
| } else { |
| fail(node, "Can't dispatch on ${receiver}."); |
| return const BottomType(); |
| } |
| } |
| |
| var value = visitExpression(node.value); |
| var receiver = getReceiverType(node, node.receiver, node.interfaceTarget); |
| |
| node.value = checkAndDowncastExpressionFrom( |
| node.value, |
| value, |
| receiver.substituteType(node.interfaceTarget.setterType, |
| contravariant: true)); |
| |
| return value; |
| } |
| |
| @override |
| DartType visitNot(Not node) { |
| visitExpression(node.operand); |
| return environment.boolType; |
| } |
| |
| @override |
| DartType visitNullLiteral(NullLiteral node) { |
| return const BottomType(); |
| } |
| |
| @override |
| DartType visitRethrow(Rethrow node) { |
| return const BottomType(); |
| } |
| |
| @override |
| DartType visitStaticGet(StaticGet node) { |
| return node.target.getterType; |
| } |
| |
| @override |
| DartType visitStaticInvocation(StaticInvocation node) { |
| return handleCall(node.arguments, node.target.function); |
| } |
| |
| @override |
| DartType visitStaticSet(StaticSet node) { |
| var value = visitExpression(node.value); |
| checkAssignable(node.value, value, node.target.setterType); |
| return value; |
| } |
| |
| @override |
| DartType visitStringConcatenation(StringConcatenation node) { |
| node.expressions.forEach(visitExpression); |
| return environment.stringType; |
| } |
| |
| @override |
| DartType visitStringLiteral(StringLiteral node) { |
| return environment.stringType; |
| } |
| |
| @override |
| DartType visitSuperMethodInvocation(SuperMethodInvocation node) { |
| if (node.interfaceTarget == null) { |
| return handleDynamicCall(environment.thisType, node.arguments); |
| } else { |
| return handleCall(node.arguments, node.interfaceTarget.function, |
| receiver: getSuperReceiverType(node.interfaceTarget)); |
| } |
| } |
| |
| @override |
| DartType visitSuperPropertyGet(SuperPropertyGet node) { |
| if (node.interfaceTarget == null) { |
| return const DynamicType(); |
| } else { |
| var receiver = getSuperReceiverType(node.interfaceTarget); |
| return receiver.substituteType(node.interfaceTarget.getterType); |
| } |
| } |
| |
| @override |
| DartType visitSuperPropertySet(SuperPropertySet node) { |
| var value = visitExpression(node.value); |
| if (node.interfaceTarget != null) { |
| var receiver = getSuperReceiverType(node.interfaceTarget); |
| checkAssignable( |
| node.value, |
| value, |
| receiver.substituteType(node.interfaceTarget.setterType, |
| contravariant: true)); |
| } |
| return value; |
| } |
| |
| @override |
| DartType visitSymbolLiteral(SymbolLiteral node) { |
| return environment.symbolType; |
| } |
| |
| @override |
| DartType visitThisExpression(ThisExpression node) { |
| return environment.thisType; |
| } |
| |
| @override |
| DartType visitThrow(Throw node) { |
| visitExpression(node.expression); |
| return const BottomType(); |
| } |
| |
| @override |
| DartType visitTypeLiteral(TypeLiteral node) { |
| return environment.typeType; |
| } |
| |
| @override |
| DartType visitVariableGet(VariableGet node) { |
| node.promotedType ??= promotions[node.variable]; |
| return node.promotedType ?? node.variable.type; |
| } |
| |
| @override |
| DartType visitVariableSet(VariableSet node) { |
| var value = visitExpression(node.value); |
| checkAssignable(node.value, value, node.variable.type); |
| return value; |
| } |
| |
| @override |
| DartType visitLoadLibrary(LoadLibrary node) { |
| return environment.futureType(const DynamicType()); |
| } |
| |
| @override |
| DartType visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) { |
| return environment.objectType; |
| } |
| |
| @override |
| DartType visitVectorCreation(VectorCreation node) { |
| return const VectorType(); |
| } |
| |
| @override |
| DartType visitVectorGet(VectorGet node) { |
| var type = visitExpression(node.vectorExpression); |
| if (type is! VectorType) { |
| fail( |
| node.vectorExpression, |
| 'The type of vector-expression in vector-get node is expected to be ' |
| 'VectorType, but $type found'); |
| } |
| return const DynamicType(); |
| } |
| |
| @override |
| visitVectorSet(VectorSet node) { |
| var type = visitExpression(node.vectorExpression); |
| if (type is! VectorType) { |
| fail( |
| node.vectorExpression, |
| 'The type of vector-expression in vector-set node is expected to be ' |
| 'VectorType, but $type found'); |
| } |
| return visitExpression(node.value); |
| } |
| |
| @override |
| visitVectorCopy(VectorCopy node) { |
| var type = visitExpression(node.vectorExpression); |
| if (type is! VectorType) { |
| fail( |
| node.vectorExpression, |
| 'The type of vector-expression in vector-copy node is exected to be ' |
| 'VectorType, but $type found'); |
| } |
| return const VectorType(); |
| } |
| |
| @override |
| visitClosureCreation(ClosureCreation node) { |
| var contextType = visitExpression(node.contextVector); |
| if (contextType is! VectorType) { |
| fail( |
| node.contextVector, |
| "The second child of 'ClosureConversion' node is supposed to be a " |
| "Vector, but $contextType found."); |
| } |
| return node.functionType; |
| } |
| |
| final List<Object> rollback = []; |
| |
| @override |
| visitAssertStatement(AssertStatement node) { |
| visitExpression(node.condition); |
| |
| if (node.parent is Block) { |
| final cond = node.condition; |
| if (cond is IsExpression) { |
| final val = cond.operand; |
| if (val is VariableGet && |
| (val.variable.isFinal || !AssignmentFinder.hasAssignmentsTo(val.variable, node.parent))) { |
| final outerPromotion = promotions[val.variable]; |
| promotions[val.variable] = cond.type; |
| if (rollback.isEmpty || |
| rollback[rollback.length - 1] != node.parent) { |
| rollback.add(new Map<VariableDeclaration, DartType>()); |
| rollback.add(node.parent); |
| } |
| |
| Map<VariableDeclaration, DartType> m = rollback[rollback.length - 2]; |
| m[val.variable] = outerPromotion; |
| } |
| } |
| } |
| |
| if (node.message != null) { |
| visitExpression(node.message); |
| } |
| } |
| |
| @override |
| visitBlock(Block node) { |
| node.statements.forEach(visitStatement); |
| if (rollback.isNotEmpty && rollback.last == node) { |
| Map<VariableDeclaration, DartType> m = rollback[rollback.length - 2]; |
| m.forEach((v, t) => promotions[v] = t); |
| rollback.length -= 2; |
| } |
| } |
| |
| @override |
| visitBreakStatement(BreakStatement node) {} |
| |
| @override |
| visitContinueSwitchStatement(ContinueSwitchStatement node) {} |
| |
| @override |
| visitDoStatement(DoStatement node) { |
| visitStatement(node.body); |
| node.condition = |
| checkAndDowncastExpression(node.condition, environment.boolType); |
| } |
| |
| @override |
| visitEmptyStatement(EmptyStatement node) {} |
| |
| @override |
| visitExpressionStatement(ExpressionStatement node) { |
| visitExpression(node.expression); |
| } |
| |
| @override |
| visitForInStatement(ForInStatement node) { |
| var 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) { |
| var iteratorGetter = |
| hierarchy.getInterfaceMember(iterable.classNode, iteratorName); |
| if (iteratorGetter == null) return const DynamicType(); |
| var castedIterable = hierarchy.getTypeAsInstanceOf( |
| iterable, iteratorGetter.enclosingClass); |
| var iteratorType = Substitution |
| .fromInterfaceType(castedIterable) |
| .substituteType(iteratorGetter.getterType); |
| if (iteratorType is InterfaceType) { |
| var currentGetter = |
| hierarchy.getInterfaceMember(iteratorType.classNode, currentName); |
| if (currentGetter == null) return const DynamicType(); |
| var castedIteratorType = hierarchy.getTypeAsInstanceOf( |
| iteratorType, currentGetter.enclosingClass); |
| return Substitution |
| .fromInterfaceType(castedIteratorType) |
| .substituteType(currentGetter.getterType); |
| } |
| } |
| return const DynamicType(); |
| } |
| |
| DartType getStreamElementType(DartType stream) { |
| if (stream is InterfaceType) { |
| var asStream = |
| hierarchy.getTypeAsInstanceOf(stream, coreTypes.streamClass); |
| if (asStream == null) return const DynamicType(); |
| return asStream.typeArguments.single; |
| } |
| return const DynamicType(); |
| } |
| |
| @override |
| visitForStatement(ForStatement node) { |
| node.variables.forEach(visitVariableDeclaration); |
| if (node.condition != null) { |
| node.condition = |
| checkAndDowncastExpression(node.condition, environment.boolType); |
| } |
| node.updates.forEach(visitExpression); |
| visitStatement(node.body); |
| } |
| |
| @override |
| visitFunctionDeclaration(FunctionDeclaration node) { |
| handleNestedFunctionNode(node.function); |
| node.variable.type = node.function.functionType; |
| } |
| |
| final Map<VariableDeclaration, DartType> promotions = |
| <VariableDeclaration, DartType>{}; |
| |
| @override |
| visitIfStatement(IfStatement node) { |
| node.condition = |
| checkAndDowncastExpression(node.condition, environment.boolType); |
| final cond = node.condition; |
| VariableDeclaration promoted; |
| DartType outerPromotion; |
| if (cond is IsExpression) { |
| final val = cond.operand; |
| if (val is VariableGet && |
| (val.variable.isFinal || !AssignmentFinder.hasAssignmentsTo(val.variable, node.then))) { |
| outerPromotion = promotions[val.variable]; |
| promotions[val.variable] = cond.type; |
| promoted = val.variable; |
| } |
| } |
| visitStatement(node.then); |
| if (promoted != null) { |
| promotions[promoted] = outerPromotion; |
| } |
| if (node.otherwise != null) { |
| visitStatement(node.otherwise); |
| } |
| } |
| |
| @override |
| visitInvalidStatement(InvalidStatement node) {} |
| |
| @override |
| visitLabeledStatement(LabeledStatement node) { |
| visitStatement(node.body); |
| } |
| |
| @override |
| visitReturnStatement(ReturnStatement node) { |
| if (node.expression != null) { |
| if (environment.returnType == null) { |
| fail(node, 'Return of a value from void method'); |
| } else { |
| if (environment.returnType != const DynamicType()) { |
| if (isUntypedLambda(node.expression)) { |
| handleUntypedLambda(node.expression, environment.returnType, |
| new Set<TypeParameter>()); |
| } |
| } |
| |
| var type = visitExpression(node.expression); |
| if (environment.currentAsyncMarker == AsyncMarker.Async) { |
| type = environment.unfutureType(type); |
| } |
| if (environment.returnType == const DynamicType()) { |
| if (!environment.canInferReturnType) { |
| fail(node, 'Functions can not have dynamic return type'); |
| } |
| |
| environment.returnType = type; |
| } |
| |
| checkAssignable(node.expression, type, environment.returnType); |
| } |
| } |
| } |
| |
| @override |
| visitSwitchStatement(SwitchStatement node) { |
| visitExpression(node.expression); |
| for (var switchCase in node.cases) { |
| switchCase.expressions.forEach(visitExpression); |
| visitStatement(switchCase.body); |
| } |
| } |
| |
| @override |
| visitTryCatch(TryCatch node) { |
| visitStatement(node.body); |
| for (var catchClause in node.catches) { |
| if (catchClause.exception?.type == const DynamicType()) { |
| catchClause.exception.type = environment.objectType; |
| } |
| if (catchClause.stackTrace?.type == const DynamicType()) { |
| catchClause.stackTrace.type = environment.objectType; |
| } |
| visitStatement(catchClause.body); |
| } |
| } |
| |
| @override |
| visitTryFinally(TryFinally node) { |
| visitStatement(node.body); |
| visitStatement(node.finalizer); |
| } |
| |
| @override |
| visitVariableDeclaration(VariableDeclaration node) { |
| if (node.type == const DynamicType()) { |
| if (node.initializer == null) { |
| fail( |
| node, 'Local variables declarated with var must have initializers'); |
| } |
| node.type = visitExpression(node.initializer); |
| } else if (node.initializer != null) { |
| node.type = ensureNoDynamic(node.type); |
| fixupInitializer(node); |
| node.initializer = |
| checkAndDowncastExpression(node.initializer, node.type); |
| } |
| } |
| |
| @override |
| visitWhileStatement(WhileStatement node) { |
| node.condition = |
| checkAndDowncastExpression(node.condition, environment.boolType); |
| visitStatement(node.body); |
| } |
| |
| @override |
| visitYieldStatement(YieldStatement node) { |
| if (node.isYieldStar) { |
| Class container = environment.currentAsyncMarker == AsyncMarker.AsyncStar |
| ? coreTypes.streamClass |
| : coreTypes.iterableClass; |
| var type = visitExpression(node.expression); |
| var asContainer = type is InterfaceType |
| ? hierarchy.getTypeAsInstanceOf(type, container) |
| : null; |
| if (asContainer != null) { |
| checkAssignable(node.expression, asContainer.typeArguments[0], |
| environment.yieldType); |
| } else { |
| fail(node.expression, '$type is not an instance of $container'); |
| } |
| } else { |
| node.expression = |
| checkAndDowncastExpression(node.expression, environment.yieldType); |
| } |
| } |
| |
| @override |
| visitFieldInitializer(FieldInitializer node) { |
| node.value = checkAndDowncastExpression(node.value, node.field.type); |
| } |
| |
| @override |
| visitRedirectingInitializer(RedirectingInitializer node) { |
| handleCall(node.arguments, node.target.function, |
| typeParameters: const <TypeParameter>[]); |
| } |
| |
| @override |
| visitSuperInitializer(SuperInitializer node) { |
| handleCall(node.arguments, node.target.function, |
| typeParameters: const <TypeParameter>[], |
| receiver: getSuperReceiverType(node.target)); |
| } |
| |
| @override |
| visitLocalInitializer(LocalInitializer node) { |
| visitVariableDeclaration(node.variable); |
| } |
| |
| @override |
| visitInvalidInitializer(InvalidInitializer node) {} |
| } |