Chromium Code Reviews| Index: sdk/lib/_internal/compiler/implementation/typechecker.dart |
| diff --git a/sdk/lib/_internal/compiler/implementation/typechecker.dart b/sdk/lib/_internal/compiler/implementation/typechecker.dart |
| index 24faf7776fb1274276f8916f7756920112574080..990d241bc8141140ab0eb68f476e9eda8ff573d9 100644 |
| --- a/sdk/lib/_internal/compiler/implementation/typechecker.dart |
| +++ b/sdk/lib/_internal/compiler/implementation/typechecker.dart |
| @@ -49,6 +49,8 @@ class MemberAccess extends ElementAccess { |
| Element get element => member.element; |
| DartType computeType(Compiler compiler) => member.computeType(compiler); |
| + |
| + String toString() => 'MemberAccess($member)'; |
| } |
| /// An access of an unresolved element. |
| @@ -60,6 +62,8 @@ class DynamicAccess implements ElementAccess { |
| DartType computeType(Compiler compiler) => compiler.types.dynamicType; |
| bool isCallable(Compiler compiler) => true; |
| + |
| + String toString() => 'DynamicAccess'; |
| } |
| /** |
| @@ -73,7 +77,19 @@ class ResolvedAccess extends ElementAccess { |
| assert(element != null); |
| } |
| - DartType computeType(Compiler compiler) => element.computeType(compiler); |
| + DartType computeType(Compiler compiler) { |
| + if (element.isGetter()) { |
| + FunctionType functionType = element.computeType(compiler); |
| + return functionType.returnType; |
| + } else if (element.isSetter()) { |
| + FunctionType functionType = element.computeType(compiler); |
| + return functionType.parameterTypes.head; |
| + } else { |
| + return element.computeType(compiler); |
| + } |
| + } |
| + |
| + String toString() => 'ResolvedAccess($element)'; |
| } |
| /** |
| @@ -89,6 +105,23 @@ class TypeAccess extends ElementAccess { |
| Element get element => type.element; |
| DartType computeType(Compiler compiler) => type; |
| + |
| + String toString() => 'TypeAccess($type)'; |
| +} |
| + |
| +/** |
| + * An access of a type literal. |
| + */ |
| +class TypeLiteralAccess extends ElementAccess { |
| + final Element element; |
| + TypeLiteralAccess(Element this.element) { |
| + assert(element != null); |
| + } |
| + |
| + DartType computeType(Compiler compiler) => |
| + compiler.typeClass.computeType(compiler); |
| + |
| + String toString() => 'TypeLiteralAccess($element)'; |
| } |
| class TypeCheckerVisitor implements Visitor<DartType> { |
| @@ -98,7 +131,7 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| Node lastSeenNode; |
| DartType expectedReturnType; |
| - ClassElement currentClass; |
| + final ClassElement currentClass; |
| Link<DartType> cascadeTypes = const Link<DartType>(); |
| @@ -109,7 +142,10 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| DartType objectType; |
| DartType listType; |
| - TypeCheckerVisitor(this.compiler, this.elements, this.types) { |
| + TypeCheckerVisitor(this.compiler, TreeElements elements, this.types) |
| + : this.elements = elements, |
| + currentClass = elements.currentElement != null |
| + ? elements.currentElement.getEnclosingClass() : null { |
| intType = compiler.intClass.computeType(compiler); |
| doubleType = compiler.doubleClass.computeType(compiler); |
| boolType = compiler.boolClass.computeType(compiler); |
| @@ -122,6 +158,11 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| compiler.reportWarning(node, new TypeWarning(kind, arguments)); |
| } |
| + reportTypeInfo(Spannable node, MessageKind kind, [Map arguments = const {}]) { |
| + compiler.reportDiagnostic(compiler.spanFromSpannable(node), |
| + 'Info: ${kind.message(arguments)}', api.Diagnostic.INFO); |
| + } |
| + |
| // TODO(karlklose): remove these functions. |
| DartType unhandledStatement() => StatementType.NOT_RETURNING; |
| DartType unhandledExpression() => types.dynamicType; |
| @@ -160,15 +201,17 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| * Check if a value of type t can be assigned to a variable, |
| * parameter or return value of type s. |
| */ |
| - checkAssignable(Node node, DartType s, DartType t) { |
| - if (!types.isAssignable(s, t)) { |
| + bool checkAssignable(Node node, DartType from, DartType to) { |
| + if (!types.isAssignable(from, to)) { |
| reportTypeWarning(node, MessageKind.NOT_ASSIGNABLE, |
| - {'fromType': s, 'toType': t}); |
| + {'fromType': from, 'toType': to}); |
| + return false; |
| } |
| + return true; |
| } |
| checkCondition(Expression condition) { |
| - checkAssignable(condition, boolType, analyze(condition)); |
| + checkAssignable(condition, analyze(condition), boolType); |
| } |
| void pushCascadeType(DartType type) { |
| @@ -260,7 +303,6 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| } |
| DartType previous = expectedReturnType; |
| expectedReturnType = returnType; |
| - if (element.isMember()) currentClass = element.getEnclosingClass(); |
| StatementType bodyType = analyze(node.body); |
| if (returnType != types.voidType && returnType != types.dynamicType |
| && bodyType != StatementType.RETURNING) { |
| @@ -297,7 +339,12 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| return unhandledStatement(); |
| } |
| - ElementAccess lookupMethod(Node node, DartType type, SourceString name) { |
| + static const METHOD = "method"; |
|
karlklose
2013/05/17 09:36:53
Could we move these to Member or a class MemberKin
Johnni Winther
2013/05/17 11:46:33
Done.
|
| + static const OPERATOR = "operator"; |
| + static const PROPERTY = "property"; |
| + |
| + ElementAccess lookupMember(Node node, DartType type, SourceString name, |
| + var memberKind) { |
| if (identical(type, types.dynamicType)) { |
| return const DynamicAccess(); |
| } |
| @@ -305,17 +352,34 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| if (member != null) { |
| return new MemberAccess(member); |
| } |
| - reportTypeWarning(node, MessageKind.METHOD_NOT_FOUND, |
| - {'className': type.name, 'memberName': name}); |
| + switch (memberKind) { |
| + case METHOD: |
| + reportTypeWarning(node, MessageKind.METHOD_NOT_FOUND, |
| + {'className': type.name, 'memberName': name}); |
| + break; |
| + case OPERATOR: |
| + reportTypeWarning(node, MessageKind.OPERATOR_NOT_FOUND, |
| + {'className': type.name, 'memberName': name}); |
| + break; |
| + case PROPERTY: |
| + reportTypeWarning(node, MessageKind.PROPERTY_NOT_FOUND, |
| + {'className': type.name, 'memberName': name}); |
| + break; |
| + } |
| return const DynamicAccess(); |
| } |
| - // TODO(johnniwinther): Provide the element from which the type came in order |
| - // to give better error messages. |
| - void analyzeArguments(Send send, DartType type) { |
| + DartType lookupMemberType(Node node, DartType type, SourceString name, |
| + var memberKind) { |
| + return lookupMember(node, type, name, memberKind).computeType(compiler); |
| + } |
| + |
| + void analyzeArguments(Send send, Element element, DartType type, |
| + [LinkBuilder<DartType> argumentTypes]) { |
| Link<Node> arguments = send.arguments; |
| DartType unaliasedType = type.unalias(compiler); |
| if (identical(unaliasedType.kind, TypeKind.FUNCTION)) { |
| + bool error = false; |
| FunctionType funType = unaliasedType; |
| Link<DartType> parameterTypes = funType.parameterTypes; |
| Link<DartType> optionalParameterTypes = funType.optionalParameterTypes; |
| @@ -328,57 +392,80 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| DartType namedParameterType = |
| funType.getNamedParameterType(argumentName); |
| if (namedParameterType == null) { |
| + error = true; |
| // TODO(johnniwinther): Provide better information on the called |
| // function. |
| reportTypeWarning(argument, MessageKind.NAMED_ARGUMENT_NOT_FOUND, |
| {'argumentName': argumentName}); |
| - analyze(argument); |
| + DartType argumentType = analyze(argument); |
| + if (argumentTypes != null) argumentTypes.addLast(argumentType); |
| } else { |
| - checkAssignable(argument, namedParameterType, analyze(argument)); |
| + DartType argumentType = analyze(argument); |
| + if (argumentTypes != null) argumentTypes.addLast(argumentType); |
| + if (!checkAssignable(argument, argumentType, namedParameterType)) { |
| + error = true; |
| + } |
| } |
| } else { |
| if (parameterTypes.isEmpty) { |
| if (optionalParameterTypes.isEmpty) { |
| + error = true; |
| // TODO(johnniwinther): Provide better information on the |
| // called function. |
| reportTypeWarning(argument, MessageKind.ADDITIONAL_ARGUMENT); |
| - analyze(argument); |
| + DartType argumentType = analyze(argument); |
| + if (argumentTypes != null) argumentTypes.addLast(argumentType); |
| } else { |
| - checkAssignable(argument, optionalParameterTypes.head, |
| - analyze(argument)); |
| + DartType argumentType = analyze(argument); |
| + if (argumentTypes != null) argumentTypes.addLast(argumentType); |
| + if (!checkAssignable(argument, |
| + argumentType, optionalParameterTypes.head)) { |
| + error = true; |
| + } |
| optionalParameterTypes = optionalParameterTypes.tail; |
| } |
| } else { |
| - checkAssignable(argument, parameterTypes.head, analyze(argument)); |
| + DartType argumentType = analyze(argument); |
| + if (argumentTypes != null) argumentTypes.addLast(argumentType); |
| + if (!checkAssignable(argument, argumentType, parameterTypes.head)) { |
| + error = true; |
| + } |
| parameterTypes = parameterTypes.tail; |
| } |
| } |
| arguments = arguments.tail; |
| } |
| if (!parameterTypes.isEmpty) { |
| + error = true; |
| // TODO(johnniwinther): Provide better information on the called |
| // function. |
| reportTypeWarning(send, MessageKind.MISSING_ARGUMENT, |
| {'argumentType': parameterTypes.head}); |
| } |
| + if (error && element != null) { |
|
karlklose
2013/05/17 09:36:53
When is element == null?
Johnni Winther
2013/05/17 11:46:33
Never at this point. Added an assertion.
|
| + reportTypeInfo(element, MessageKind.THIS_IS_THE_METHOD); |
| + } |
| } else { |
| while(!arguments.isEmpty) { |
| - analyze(arguments.head); |
| + DartType argumentType = analyze(arguments.head); |
| + if (argumentTypes != null) argumentTypes.addLast(argumentType); |
| arguments = arguments.tail; |
| } |
| } |
| } |
| - DartType analyzeInvocation(Send node, ElementAccess elementAccess) { |
| + DartType analyzeInvocation(Send node, ElementAccess elementAccess, |
| + [LinkBuilder<DartType> argumentTypes]) { |
| DartType type = elementAccess.computeType(compiler); |
| if (elementAccess.isCallable(compiler)) { |
| - analyzeArguments(node, type); |
| + analyzeArguments(node, elementAccess.element, type, argumentTypes); |
| } else { |
| reportTypeWarning(node, MessageKind.NOT_CALLABLE, |
| {'elementName': elementAccess.element.name}); |
| - analyzeArguments(node, types.dynamicType); |
| + analyzeArguments(node, elementAccess.element, types.dynamicType, |
| + argumentTypes); |
| } |
| if (identical(type.kind, TypeKind.FUNCTION)) { |
| FunctionType funType = type; |
| @@ -388,6 +475,104 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| } |
| } |
| + /** |
| + * Computes the [ElementAccess] for [name] on the [node] possibly using the |
| + * [element] provided for [node] by the resolver. |
| + */ |
| + ElementAccess computeAccess(Send node, SourceString name, Element element, |
| + var memberKind) { |
|
karlklose
2013/05/17 09:36:53
memberKind is a String (and could be a MemberKind,
Johnni Winther
2013/05/17 11:46:33
Done.
|
| + if (node.receiver != null) { |
| + Element receiverElement = elements[node.receiver]; |
| + if (receiverElement != null) { |
| + if (receiverElement.isPrefix()) { |
| + assert(invariant(node, element != null, |
| + message: 'Prefixed node has no element.')); |
| + return computeResolvedAccess(node, name, element, memberKind); |
| + } |
| + } |
| + // e.foo() for some expression e. |
| + DartType receiverType = analyze(node.receiver); |
| + if (receiverType.isDynamic || |
| + receiverType.isMalformed || |
| + receiverType.isVoid) { |
| + return const DynamicAccess(); |
| + } |
| + TypeKind receiverKind = receiverType.kind; |
| + if (identical(receiverKind, TypeKind.TYPEDEF)) { |
| + // TODO(johnniwinther): handle typedefs. |
| + return const DynamicAccess(); |
| + } |
| + if (identical(receiverKind, TypeKind.FUNCTION)) { |
| + // TODO(johnniwinther): handle functions. |
| + return const DynamicAccess(); |
| + } |
| + if (identical(receiverKind, TypeKind.TYPE_VARIABLE)) { |
| + // TODO(johnniwinther): handle type variables. |
| + return const DynamicAccess(); |
| + } |
| + assert(invariant(node.receiver, |
| + identical(receiverKind, TypeKind.INTERFACE), |
| + message: "interface type expected, got ${receiverKind}")); |
| + return lookupMember(node, receiverType, name, memberKind); |
| + } else { |
| + return computeResolvedAccess(node, name, element, memberKind); |
| + } |
| + } |
| + |
| + /** |
| + * Computes the [ElementAccess] for [name] on the [node] using the [element] |
| + * provided for [node] by the resolver. |
| + */ |
| + ElementAccess computeResolvedAccess(Send node, SourceString name, |
| + Element element, var memberKind) { |
| + if (Elements.isUnresolved(element)) { |
| + // foo() where foo is unresolved. |
| + return const DynamicAccess(); |
| + } else if (element.isMember()) { |
| + // foo() where foo is an instance member. |
| + return lookupMember(node, currentClass.computeType(compiler), |
| + name, memberKind); |
| + } else if (element.isFunction()) { |
| + // foo() where foo is a method in the same class. |
| + return new ResolvedAccess(element); |
| + } else if (element.isVariable() || |
| + element.isParameter() || |
| + element.isField()) { |
| + // foo() where foo is a field in the same class. |
| + return new ResolvedAccess(element); |
| + } else if (element.impliesType()) { |
| + // foo where foo is a type. |
|
karlklose
2013/05/17 09:36:53
Perhaps "The literal `Foo` where Foo is a type or
Johnni Winther
2013/05/17 11:46:33
Done.
|
| + if (elements.getType(node) != null) { |
| + assert(invariant(node, identical(compiler.typeClass, |
| + elements.getType(node).element), |
| + message: 'Expected type literal type: ' |
| + '${elements.getType(node)}')); |
| + return new TypeLiteralAccess(element); |
| + } |
| + return new ResolvedAccess(element); |
| + } else if (element.isGetter() || element.isSetter()) { |
| + return new ResolvedAccess(element); |
| + } else { |
| + compiler.internalErrorOnElement( |
| + element, 'unexpected element kind ${element.kind}'); |
| + } |
| + } |
| + |
| + /** |
| + * Computes the type of the access of [name] on the [node] possibly using the |
| + * [element] provided for [node] by the resolver. |
| + */ |
| + DartType computeAccessType(Send node, SourceString name, Element element, |
| + var memberKind) { |
| + DartType type = |
| + computeAccess(node, name, element, memberKind).computeType(compiler); |
| + if (type == null) { |
| + compiler.internalError('type is null on access of $name on $node', |
| + node: node); |
| + } |
| + return type; |
| + } |
| + |
| DartType visitSend(Send node) { |
| Element element = elements[node]; |
| @@ -408,117 +593,254 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| if (node.isOperator && identical(name, 'is')) { |
| analyze(node.receiver); |
| return boolType; |
| + } if (node.isOperator && identical(name, 'as')) { |
| + analyze(node.receiver); |
| + DartType type = elements.getType(node.arguments.head); |
| + assert(invariant(node, type != null, message: 'No type set for $node')); |
|
karlklose
2013/05/17 09:36:53
Don't we need the same invariant for 'is'? Also, a
Johnni Winther
2013/05/17 11:46:33
Removed.
|
| + return type; |
| } else if (node.isOperator) { |
| - final Node firstArgument = node.receiver; |
| - final DartType firstArgumentType = analyze(node.receiver); |
| - final arguments = node.arguments; |
| - final Node secondArgument = arguments.isEmpty ? null : arguments.head; |
| - final DartType secondArgumentType = |
| - analyzeWithDefault(secondArgument, null); |
| - |
| - if (identical(name, '+') || identical(name, '=') || |
| - identical(name, '-') || identical(name, '*') || |
| - identical(name, '/') || identical(name, '%') || |
| - identical(name, '~/') || identical(name, '|') || |
| - identical(name, '&') || identical(name, '^') || |
| - identical(name, '~')|| identical(name, '<<') || |
| - identical(name, '>>') || identical(name, '[]')) { |
| - return types.dynamicType; |
| - } else if (identical(name, '<') || identical(name, '>') || |
| - identical(name, '<=') || identical(name, '>=') || |
| - identical(name, '==') || identical(name, '!=') || |
| - identical(name, '===') || identical(name, '!==')) { |
| + final Node receiver = node.receiver; |
| + final DartType receiverType = analyze(receiver); |
| + if (identical(name, '==') || identical(name, '!=') |
| + // TODO(johnniwinther): Remove these. |
| + || identical(name, '===') || identical(name, '!==')) { |
| + // Analyze argument. |
| + analyze(node.arguments.head); |
| return boolType; |
| } else if (identical(name, '||') || |
| - identical(name, '&&') || |
| - identical(name, '!')) { |
| - checkAssignable(firstArgument, boolType, firstArgumentType); |
| - if (!arguments.isEmpty) { |
| - // TODO(karlklose): check number of arguments in validator. |
| - checkAssignable(secondArgument, boolType, secondArgumentType); |
| - } |
| + identical(name, '&&')) { |
| + checkAssignable(receiver, receiverType, boolType); |
| + final Node argument = node.arguments.head; |
| + final DartType argumentType = analyze(argument); |
| + checkAssignable(argument, argumentType, boolType); |
| + return boolType; |
| + } else if (identical(name, '!')) { |
| + checkAssignable(receiver, receiverType, boolType); |
| + return boolType; |
| + } else if (identical(name, '?')) { |
| return boolType; |
| - } else { |
| - return unhandledExpression(); |
| } |
| - |
| - } else if (node.isPropertyAccess) { |
| - if (node.receiver != null) { |
| - // TODO(karlklose): we cannot handle fields. |
| - return unhandledExpression(); |
| + SourceString operatorName = selector.source; |
| + if (identical(name, '-') && node.arguments.isEmpty) { |
| + operatorName = const SourceString('unary-'); |
| } |
| - if (element == null) return types.dynamicType; |
| - return computeType(element); |
| - |
| + assert(invariant(node, |
| + identical(name, '+') || identical(name, '=') || |
| + identical(name, '-') || identical(name, '*') || |
| + identical(name, '/') || identical(name, '%') || |
| + identical(name, '~/') || identical(name, '|') || |
| + identical(name, '&') || identical(name, '^') || |
| + identical(name, '~')|| identical(name, '<<') || |
| + identical(name, '>>') || |
| + identical(name, '<') || identical(name, '>') || |
| + identical(name, '<=') || identical(name, '>=') || |
| + identical(name, '[]'), |
| + message: 'Unexpected operator $name')); |
| + |
| + ElementAccess access = lookupMember(node, receiverType, |
| + operatorName, OPERATOR); |
| + LinkBuilder<DartType> argumentTypesBuilder = new LinkBuilder<DartType>(); |
| + DartType resultType = |
| + analyzeInvocation(node, access, argumentTypesBuilder); |
| + if (identical(receiverType.element, compiler.intClass)) { |
| + if (identical(name, '+') || |
| + identical(operatorName, const SourceString('-')) || |
| + identical(name, '*') || |
| + identical(name, '%')) { |
| + DartType argumentType = argumentTypesBuilder.toLink().head; |
| + if (identical(argumentType.element, compiler.intClass)) { |
| + return intType; |
| + } else if (identical(argumentType.element, compiler.doubleClass)) { |
| + return doubleType; |
| + } |
| + } |
| + } |
| + return resultType; |
| + } else if (node.isPropertyAccess) { |
| + ElementAccess access = |
| + computeAccess(node, selector.source, element, PROPERTY); |
| + return access.computeType(compiler); |
| } else if (node.isFunctionObjectInvocation) { |
| return unhandledExpression(); |
| } else { |
| - ElementAccess computeMethod() { |
| - if (node.receiver != null) { |
| - // e.foo() for some expression e. |
| - DartType receiverType = analyze(node.receiver); |
| - if (receiverType.element == compiler.dynamicClass || |
| - receiverType == null || |
| - receiverType.isMalformed || |
| - receiverType.isVoid) { |
| - return const DynamicAccess(); |
| - } |
| - TypeKind receiverKind = receiverType.kind; |
| - if (identical(receiverKind, TypeKind.TYPEDEF)) { |
| - // TODO(karlklose): handle typedefs. |
| - return const DynamicAccess(); |
| - } |
| - if (identical(receiverKind, TypeKind.TYPE_VARIABLE)) { |
| - // TODO(karlklose): handle type variables. |
| - return const DynamicAccess(); |
| - } |
| - if (identical(receiverKind, TypeKind.FUNCTION)) { |
| - // TODO(karlklose): handle getters. |
| - return const DynamicAccess(); |
| + ElementAccess access = |
| + computeAccess(node, selector.source, element, METHOD); |
| + return analyzeInvocation(node, access); |
| + } |
| + } |
| + |
| + /** |
| + * Checks [: target o= value :] for some operator o, and returns the type |
| + * of the result. This method also handles increment/decrement expressions |
| + * like [: target++ :]. |
| + */ |
| + DartType checkAssignmentOperator(SendSet node, |
| + SourceString operatorName, |
| + Node valueNode, |
| + DartType value) { |
| + assert(invariant(node, !node.isIndex)); |
| + Element element = elements[node]; |
| + Identifier selector = node.selector; |
| + DartType target = |
| + computeAccessType(node, selector.source, element, PROPERTY); |
| + // [operator] is the type of operator+ or operator- on [target]. |
| + DartType operator = |
| + lookupMemberType(node, target, operatorName, OPERATOR); |
| + if (operator is FunctionType) { |
| + FunctionType operatorType = operator; |
| + // [result] is the type of target o value. |
| + DartType result = operatorType.returnType; |
| + DartType operatorArgument = operatorType.parameterTypes.head; |
| + // Check target o value. |
| + bool validValue = checkAssignable(valueNode, value, operatorArgument); |
| + if (validValue || !(node.isPrefix || node.isPostfix)) { |
| + // Check target = result. |
| + checkAssignable(node.assignmentOperator, result, target); |
| + } |
| + return node.isPostfix ? target : result; |
| + } |
| + return types.dynamicType; |
| + } |
| + |
| + /** |
| + * Checks [: base[key] o= value :] for some operator o, and returns the type |
| + * of the result. This method also handles increment/decrement expressions |
| + * like [: base[key]++ :]. |
| + */ |
| + DartType checkIndexAssignmentOperator(SendSet node, |
| + SourceString operatorName, |
| + Node valueNode, |
| + DartType value) { |
| + assert(invariant(node, node.isIndex)); |
| + final DartType base = analyze(node.receiver); |
| + final Node keyNode = node.arguments.head; |
| + final DartType key = analyze(keyNode); |
| + |
| + // [indexGet] is the type of operator[] on [base]. |
| + DartType indexGet = lookupMemberType( |
| + node, base, const SourceString('[]'), OPERATOR); |
| + if (indexGet is FunctionType) { |
| + FunctionType indexGetType = indexGet; |
| + DartType indexGetKey = indexGetType.parameterTypes.head; |
| + // Check base[key]. |
| + bool validKey = checkAssignable(keyNode, key, indexGetKey); |
| + |
| + // [element] is the type of base[key]. |
| + DartType element = indexGetType.returnType; |
| + // [operator] is the type of operator o on [element]. |
| + DartType operator = lookupMemberType( |
| + node, element, operatorName, OPERATOR); |
| + if (operator is FunctionType) { |
| + FunctionType operatorType = operator; |
| + |
| + // Check base[key] o value. |
| + DartType operatorArgument = operatorType.parameterTypes.head; |
| + bool validValue = checkAssignable(valueNode, value, operatorArgument); |
| + |
| + // [result] is the type of base[key] o value. |
| + DartType result = operatorType.returnType; |
| + |
| + // [indexSet] is the type of operator[]= on [base]. |
| + DartType indexSet = lookupMemberType( |
| + node, base, const SourceString('[]='), OPERATOR); |
| + if (indexSet is FunctionType) { |
| + FunctionType indexSetType = indexSet; |
| + DartType indexSetKey = indexSetType.parameterTypes.head; |
| + DartType indexSetValue = |
| + indexSetType.parameterTypes.tail.head; |
| + |
| + if (validKey || indexGetKey != indexSetKey) { |
| + // Only check base[key] on []= if base[key] was valid for [] or |
| + // if the key types differ. |
| + checkAssignable(keyNode, key, indexSetKey); |
| } |
| - assert(invariant(node.receiver, |
| - identical(receiverKind, TypeKind.INTERFACE), |
| - message: "interface type expected, got ${receiverKind}")); |
| - return lookupMethod(selector, receiverType, selector.source); |
| - } else { |
| - if (Elements.isUnresolved(element)) { |
| - // foo() where foo is unresolved. |
| - return const DynamicAccess(); |
| - } else if (element.isFunction()) { |
| - // foo() where foo is a method in the same class. |
| - return new ResolvedAccess(element); |
| - } else if (element.isVariable() || element.isField()) { |
| - // foo() where foo is a field in the same class. |
| - return new ResolvedAccess(element); |
| - } else if (element.isGetter()) { |
| - // TODO(karlklose): handle getters. |
| - return const DynamicAccess(); |
| - } else if (element.isClass()) { |
| - // TODO(karlklose): handle type literals. |
| - return const DynamicAccess(); |
| - } else { |
| - compiler.internalErrorOnElement( |
| - element, 'unexpected element kind ${element.kind}'); |
| + // Check base[key] = result |
| + if (validValue || !(node.isPrefix || node.isPostfix)) { |
| + checkAssignable(node.assignmentOperator, result, indexSetValue); |
| } |
| } |
| + return node.isPostfix ? element : result; |
| } |
| - return analyzeInvocation(node, computeMethod()); |
| } |
| + return types.dynamicType; |
| } |
| visitSendSet(SendSet node) { |
| + Element element = elements[node]; |
| Identifier selector = node.selector; |
| final name = node.assignmentOperator.source.stringValue; |
| - if (identical(name, '++') || identical(name, '--')) { |
| - final Element element = elements[node.selector]; |
| - final DartType receiverType = computeType(element); |
| - // TODO(karlklose): this should be the return type instead of int. |
| - return node.isPrefix ? intType : receiverType; |
| + if (identical(name, '=')) { |
| + // e1 = value |
| + if (node.isIndex) { |
| + // base[key] = value |
| + final DartType base = analyze(node.receiver); |
| + final Node keyNode = node.arguments.head; |
| + final DartType key = analyze(keyNode); |
| + final Node valueNode = node.arguments.tail.head; |
| + final DartType value = analyze(valueNode); |
| + DartType indexSet = lookupMemberType( |
| + node, base, const SourceString('[]='), OPERATOR); |
| + if (indexSet is FunctionType) { |
| + FunctionType indexSetType = indexSet; |
| + DartType indexSetKey = indexSetType.parameterTypes.head; |
| + checkAssignable(keyNode, key, indexSetKey); |
| + DartType indexSetValue = indexSetType.parameterTypes.tail.head; |
| + checkAssignable(node.assignmentOperator, value, indexSetValue); |
| + } |
| + return value; |
| + } else { |
| + // target = value |
| + DartType target = |
| + computeAccessType(node, selector.source, element, PROPERTY); |
| + final Node valueNode = node.arguments.head; |
| + final DartType value = analyze(valueNode); |
| + checkAssignable(node.assignmentOperator, value, target); |
| + return value; |
| + } |
| + } else if (identical(name, '++') || identical(name, '--')) { |
| + // e++ |
|
karlklose
2013/05/17 09:36:53
or 'e--'.
Johnni Winther
2013/05/17 11:46:33
Done.
|
| + SourceString operatorName = identical(name, '++') |
| + ? const SourceString('+') : const SourceString('-'); |
| + if (node.isIndex) { |
| + // base[key]++, base[key]--, ++base[key], or --base[key] |
| + return checkIndexAssignmentOperator( |
| + node, operatorName, node.assignmentOperator, intType); |
| + } else { |
| + // target++, target--, ++target, or --target |
| + return checkAssignmentOperator( |
| + node, operatorName, node.assignmentOperator, intType); |
| + } |
| } else { |
| - DartType targetType = computeType(elements[node]); |
| - Node value = node.arguments.head; |
| - checkAssignable(value, targetType, analyze(value)); |
| - return targetType; |
| + // e1 o= e2 for some operator o. |
| + SourceString operatorName; |
| + switch (name) { |
| + case '+=': operatorName = const SourceString('+'); break; |
| + case '-=': operatorName = const SourceString('-'); break; |
| + case '*=': operatorName = const SourceString('*'); break; |
| + case '/=': operatorName = const SourceString('/'); break; |
| + case '%=': operatorName = const SourceString('%'); break; |
| + case '~/=': operatorName = const SourceString('~/'); break; |
| + case '&=': operatorName = const SourceString('&'); break; |
| + case '|=': operatorName = const SourceString('|'); break; |
| + case '^=': operatorName = const SourceString('^'); break; |
| + case '<<=': operatorName = const SourceString('<<'); break; |
| + case '>>=': operatorName = const SourceString('>>'); break; |
| + default: |
| + compiler.internalError( |
| + 'Unexpected assignment operator $name', node: node); |
| + } |
| + if (node.isIndex) { |
| + // base[key] o= value for some operator o. |
| + final Node valueNode = node.arguments.tail.head; |
| + final DartType value = analyze(valueNode); |
| + return checkIndexAssignmentOperator( |
| + node, operatorName, valueNode, value); |
| + } else { |
| + // target o= value for some operator o. |
| + final Node valueNode = node.arguments.head; |
| + final DartType value = analyze(valueNode); |
| + return checkAssignmentOperator(node, operatorName, valueNode, value); |
| + } |
| } |
| } |
| @@ -550,11 +872,30 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| DartType visitNewExpression(NewExpression node) { |
| Element element = elements[node.send]; |
| - analyzeArguments(node.send, computeType(element)); |
| - return analyze(node.send.selector); |
| + DartType constructorType = computeType(element); |
| + DartType newType = elements.getType(node); |
| + // TODO(johnniwinther): Use [:lookupMember:] to account for type variable |
| + // substitution of parameter types. |
| + if (identical(newType.kind, TypeKind.INTERFACE)) { |
| + InterfaceType newInterfaceType = newType; |
| + constructorType = constructorType.subst( |
| + newInterfaceType.typeArguments, |
| + newInterfaceType.element.typeVariables); |
| + } |
| + analyzeArguments(node.send, element, constructorType); |
| + return newType; |
| } |
| DartType visitLiteralList(LiteralList node) { |
| + InterfaceType listType = elements.getType(node); |
| + DartType listElementType = listType.typeArguments.head; |
| + for (Link<Node> link = node.elements.nodes; |
| + !link.isEmpty; |
| + link = link.tail) { |
| + Node element = link.head; |
| + DartType elementType = analyze(element); |
| + checkAssignable(element, elementType, listElementType); |
| + } |
| return listType; |
| } |
| @@ -610,7 +951,7 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| && !types.isAssignable(expressionType, types.voidType)) { |
| reportTypeWarning(expression, MessageKind.RETURN_VALUE_IN_VOID); |
| } else { |
| - checkAssignable(expression, expectedReturnType, expressionType); |
| + checkAssignable(expression, expressionType, expectedReturnType); |
| } |
| // Let f be the function immediately enclosing a return statement of the |
| @@ -630,6 +971,7 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| return types.dynamicType; |
| } |
| + // TODO(johnniwinther): Remove this. |
|
karlklose
2013/05/17 09:36:53
Where do you intent to check for unresolved/malfor
Johnni Winther
2013/05/17 11:46:33
It will (eventually) only be used for FunctionExpr
|
| DartType computeType(Element element) { |
| if (Elements.isUnresolved(element)) return types.dynamicType; |
| DartType result = element.computeType(compiler); |
| @@ -652,12 +994,12 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| } |
| for (Link<Node> link = node.definitions.nodes; !link.isEmpty; |
| link = link.tail) { |
| - Node initialization = link.head; |
| - compiler.ensure(initialization is Identifier |
| - || initialization is Send); |
| - if (initialization is Send) { |
| - DartType initializer = analyzeNonVoid(link.head); |
| - checkAssignable(node, type, initializer); |
| + Node definition = link.head; |
| + compiler.ensure(definition is Identifier || definition is SendSet); |
| + if (definition is Send) { |
| + SendSet initialization = definition; |
| + DartType initializer = analyzeNonVoid(initialization.arguments.head); |
| + checkAssignable(initialization.assignmentOperator, initializer, type); |
| } |
| } |
| return StatementType.NOT_RETURNING; |
| @@ -733,7 +1075,19 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
| } |
| visitLiteralMap(LiteralMap node) { |
| - return unhandledExpression(); |
| + InterfaceType mapType = elements.getType(node); |
| + DartType mapKeyType = mapType.typeArguments.head; |
| + DartType mapValueType = mapType.typeArguments.tail.head; |
| + for (Link<Node> link = node.entries.nodes; |
| + !link.isEmpty; |
| + link = link.tail) { |
| + LiteralMapEntry entry = link.head; |
| + DartType keyType = analyze(entry.key); |
| + checkAssignable(entry.key, keyType, mapKeyType); |
| + DartType valueType = analyze(entry.value); |
| + checkAssignable(entry.value, valueType, mapValueType); |
| + } |
| + return mapType; |
| } |
| visitLiteralMapEntry(LiteralMapEntry node) { |