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..b58a0eb6c5e99eb269c61455a61225bb5b4b5215 100644 |
--- a/sdk/lib/_internal/compiler/implementation/typechecker.dart |
+++ b/sdk/lib/_internal/compiler/implementation/typechecker.dart |
@@ -18,6 +18,22 @@ class TypeCheckerTask extends CompilerTask { |
} |
/** |
+ * Class used to report different warnings for differrent kinds of members. |
+ */ |
+class MemberKind { |
+ static const MemberKind METHOD = const MemberKind("method"); |
+ static const MemberKind OPERATOR = const MemberKind("operator"); |
+ static const MemberKind PROPERTY = const MemberKind("property"); |
+ |
+ final String name; |
+ |
+ const MemberKind(this.name); |
+ |
+ String toString() => name; |
+} |
+ |
+ |
+/** |
* [ElementAccess] represents the access of [element], either as a property |
* access or invocation. |
*/ |
@@ -49,6 +65,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 +78,8 @@ class DynamicAccess implements ElementAccess { |
DartType computeType(Compiler compiler) => compiler.types.dynamicType; |
bool isCallable(Compiler compiler) => true; |
+ |
+ String toString() => 'DynamicAccess'; |
} |
/** |
@@ -73,7 +93,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 +121,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 +147,7 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
Node lastSeenNode; |
DartType expectedReturnType; |
- ClassElement currentClass; |
+ final ClassElement currentClass; |
Link<DartType> cascadeTypes = const Link<DartType>(); |
@@ -109,7 +158,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 +174,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 +217,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 +319,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 +355,8 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
return unhandledStatement(); |
} |
- ElementAccess lookupMethod(Node node, DartType type, SourceString name) { |
+ ElementAccess lookupMember(Node node, DartType type, SourceString name, |
+ MemberKind memberKind) { |
if (identical(type, types.dynamicType)) { |
return const DynamicAccess(); |
} |
@@ -305,17 +364,35 @@ 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 MemberKind.METHOD: |
+ reportTypeWarning(node, MessageKind.METHOD_NOT_FOUND, |
+ {'className': type.name, 'memberName': name}); |
+ break; |
+ case MemberKind.OPERATOR: |
+ reportTypeWarning(node, MessageKind.OPERATOR_NOT_FOUND, |
+ {'className': type.name, 'memberName': name}); |
+ break; |
+ case MemberKind.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, |
+ MemberKind 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)) { |
+ assert(invariant(send, element != null, message: 'No element for $send')); |
+ bool error = false; |
FunctionType funType = unaliasedType; |
Link<DartType> parameterTypes = funType.parameterTypes; |
Link<DartType> optionalParameterTypes = funType.optionalParameterTypes; |
@@ -328,57 +405,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) { |
+ 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 +488,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, |
+ MemberKind memberKind) { |
+ 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, MemberKind 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()) { |
+ // The literal `Foo` where Foo is a class, a typedef, or a type variable. |
+ 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, |
+ MemberKind 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 +606,252 @@ 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); |
+ return elements.getType(node.arguments.head); |
} 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, MemberKind.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, MemberKind.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, MemberKind.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, MemberKind.PROPERTY); |
+ // [operator] is the type of operator+ or operator- on [target]. |
+ DartType operator = |
+ lookupMemberType(node, target, operatorName, MemberKind.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('[]'), MemberKind.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, MemberKind.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('[]='), MemberKind.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('[]='), MemberKind.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, MemberKind.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++ or e-- |
+ 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 +883,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 +962,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 +982,7 @@ class TypeCheckerVisitor implements Visitor<DartType> { |
return types.dynamicType; |
} |
+ // TODO(johnniwinther): Remove this. |
DartType computeType(Element element) { |
if (Elements.isUnresolved(element)) return types.dynamicType; |
DartType result = element.computeType(compiler); |
@@ -652,12 +1005,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 +1086,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) { |