Index: lib/compiler/implementation/typechecker.dart |
diff --git a/lib/compiler/implementation/typechecker.dart b/lib/compiler/implementation/typechecker.dart |
index 440c60b7f974d75817b8d7a0a1af6f96da027bef..f2716d423262fc4a763e95529d66d4943bf596f7 100644 |
--- a/lib/compiler/implementation/typechecker.dart |
+++ b/lib/compiler/implementation/typechecker.dart |
@@ -29,13 +29,16 @@ interface Type { |
Element get element(); |
/** |
- * Performs the substitution [arguments[i]/parameters[i]]this. |
+ * Performs the substitution `[arguments[i]/parameters[i]]this`, where |
+ * `parameters[i]` denotes the i'th type variable in [typeParameters]. |
* The notation is known from this lambda calculus rule: |
- * (lambda x.e0)e1 -> [e1/x]e0. |
+ * |
+ * (lambda x.e0)e1 -> [e1/x]e0. |
* |
* See [TypeVariableType] for a motivation for this method. |
*/ |
- Type subst(Link<Type> arguments, List<Type> parameters); |
+ Type subst(Compiler compiler, Link<Type> arguments, |
+ LinkedHashMap<SourceString, TypeVariableElement> typeParameters); |
} |
/** |
@@ -65,20 +68,33 @@ class TypeVariableType implements Type { |
TypeVariableElement element; |
TypeVariableType(this.name, [this.element]); |
- Type subst(Link<Type> arguments, List<Type> parameters) { |
- if (arguments.isEmpty()) { |
+ Type subst(Compiler compiler, Link<Type> arguments, |
+ LinkedHashMap<SourceString, TypeVariableElement> typeParameters) { |
+ if (typeParameters.isEmpty()) { |
// Return fast on empty substitutions. |
return this; |
} |
- Link<Type> argumentLink = arguments; |
- Iterator<Type> parameterIterator = parameters.iterator(); |
- while (!argumentLink.isEmpty() && parameterIterator.hasNext()) { |
- Type parameter = parameterIterator.next(); |
- Type argument = argumentLink.head; |
- if (parameter === this) { |
- return argument; |
+ Iterator<TypeVariableElement> parameterIterator = |
+ typeParameters.getValues().iterator(); |
+ if (arguments.isEmpty()) { |
+ // Raw substitution: Dynamic replaces parameters. |
+ while (parameterIterator.hasNext()) { |
+ TypeVariableType parameter = parameterIterator.next().type; |
+ if (parameter == this) { |
+ return compiler.types.dynamicType; |
+ } |
+ } |
+ } else { |
+ // Normal substitution. |
+ Link<Type> argumentLink = arguments; |
+ while (!argumentLink.isEmpty() && parameterIterator.hasNext()) { |
+ TypeVariableType parameter = parameterIterator.next().type; |
+ Type argument = argumentLink.head; |
+ if (parameter == this) { |
+ return argument; |
+ } |
+ argumentLink = argumentLink.tail; |
} |
- argumentLink = argumentLink.tail; |
} |
// The type variable was not substituted. |
return this; |
@@ -107,7 +123,8 @@ class StatementType implements Type { |
return (this === other) ? this : MAYBE_RETURNING; |
} |
- Type subst(Link<Type> arguments, List<Type> parameters) { |
+ Type subst(Compiler compiler, Link<Type> arguments, |
+ LinkedHashMap<SourceString, TypeVariableElement> typeParameters) { |
// Statement types are not substitutable. |
return this; |
} |
@@ -120,7 +137,8 @@ class VoidType implements Type { |
SourceString get name() => element.name; |
final VoidElement element; |
- Type subst(Link<Type> arguments, List<Type> parameters) { |
+ Type subst(Compiler compiler, Link<Type> arguments, |
+ LinkedHashMap<SourceString, TypeVariableElement> typeParameters) { |
// Void cannot be substituted. |
return this; |
} |
@@ -129,33 +147,39 @@ class VoidType implements Type { |
} |
class InterfaceType implements Type { |
- final Element element; |
- final Link<Type> arguments; |
+ final ClassElement element; |
+ final Link<Type> typeArguments; |
const InterfaceType(this.element, |
- [this.arguments = const EmptyLink<Type>()]); |
+ [this.typeArguments = const EmptyLink<Type>()]); |
SourceString get name() => element.name; |
- Type subst(Link<Type> replacements, List<Type> parameters) { |
- if (arguments.isEmpty()) { |
+ Type subst(Compiler compiler, Link<Type> arguments, |
+ LinkedHashMap<SourceString, TypeVariableElement> typeParameters) { |
+ if (typeArguments.isEmpty()) { |
// Return fast on non-generic types. |
return this; |
} |
- if (replacements.isEmpty()) { |
+ if (typeParameters.isEmpty()) { |
// Return fast on empty substitutions. |
return this; |
} |
+ if (arguments.isEmpty()) { |
+ // Return the 'raw' type on raw substitutions. |
+ return new InterfaceType(element); |
+ } |
bool changed = false; |
var argumentsBuilder = new LinkBuilder<Type>(); |
- Link<Type> argument = arguments; |
- while (!argument.isEmpty()) { |
- var replacement = argument.head.subst(replacements, parameters); |
- if (!changed && replacement !== argument.head) { |
+ Link<Type> typeArgument = typeArguments; |
+ while (!typeArgument.isEmpty()) { |
+ var argument = |
+ typeArgument.head.subst(compiler, arguments, typeParameters); |
+ if (!changed && argument !== typeArgument.head) { |
changed = true; |
} |
- argumentsBuilder.addLast(replacement); |
- argument = argument.tail; |
+ argumentsBuilder.addLast(argument); |
+ typeArgument = typeArgument.tail; |
} |
if (changed) { |
// Create a new type only if necessary. |
@@ -164,12 +188,41 @@ class InterfaceType implements Type { |
return this; |
} |
+ /** |
+ * Finds the method, field or property on this interface type named [name]. |
+ */ |
+ Member lookupMember(Compiler compiler, SourceString name) { |
+ ClassElement classElement = element; |
+ InterfaceType receiver = this; |
+ InterfaceType declarer = receiver; |
+ Element member = classElement.lookupLocalMember(name); |
+ if (member === null) { |
+ classElement.ensureResolved(compiler); |
+ for (Link<InterfaceType> supertypes = classElement.allSupertypes; |
+ !supertypes.isEmpty() && member === null; |
+ supertypes = supertypes.tail) { |
+ declarer = supertypes.head; |
+ ClassElement lookupTarget = declarer.element; |
+ member = lookupTarget.lookupLocalMember(name); |
+ } |
+ } |
+ if (member == null) { |
+ return null; |
+ } |
+ if (member.kind == ElementKind.FUNCTION || |
+ member.kind == ElementKind.ABSTRACT_FIELD || |
+ member.kind == ElementKind.FIELD) { |
+ return new Member(receiver, declarer, member); |
+ } |
+ return null; |
+ } |
+ |
String toString() { |
StringBuffer sb = new StringBuffer(); |
sb.add(name.slowToString()); |
- if (!arguments.isEmpty()) { |
+ if (!typeArguments.isEmpty()) { |
sb.add('<'); |
- arguments.printOn(sb, ', '); |
+ typeArguments.printOn(sb, ', '); |
sb.add('>'); |
} |
return sb.toString(); |
@@ -184,26 +237,28 @@ class FunctionType implements Type { |
const FunctionType(Type this.returnType, Link<Type> this.parameterTypes, |
Element this.element); |
- Type subst(Link<Type> arguments, List<Type> parameters) { |
- if (arguments.isEmpty()) { |
+ Type subst(Compiler compiler, Link<Type> arguments, |
+ LinkedHashMap<SourceString, TypeVariableElement> typeParameters) { |
+ if (typeParameters.isEmpty()) { |
// Return fast on empty substitutions. |
return this; |
} |
- var newReturnType = returnType.subst(arguments, parameters); |
+ var newReturnType = returnType.subst(compiler, arguments, typeParameters); |
bool changed = newReturnType !== returnType; |
- var parameterBuilder = new LinkBuilder<Type>(); |
+ var newParameterTypesBuilder = new LinkBuilder<Type>(); |
Link<Type> parameterType = parameterTypes; |
while (!parameterType.isEmpty()) { |
- var replacement = parameterType.head.subst(arguments, parameters); |
- if (!changed && replacement !== parameterType.head) { |
+ var newParameterType = |
+ parameterType.head.subst(compiler, arguments, typeParameters); |
+ if (!changed && newParameterType !== parameterType.head) { |
changed = true; |
} |
- parameterBuilder.addLast(replacement); |
+ newParameterTypesBuilder.addLast(newParameterType); |
parameterType = parameterType.tail; |
} |
if (changed) { |
// Create a new type only if necessary. |
- return new FunctionType(newReturnType, parameterBuilder.toLink(), |
+ return new FunctionType(newReturnType, newParameterTypesBuilder.toLink(), |
element); |
} |
return this; |
@@ -227,15 +282,74 @@ class FunctionType implements Type { |
} |
} |
+/** |
+ * Member encapsulates a member (constructor, method, field, property, getter, |
+ * or setter) with the types of the declarer and receiver in order to do |
+ * substitution on the member type. |
+ * |
+ * Consider for instance these classes and the variable `B<String> b`: |
+ * |
+ * class A<E> { |
+ * E field; |
+ * } |
+ * class B<F> extends A<F> {} |
+ * |
+ * In a [Member] for `b.field` the [receiver] is `B<String>` and the declarer |
+ * is `A<F>`, which is the supertype of `B<F>` from which `field` has been |
+ * inherited. To compute the type of `b.field` we must first substitute `E` |
+ * by `F` using the relation between `A<E>` and `A<F>`, and then `F` by `String` |
+ * using the relation between `B<F>` and `B<String>`. |
+ */ |
+class Member { |
+ final InterfaceType receiver; |
+ final InterfaceType declarer; |
+ final Element element; |
+ |
+ Member(this.receiver, this.declarer, this.element); |
+ |
+ Type computeType(Compiler compiler) { |
+ Type type; |
+ if (element.kind == ElementKind.ABSTRACT_FIELD) { |
+ AbstractFieldElement abstractFieldElement = element; |
+ if (abstractFieldElement.getter != null) { |
+ type = abstractFieldElement.getter.computeType(compiler).returnType; |
+ } else { |
+ type = abstractFieldElement.setter.computeType( |
+ compiler).parameterTypes.head; |
+ if (type == null) { |
+ type = compiler.types.dynamicType; |
+ } |
+ } |
+ } else { |
+ type = element.computeType(compiler); |
+ } |
+ if (!declarer.element.typeParameters.isEmpty()) { |
+ type = type.subst(compiler, |
+ declarer.typeArguments, declarer.element.typeParameters); |
+ type = type.subst(compiler, |
+ receiver.typeArguments, receiver.element.typeParameters); |
+ } |
+ return type; |
+ } |
+ |
+ String toString() { |
+ return '$receiver.$element'; |
+ } |
+} |
+ |
class Types { |
final VoidType voidType; |
final InterfaceType dynamicType; |
+ final Element functionElement; |
- Types(Element dynamicElement) |
- : this.with(dynamicElement, new LibraryElement(new Script(null, null))); |
+ Types(Element dynamicElement, Element functionElement) |
+ : this.with(dynamicElement, functionElement, |
+ new LibraryElement(new Script(null, null))); |
// TODO(karlklose): should we have a class Void? |
- Types.with(Element dynamicElement, LibraryElement library) |
+ Types.with(Element dynamicElement, |
+ Element this.functionElement, |
+ LibraryElement library) |
: voidType = new VoidType(new VoidElement(library)), |
dynamicType = new InterfaceType(dynamicElement); |
@@ -258,6 +372,7 @@ class Types { |
} |
return false; |
} else if (t is FunctionType) { |
+ if (s.element == functionElement) return true; |
if (s is !FunctionType) return false; |
FunctionType tf = t; |
FunctionType sf = s; |
@@ -318,9 +433,12 @@ class TypeCheckerVisitor implements Visitor<Type> { |
listType = compiler.listClass.computeType(compiler); |
} |
- Type fail(node, [reason]) { |
+ Type fail(Node node, [reason]) { |
String message = 'cannot type-check'; |
- if (reason !== null) { |
+ if (node != null) { |
+ message = '$message ${node.getObjectDescription()} `$node`'; |
+ } |
+ if (reason != null) { |
message = '$message: $reason'; |
} |
throw new CancelTypeCheckException(node, message); |
@@ -506,6 +624,37 @@ class TypeCheckerVisitor implements Visitor<Type> { |
return types.dynamicType; |
} |
+ /** |
+ * Returns the interface type on which to lookup members. If [type] is the |
+ * function of a getter, the return type is returned. If [type] is a type |
+ * variable the bound is returned. |
+ */ |
+ InterfaceType findReceiverType(Node receiver, Type receiverType) { |
+ if (receiverType === null) { |
+ fail(receiver, 'receivertype is null'); |
+ } |
+ if (receiverType.element == compiler.dynamicClass) { |
+ return receiverType; |
+ } |
+ ElementKind receiverKind = receiverType.element.kind; |
+ if (receiverKind === ElementKind.GETTER) { |
+ FunctionType getterType = receiverType; |
+ return findReceiverType(receiver, getterType.returnType); |
+ } |
+ if (receiverKind === ElementKind.TYPEDEF) { |
+ // TODO(karlklose): handle typedefs. |
+ return null; |
+ } |
+ if (receiverKind === ElementKind.TYPE_VARIABLE) { |
+ TypeVariableElement typeVariableElement = receiverType.element; |
+ return findReceiverType(receiver, typeVariableElement.bound); |
+ } |
+ if (receiverKind !== ElementKind.CLASS) { |
+ fail(receiver, 'unexpected receiver kind: ${receiverKind}'); |
+ } |
+ return receiverType; |
+ } |
+ |
void analyzeArguments(Send send, FunctionType funType) { |
Link<Node> arguments = send.arguments; |
if (funType === null || funType === types.dynamicType) { |
@@ -571,56 +720,87 @@ class TypeCheckerVisitor implements Visitor<Type> { |
} else if (node.isPropertyAccess) { |
if (node.receiver !== null) { |
- // TODO(karlklose): we cannot handle fields. |
- return unhandledExpression(); |
- } |
- Element element = elements[node]; |
- if (element === null) return types.dynamicType; |
- return computeType(element); |
+ InterfaceType receiverType = |
+ findReceiverType(node.receiver, analyze(node.receiver)); |
+ if (receiverType === null) return null; |
+ if (receiverType.element === compiler.dynamicClass) { |
+ return types.dynamicType; |
+ } |
+ Member member = receiverType.lookupMember(compiler, selector.source); |
+ if (member === null || |
+ !(member.element.kind === ElementKind.FIELD || |
+ member.element.kind === ElementKind.ABSTRACT_FIELD)) { |
+ reportTypeWarning(selector, MessageKind.PROPERTY_NOT_FOUND, |
+ [receiverType, selector.source]); |
+ } else { |
+ return member.computeType(compiler); |
+ } |
+ return types.dynamicType; |
+ } else { |
+ Element element = elements[node]; |
+ if (element === null) { |
+ return types.dynamicType; |
+ } |
+ if ((element.kind == ElementKind.FIELD || |
+ element.kind == ElementKind.GETTER) && |
+ element.isInstanceMember()) { |
+ // Ensure substitution of declared type variables with inherited type |
+ // variables. |
+ InterfaceType thisType = currentClass.computeType(compiler); |
+ Member member = thisType.lookupMember(compiler, selector.source); |
+ if (member != null) { |
+ // Should always work! |
+ return member.computeType(compiler); |
+ } |
+ compiler.internalError('Could not lookup resolved member ' |
+ '${selector.source} on ${thisType}.'); |
+ } |
+ return computeType(element); |
+ } |
} else if (node.isFunctionObjectInvocation) { |
fail(node.receiver, 'function object invocation unimplemented'); |
} else { |
FunctionType computeFunType() { |
if (node.receiver !== null) { |
- Type receiverType = analyze(node.receiver); |
- if (receiverType.element == compiler.dynamicClass) return null; |
- if (receiverType === null) { |
- fail(node.receiver, 'receivertype is null'); |
- } |
- if (receiverType.element.kind === ElementKind.GETTER) { |
- FunctionType getterType = receiverType; |
- receiverType = getterType.returnType; |
- } |
- ElementKind receiverKind = receiverType.element.kind; |
- if (receiverKind === ElementKind.TYPEDEF) { |
- // TODO(karlklose): handle typedefs. |
+ InterfaceType receiverType = |
+ findReceiverType(node.receiver, analyze(node.receiver)); |
+ if (receiverType === null) return null; |
+ if (receiverType.element === compiler.dynamicClass) return null; |
+ |
+ Member member = receiverType.lookupMember(compiler, selector.source); |
+ if (member === null || member.element.kind !== ElementKind.FUNCTION) { |
+ reportTypeWarning(selector, MessageKind.METHOD_NOT_FOUND, |
+ [receiverType, name]); |
return null; |
} |
- if (receiverKind === ElementKind.TYPE_VARIABLE) { |
- // TODO(karlklose): handle type variables. |
- return null; |
- } |
- if (receiverKind !== ElementKind.CLASS) { |
- fail(node.receiver, 'unexpected receiver kind: ${receiverKind}'); |
- } |
- ClassElement classElement = receiverType.element; |
- // TODO(karlklose): substitute type arguments. |
- Type memberType = |
- lookupMethodType(selector, classElement, selector.source); |
- if (memberType.element === compiler.dynamicClass) return null; |
+ Type memberType = member.computeType(compiler); |
+ if (memberType.element == compiler.dynamicClass) return null; |
+ if (memberType.element == compiler.functionClass) return null; |
return memberType; |
} else { |
Element element = elements[node]; |
if (element === null) { |
fail(node, 'unresolved ${node.selector}'); |
} else if (element.kind === ElementKind.FUNCTION) { |
+ if (element.isInstanceMember()) { |
+ // Ensure substitution of declared type variables with inherited |
+ // type variables. |
+ InterfaceType thisType = currentClass.computeType(compiler); |
+ Member member = thisType.lookupMember(compiler, selector.source); |
+ if (member != null) { |
+ // Should always work! |
+ return member.computeType(compiler); |
+ } |
+ compiler.internalError('Could not lookup resolved member ' |
+ '${selector.source} on ${thisType}.'); |
+ } |
return computeType(element); |
} else if (element.kind === ElementKind.FOREIGN) { |
return null; |
- } else if (element.kind === ElementKind.VARIABLE |
- || element.kind === ElementKind.FIELD) { |
+ } else if (element.kind === ElementKind.VARIABLE || |
+ element.kind === ElementKind.FIELD) { |
// TODO(karlklose): handle object invocations. |
return null; |
} else { |
@@ -677,13 +857,22 @@ class TypeCheckerVisitor implements Visitor<Type> { |
} |
Type visitNewExpression(NewExpression node) { |
+ InterfaceType type = elements.getType(node.getTypeAnnotation()); |
Element element = elements[node.send]; |
- analyzeArguments(node.send, computeType(element)); |
- return analyze(node.send.selector); |
+ var member = new Member(type, |
+ element.enclosingElement.computeType(compiler), |
+ element); |
+ analyzeArguments(node.send, member.computeType(compiler)); |
+ return type; |
} |
Type visitLiteralList(LiteralList node) { |
- return listType; |
+ Type elementType = node.type != null |
+ ? elements[node.type] |
+ : compiler.types.dynamicType; |
+ var linkBuilder = new LinkBuilder<Type>(); |
+ linkBuilder.addLast(elementType); |
+ return new InterfaceType(listType.element, linkBuilder.toLink()); |
} |
Type visitNodeList(NodeList node) { |