| 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) {
|
|
|