content: // Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
  // for details. All rights reserved. Use of this source code is governed by a
  // BSD-style license that can be found in the LICENSE file.
  
  part of dart2js;
  
  class TypeCheckerTask extends CompilerTask {
    TypeCheckerTask(Compiler compiler) : super(compiler);
    String get name => "Type checker";
  
    void check(TreeElements elements) {
      Element element = elements.currentElement;
      compiler.withCurrentElement(element, () {
        measure(() {
          Node tree = element.parseNode(compiler);
          Visitor visitor =
              new TypeCheckerVisitor(compiler, elements, compiler.types);
          tree.accept(visitor);
        });
      });
    }
  }
  
  /**
   * 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 GETTER = const MemberKind("getter");
    static const MemberKind SETTER = const MemberKind("setter");
  
    final String name;
  
    const MemberKind(this.name);
  
    String toString() => name;
  }
  
  /**
   * [ElementAccess] represents the access of [element], either as a property
   * access or invocation.
   */
  abstract class ElementAccess {
    Element get element;
  
    DartType computeType(Compiler compiler);
  
    /// Returns [: true :] if the element can be access as an invocation.
    bool isCallable(Compiler compiler) {
      if (element.isAbstractField()) {
        AbstractFieldElement abstractFieldElement = element;
        if (abstractFieldElement.getter == null) {
          // Setters cannot be invoked as function invocations.
          return false;
        }
      }
      return compiler.types.isAssignable(
          computeType(compiler), compiler.functionClass.computeType(compiler));
    }
  }
  
  /// An access of a instance member.
  class MemberAccess extends ElementAccess {
    final Member member;
  
    MemberAccess(Member this.member);
  
    Element get element => member.element;
  
    DartType computeType(Compiler compiler) => member.computeType(compiler);
  
    String toString() => 'MemberAccess($member)';
  }
  
  /// An access of an unresolved element.
  class DynamicAccess implements ElementAccess {
    const DynamicAccess();
  
    Element get element => null;
  
    DartType computeType(Compiler compiler) => compiler.types.dynamicType;
  
    bool isCallable(Compiler compiler) => true;
  
    String toString() => 'DynamicAccess';
  }
  
  /**
   * An access of a resolved top-level or static property or function, or an
   * access of a resolved element through [:this:].
   */
  class ResolvedAccess extends ElementAccess {
    final Element element;
  
    ResolvedAccess(Element this.element) {
      assert(element != null);
    }
  
    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)';
  }
  
  /// An access to a promoted variable.
  class PromotedAccess extends ElementAccess {
    final VariableElement element;
    final DartType type;
  
    PromotedAccess(VariableElement this.element, DartType this.type) {
      assert(element != null);
      assert(type != null);
    }
  
    DartType computeType(Compiler compiler) => type;
  
    String toString() => 'PromotedAccess($element,$type)';
  }
  
  /**
   * An access of a resolved top-level or static property or function, or an
   * access of a resolved element through [:this:].
   */
  class TypeAccess extends ElementAccess {
    final DartType type;
    TypeAccess(DartType this.type) {
      assert(type != null);
    }
  
    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)';
  }
  
  /// An is-expression that potentially promotes a variable.
  class TypePromotion {
    final Send node;
    final VariableElement variable;
    final DartType type;
    final List<TypePromotionMessage> messages = <TypePromotionMessage>[];
  
    TypePromotion(this.node, this.variable, this.type);
  
    bool get isValid => messages.isEmpty;
  
    TypePromotion copy() {
      return new TypePromotion(node, variable, type)..messages.addAll(messages);
    }
  
    void addHint(Spannable spannable, MessageKind kind, [Map arguments]) {
      messages.add(new TypePromotionMessage(api.Diagnostic.HINT,
          spannable, kind, arguments));
    }
  
    void addInfo(Spannable spannable, MessageKind kind, [Map arguments]) {
      messages.add(new TypePromotionMessage(api.Diagnostic.INFO,
          spannable, kind, arguments));
    }
  
    String toString() {
      return 'Promote ${variable} to ${type}';
    }
  }
  
  /// A hint or info message attached to a type promotion.
  class TypePromotionMessage {
    api.Diagnostic diagnostic;
    Spannable spannable;
    MessageKind messageKind;
    Map messageArguments;
  
    TypePromotionMessage(this.diagnostic, this.spannable, this.messageKind,
                         [this.messageArguments]);
  }
  
  class TypeCheckerVisitor extends Visitor<DartType> {
    final Compiler compiler;
    final TreeElements elements;
    final Types types;
  
    Node lastSeenNode;
    DartType expectedReturnType;
  
    final ClassElement currentClass;
  
    InterfaceType thisType;
    InterfaceType superType;
  
    Link<DartType> cascadeTypes = const Link<DartType>();
  
    DartType intType;
    DartType doubleType;
    DartType boolType;
    DartType stringType;
    DartType objectType;
    DartType listType;
  
    Map<Node, List<TypePromotion>> shownTypePromotionsMap =
        new Map<Node, List<TypePromotion>>();
  
    Map<VariableElement, Link<TypePromotion>> typePromotionsMap =
        new Map<VariableElement, Link<TypePromotion>>();
  
    Set<TypePromotion> reportedTypePromotions = new Set<TypePromotion>();
  
    void showTypePromotion(Node node, TypePromotion typePromotion) {
      List<TypePromotion> shownTypePromotions =
          shownTypePromotionsMap.putIfAbsent(node, () => <TypePromotion>[]);
      shownTypePromotions.add(typePromotion);
    }
  
    void registerKnownTypePromotion(TypePromotion typePromotion) {
      VariableElement variable = typePromotion.variable;
      Link<TypePromotion> knownTypes =
          typePromotionsMap.putIfAbsent(variable,
                                        () => const Link<TypePromotion>());
      typePromotionsMap[variable] = knownTypes.prepend(typePromotion);
    }
  
    void unregisterKnownTypePromotion(TypePromotion typePromotion) {
      VariableElement variable = typePromotion.variable;
      Link<TypePromotion> knownTypes = typePromotionsMap[variable].tail;
      if (knownTypes.isEmpty) {
        typePromotionsMap.remove(variable);
      } else {
        typePromotionsMap[variable] = knownTypes;
      }
    }
  
    List<TypePromotion> getShownTypePromotionsFor(Node node) {
      List<TypePromotion> shownTypePromotions = shownTypePromotionsMap[node];
      return shownTypePromotions != null ? shownTypePromotions : const [];
    }
  
    TypePromotion getKnownTypePromotion(VariableElement element) {
      Link<TypePromotion> promotions = typePromotionsMap[element];
      if (promotions != null) {
        while (!promotions.isEmpty) {
          TypePromotion typePromotion = promotions.head;
          if (typePromotion.isValid) {
            return typePromotion;
          }
          promotions = promotions.tail;
        }
      }
      return null;
    }
  
    DartType getKnownType(VariableElement element) {
      TypePromotion typePromotion = getKnownTypePromotion(element);
      if (typePromotion != null) return typePromotion.type;
      return element.computeType(compiler);
    }
  
    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);
      stringType = compiler.stringClass.computeType(compiler);
      objectType = compiler.objectClass.computeType(compiler);
      listType = compiler.listClass.computeType(compiler);
  
      if (currentClass != null) {
        thisType = currentClass.computeType(compiler);
        superType = currentClass.supertype;
      }
    }
  
    LibraryElement get currentLibrary => elements.currentElement.getLibrary();
  
    reportTypeWarning(Node node, MessageKind kind, [Map arguments = const {}]) {
      compiler.reportWarning(
          node, new TypeWarning(kind, arguments, compiler.terseDiagnostics));
    }
  
    reportTypeInfo(Spannable node, MessageKind kind, [Map arguments = const {}]) {
      compiler.reportInfo(node, kind, arguments);
    }
  
    reportTypePromotionHint(TypePromotion typePromotion) {
      if (!reportedTypePromotions.contains(typePromotion)) {
        reportedTypePromotions.add(typePromotion);
        for (TypePromotionMessage message in typePromotion.messages) {
          switch (message.diagnostic) {
            case api.Diagnostic.HINT:
              compiler.reportHint(message.spannable,
                                  message.messageKind,
                                  message.messageArguments);
              break;
            case api.Diagnostic.INFO:
              compiler.reportInfo(message.spannable,
                                  message.messageKind,
                                  message.messageArguments);
              break;
          }
        }
      }
    }
  
    // TODO(karlklose): remove these functions.
    DartType unhandledStatement() => StatementType.NOT_RETURNING;
    DartType unhandledExpression() => types.dynamicType;
  
    DartType analyzeNonVoid(Node node) {
      DartType type = analyze(node);
      if (type == types.voidType) {
        reportTypeWarning(node, MessageKind.VOID_EXPRESSION);
      }
      return type;
    }
  
    DartType analyzeWithDefault(Node node, DartType defaultValue) {
      return node != null ? analyze(node) : defaultValue;
    }
  
    DartType analyze(Node node) {
      if (node == null) {
        final String error = 'unexpected node: null';
        if (lastSeenNode != null) {
          compiler.internalError(error, node: lastSeenNode);
        } else {
          compiler.cancel(error);
        }
      } else {
        lastSeenNode = node;
      }
      DartType result = node.accept(this);
      if (result == null) {
        compiler.internalError('type is null', node: node);
      }
      return result;
    }
  
    void checkTypePromotion(Node node, TypePromotion typePromotion,
                            {bool checkAccesses: false}) {
      VariableElement variable = typePromotion.variable;
      String variableName = variable.name;
      List<Node> potentialMutationsIn =
          elements.getPotentialMutationsIn(node, variable);
      if (!potentialMutationsIn.isEmpty) {
        typePromotion.addHint(typePromotion.node,
            MessageKind.POTENTIAL_MUTATION,
            {'variableName': variableName, 'shownType': typePromotion.type});
        for (Node mutation in potentialMutationsIn) {
          typePromotion.addInfo(mutation,
              MessageKind.POTENTIAL_MUTATION_HERE,
              {'variableName': variableName});
        }
      }
      List<Node> potentialMutationsInClosures =
          elements.getPotentialMutationsInClosure(variable);
      if (!potentialMutationsInClosures.isEmpty) {
        typePromotion.addHint(typePromotion.node,
            MessageKind.POTENTIAL_MUTATION_IN_CLOSURE,
            {'variableName': variableName, 'shownType': typePromotion.type});
        for (Node mutation in potentialMutationsInClosures) {
          typePromotion.addInfo(mutation,
              MessageKind.POTENTIAL_MUTATION_IN_CLOSURE_HERE,
              {'variableName': variableName});
        }
      }
      if (checkAccesses) {
        List<Node> accesses = elements.getAccessesByClosureIn(node, variable);
        List<Node> mutations = elements.getPotentialMutations(variable);
        if (!accesses.isEmpty && !mutations.isEmpty) {
          typePromotion.addHint(typePromotion.node,
              MessageKind.ACCESSED_IN_CLOSURE,
              {'variableName': variableName, 'shownType': typePromotion.type});
          for (Node access in accesses) {
            typePromotion.addInfo(access,
                MessageKind.ACCESSED_IN_CLOSURE_HERE,
                {'variableName': variableName});
          }
          for (Node mutation in mutations) {
            typePromotion.addInfo(mutation,
                MessageKind.POTENTIAL_MUTATION_HERE,
                {'variableName': variableName});
          }
        }
      }
    }
  
    void reshowTypePromotions(Node node, Node receiver, Node argument) {
      for (TypePromotion typePromotion in  getShownTypePromotionsFor(receiver)) {
        typePromotion = typePromotion.copy();
        checkTypePromotion(argument, typePromotion);
        showTypePromotion(node, typePromotion);
      }
  
      for (TypePromotion typePromotion in getShownTypePromotionsFor(argument)) {
        typePromotion = typePromotion.copy();
        checkTypePromotion(argument, typePromotion);
        showTypePromotion(node, typePromotion);
      }
    }
  
    /// Analyze [node] in the context of the known types shown in [context].
    DartType analyzeInPromotedContext(Node context, Node node) {
      Link<TypePromotion> knownForNode = const Link<TypePromotion>();
      for (TypePromotion typePromotion in  getShownTypePromotionsFor(context)) {
        typePromotion = typePromotion.copy();
        checkTypePromotion(node, typePromotion, checkAccesses: true);
        knownForNode = knownForNode.prepend(typePromotion);
        registerKnownTypePromotion(typePromotion);
      }
  
      final DartType type = analyze(node);
  
      while (!knownForNode.isEmpty) {
        unregisterKnownTypePromotion(knownForNode.head);
        knownForNode = knownForNode.tail;
      }
  
      return type;
    }
  
    /**
     * Check if a value of type [from] can be assigned to a variable, parameter or
     * return value of type [to].  If `isConst == true`, an error is emitted in
     * checked mode, otherwise a warning is issued.
     */
    bool checkAssignable(Node node, DartType from, DartType to,
                         {bool isConst: false}) {
      if (!types.isAssignable(from, to)) {
        if (compiler.enableTypeAssertions && isConst) {
          compiler.reportError(node, MessageKind.NOT_ASSIGNABLE.error,
                               {'fromType': from, 'toType': to});
        } else {
          reportTypeWarning(node, MessageKind.NOT_ASSIGNABLE.warning,
                            {'fromType': from, 'toType': to});
        }
        return false;
      }
      return true;
    }
  
    checkCondition(Expression condition) {
      checkAssignable(condition, analyze(condition), boolType);
    }
  
    void pushCascadeType(DartType type) {
      cascadeTypes = cascadeTypes.prepend(type);
    }
  
    DartType popCascadeType() {
      DartType type = cascadeTypes.head;
      cascadeTypes = cascadeTypes.tail;
      return type;
    }
  
    DartType visitBlock(Block node) {
      return analyze(node.statements);
    }
  
    DartType visitCascade(Cascade node) {
      analyze(node.expression);
      if (node.expression.asCascadeReceiver() == null) {
        // TODO(karlklose): bug: expressions of the form e..x = y do not have
        // a CascadeReceiver as expression currently.
        return types.dynamicType;
      }
      return popCascadeType();
    }
  
    DartType visitCascadeReceiver(CascadeReceiver node) {
      DartType type = analyze(node.expression);
      pushCascadeType(type);
      return type;
    }
  
    DartType visitDoWhile(DoWhile node) {
      StatementType bodyType = analyze(node.body);
      checkCondition(node.condition);
      return bodyType.join(StatementType.NOT_RETURNING);
    }
  
    DartType visitExpressionStatement(ExpressionStatement node) {
      Expression expression = node.expression;
      analyze(expression);
      return (expression.asThrow() != null)
          ? StatementType.RETURNING
          : StatementType.NOT_RETURNING;
    }
  
    /** Dart Programming Language Specification: 11.5.1 For Loop */
    DartType visitFor(For node) {
      analyzeWithDefault(node.initializer, StatementType.NOT_RETURNING);
      if (node.condition != null) {
        checkCondition(node.condition);
      }
      analyzeWithDefault(node.update, StatementType.NOT_RETURNING);
      StatementType bodyType = analyze(node.body);
      return bodyType.join(StatementType.NOT_RETURNING);
    }
  
    DartType visitFunctionDeclaration(FunctionDeclaration node) {
      analyze(node.function);
      return StatementType.NOT_RETURNING;
    }
  
    DartType visitFunctionExpression(FunctionExpression node) {
      DartType type;
      DartType returnType;
      DartType previousType;
      final FunctionElement element = elements[node];
      assert(invariant(node, element != null,
                       message: 'FunctionExpression with no element'));
      if (Elements.isUnresolved(element)) return types.dynamicType;
      if (identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR) ||
          identical(element.kind, ElementKind.GENERATIVE_CONSTRUCTOR_BODY)) {
        type = types.dynamicType;
        returnType = types.voidType;
  
        element.functionSignature.forEachParameter((Element parameter) {
          if (parameter.isFieldParameter()) {
            FieldParameterElement fieldParameter = parameter;
            checkAssignable(parameter.parseNode(compiler),
                parameter.computeType(compiler),
                fieldParameter.fieldElement.computeType(compiler));
          }
        });
        if (node.initializers != null) {
          analyze(node.initializers);
        }
      } else {
        FunctionType functionType = element.computeType(compiler);
        returnType = functionType.returnType;
        type = functionType;
      }
      DartType previous = expectedReturnType;
      expectedReturnType = returnType;
      StatementType bodyType = analyze(node.body);
      if (returnType != types.voidType && !returnType.treatAsDynamic
          && bodyType != StatementType.RETURNING) {
        MessageKind kind;
        if (bodyType == StatementType.MAYBE_RETURNING) {
          kind = MessageKind.MAYBE_MISSING_RETURN;
        } else {
          kind = MessageKind.MISSING_RETURN;
        }
        reportTypeWarning(node.name, kind);
      }
      expectedReturnType = previous;
      return type;
    }
  
    DartType visitIdentifier(Identifier node) {
      if (node.isThis()) {
        return thisType;
      } else if (node.isSuper()) {
        return superType;
      } else {
        Element element = elements[node];
        assert(invariant(node, element != null,
            message: 'Missing element for identifier'));
        assert(invariant(node, element.isVariable() ||
                               element.isParameter() ||
                               element.isField(),
            message: 'Unexpected context element ${element}'));
        return element.computeType(compiler);
      }
    }
  
    DartType visitIf(If node) {
      Expression condition = node.condition.expression;
      Statement thenPart = node.thenPart;
  
      checkCondition(node.condition);
  
      StatementType thenType = analyzeInPromotedContext(condition, thenPart);
  
      StatementType elseType = node.hasElsePart ? analyze(node.elsePart)
                                                : StatementType.NOT_RETURNING;
      return thenType.join(elseType);
    }
  
    void checkPrivateAccess(Node node, Element element, String name) {
      if (name != null &&
          isPrivateName(name) &&
          element.getLibrary() != currentLibrary) {
        reportTypeWarning(
            node,
            MessageKind.PRIVATE_ACCESS,
            {'name': name,
             'libraryName': element.getLibrary().getLibraryOrScriptName()});
      }
  
    }
  
    ElementAccess lookupMember(Node node, DartType receiverType, String name,
                               MemberKind memberKind, Element receiverElement) {
      if (receiverType.treatAsDynamic) {
        return const DynamicAccess();
      }
      Member getMember(DartType type) {
        DartType originalType = type;
        while (identical(type.kind, TypeKind.TYPE_VARIABLE)) {
          TypeVariableType variable = type;
          type = variable.element.bound;
          if (type == originalType) {
            type = compiler.objectClass.rawType;
          }
        }
        if (type.kind == TypeKind.FUNCTION || type.kind == TypeKind.TYPEDEF) {
          // TODO(karlklose): handle calling `call` on the function type. Do we
          // have to type-check the arguments against the function type?
          type = compiler.functionClass.rawType;
        }
        assert(invariant(node, type.kind == TypeKind.INTERFACE,
            message: "unexpected type kind ${type.kind}."));
        InterfaceType interface = type;
        return interface.lookupMember(name,
            isSetter: identical(memberKind, MemberKind.SETTER));
      }
      Member member = getMember(receiverType);
      if (member != null) {
        checkPrivateAccess(node, member.element, name);
        return new MemberAccess(member);
      }
      if (receiverElement != null &&
          (receiverElement.isVariable() || receiverElement.isParameter())) {
        Link<TypePromotion> typePromotions = typePromotionsMap[receiverElement];
        if (typePromotions != null) {
          while (!typePromotions.isEmpty) {
            TypePromotion typePromotion = typePromotions.head;
            if (!typePromotion.isValid) {
              if (getMember(typePromotion.type) != null) {
                reportTypePromotionHint(typePromotion);
              }
            }
            typePromotions = typePromotions.tail;
          }
        }
      }
      switch (memberKind) {
        case MemberKind.METHOD:
          reportTypeWarning(node, MessageKind.METHOD_NOT_FOUND,
              {'className': receiverType.name, 'memberName': name});
          break;
        case MemberKind.OPERATOR:
          reportTypeWarning(node, MessageKind.OPERATOR_NOT_FOUND,
              {'className': receiverType.name, 'memberName': name});
          break;
        case MemberKind.GETTER:
          reportTypeWarning(node, MessageKind.MEMBER_NOT_FOUND.warning,
              {'className': receiverType.name, 'memberName': name});
          break;
        case MemberKind.SETTER:
          reportTypeWarning(node, MessageKind.PROPERTY_NOT_FOUND,
              {'className': receiverType.name, 'memberName': name});
          break;
      }
      return const DynamicAccess();
    }
  
    DartType lookupMemberType(Node node, DartType type, String name,
                              MemberKind memberKind) {
      return lookupMember(node, type, name, memberKind, null).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;
        while (!arguments.isEmpty) {
          Node argument = arguments.head;
          NamedArgument namedArgument = argument.asNamedArgument();
          if (namedArgument != null) {
            argument = namedArgument.expression;
            String argumentName = namedArgument.name.source;
            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});
  
              DartType argumentType = analyze(argument);
              if (argumentTypes != null) argumentTypes.addLast(argumentType);
            } else {
              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);
  
                DartType argumentType = analyze(argument);
                if (argumentTypes != null) argumentTypes.addLast(argumentType);
              } else {
                DartType argumentType = analyze(argument);
                if (argumentTypes != null) argumentTypes.addLast(argumentType);
                if (!checkAssignable(argument,
                                     argumentType, optionalParameterTypes.head)) {
                  error = true;
                }
                optionalParameterTypes = optionalParameterTypes.tail;
              }
            } else {
              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) {
          DartType argumentType = analyze(arguments.head);
          if (argumentTypes != null) argumentTypes.addLast(argumentType);
          arguments = arguments.tail;
        }
      }
    }
  
    DartType analyzeInvocation(Send node, ElementAccess elementAccess,
                               [LinkBuilder<DartType> argumentTypes]) {
      DartType type = elementAccess.computeType(compiler);
      if (elementAccess.isCallable(compiler)) {
        analyzeArguments(node, elementAccess.element, type, argumentTypes);
      } else {
        reportTypeWarning(node, MessageKind.NOT_CALLABLE,
            {'elementName': elementAccess.element.name});
        analyzeArguments(node, elementAccess.element, types.dynamicType,
                         argumentTypes);
      }
      type = type.unalias(compiler);
      if (identical(type.kind, TypeKind.FUNCTION)) {
        FunctionType funType = type;
        return funType.returnType;
      } else {
        return types.dynamicType;
      }
    }
  
    /**
     * Computes the [ElementAccess] for [name] on the [node] possibly using the
     * [element] provided for [node] by the resolver.
     */
    ElementAccess computeAccess(Send node, String name, Element element,
                                MemberKind memberKind) {
      if (element != null && element.isErroneous()) {
        // An error has already been reported for this node.
        return const DynamicAccess();
      }
      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.treatAsDynamic || receiverType.isVoid) {
          return const DynamicAccess();
        }
        TypeKind receiverKind = receiverType.kind;
        return lookupMember(node, receiverType, name, memberKind,
            elements[node.receiver]);
      } 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, String name,
                                        Element element, MemberKind memberKind) {
      if (element == null) {
        // foo() where foo is unresolved.
        return lookupMember(node, thisType, name, memberKind, null);
      } else if (element.isErroneous()) {
        // foo() where foo is erroneous.
        return const DynamicAccess();
      } else if (element.impliesType()) {
        // The literal `Foo` where Foo is a class, a typedef, or a type variable.
        if (elements.isTypeLiteral(node)) {
          assert(invariant(node, identical(compiler.typeClass,
              elements.getType(node).element),
              message: 'Expected type literal type: '
                '${elements.getType(node)}'));
          return new TypeLiteralAccess(element);
        }
        return createResolvedAccess(node, name, element);
      } else if (element.isMember()) {
        // foo() where foo is an instance member.
        return lookupMember(node, thisType, name, memberKind, null);
      } else if (element.isFunction()) {
        // foo() where foo is a method in the same class.
        return createResolvedAccess(node, name, element);
      } else if (element.isVariable() ||
          element.isParameter() ||
          element.isField()) {
        // foo() where foo is a field in the same class.
        return createResolvedAccess(node, name, element);
      } else if (element.isGetter() || element.isSetter()) {
        return createResolvedAccess(node, name, element);
      } else {
        compiler.internalErrorOnElement(
            element, 'unexpected element kind ${element.kind}');
      }
    }
  
    ElementAccess createResolvedAccess(Send node, String name,
                                       Element element) {
      checkPrivateAccess(node, element, name);
      return createPromotedAccess(element);
    }
  
    ElementAccess createPromotedAccess(Element element) {
      if (element.isVariable() || element.isParameter()) {
        TypePromotion typePromotion = getKnownTypePromotion(element);
        if (typePromotion != null) {
          return new PromotedAccess(element, typePromotion.type);
        }
      }
      return new ResolvedAccess(element);
    }
  
    /**
     * 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, String 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];
  
      if (element != null && element.isConstructor()) {
        DartType receiverType;
        if (node.receiver != null) {
          receiverType = analyze(node.receiver);
        } else if (node.selector.isSuper()) {
          receiverType = superType;
        } else {
          assert(node.selector.isThis());
          receiverType = thisType;
        }
        DartType constructorType = computeConstructorType(element, receiverType);
        analyzeArguments(node, element, constructorType);
        return types.dynamicType;
      }
  
      if (Elements.isClosureSend(node, element)) {
        if (element != null) {
          // foo() where foo is a local or a parameter.
          return analyzeInvocation(node, createPromotedAccess(element));
        } else {
          // exp() where exp is some complex expression like (o) or foo().
          DartType type = analyze(node.selector);
          return analyzeInvocation(node, new TypeAccess(type));
        }
      }
  
      Identifier selector = node.selector.asIdentifier();
      String name = selector.source;
  
      if (node.isOperator && identical(name, 'is')) {
        analyze(node.receiver);
        if (!node.isIsNotCheck) {
          Element variable = elements[node.receiver];
          if (variable != null &&
              (variable.isVariable() || variable.isParameter())) {
            DartType knownType = getKnownType(variable);
            if (!knownType.isDynamic) {
              DartType shownType = elements.getType(node.arguments.head);
              TypePromotion typePromotion =
                  new TypePromotion(node, variable, shownType);
              if (!types.isMoreSpecific(shownType, knownType)) {
                String variableName = variable.name;
                // TODO(johnniwinther): Provide a how-to-fix in the case one tries
                // to promote a generic type to a raw type.
                typePromotion.addHint(node,
                    MessageKind.NOT_MORE_SPECIFIC,
                    {'variableName': variableName,
                     'shownType': shownType,
                     'knownType': knownType});
              }
              showTypePromotion(node, typePromotion);
            }
          }
        }
        return boolType;
      } if (node.isOperator && identical(name, 'as')) {
        analyze(node.receiver);
        return elements.getType(node.arguments.head);
      } else if (node.isOperator) {
        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, '||')) {
          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);
          final Node argument = node.arguments.head;
  
          final DartType argumentType =
              analyzeInPromotedContext(receiver, argument);
  
          reshowTypePromotions(node, receiver, argument);
  
          checkAssignable(argument, argumentType, boolType);
          return boolType;
        } else if (identical(name, '!')) {
          checkAssignable(receiver, receiverType, boolType);
          return boolType;
        } else if (identical(name, '?')) {
          return boolType;
        }
        String operatorName = selector.source;
        if (identical(name, '-') && node.arguments.isEmpty) {
          operatorName = 'unary-';
        }
        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'));
  
        // TODO(karlklose): handle `void` in expression context by calling
        // [analyzeNonVoid] instead of [analyze].
        ElementAccess access = receiverType.isVoid ? const DynamicAccess()
            : lookupMember(node, receiverType, operatorName,
                           MemberKind.OPERATOR, null);
        LinkBuilder<DartType> argumentTypesBuilder = new LinkBuilder<DartType>();
        DartType resultType =
            analyzeInvocation(node, access, argumentTypesBuilder);
        if (identical(receiverType.element, compiler.intClass)) {
          if (identical(name, '+') ||
              identical(operatorName, '-') ||
              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.GETTER);
        return access.computeType(compiler);
      } else if (node.isFunctionObjectInvocation) {
        return unhandledExpression();
      } else {
        ElementAccess access =
            computeAccess(node, selector.source, element, MemberKind.METHOD);
        return analyzeInvocation(node, access);
      }
    }
  
    /// Returns the first type in the list or [:dynamic:] if the list is empty.
    DartType firstType(Link<DartType> link) {
      return link.isEmpty ? types.dynamicType : link.head;
    }
  
    /**
     * Returns the second type in the list or [:dynamic:] if the list is too
     * short.
     */
    DartType secondType(Link<DartType> link) {
      return link.isEmpty || link.tail.isEmpty
          ? types.dynamicType : link.tail.head;
    }
  
    /**
     * 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,
                                     String operatorName,
                                     Node valueNode,
                                     DartType value) {
      assert(invariant(node, !node.isIndex));
      Element setterElement = elements[node];
      Element getterElement = elements[node.selector];
      Identifier selector = node.selector;
      DartType getter = computeAccessType(
          node, selector.source, getterElement, MemberKind.GETTER);
      DartType setter = computeAccessType(
          node, selector.source, setterElement, MemberKind.SETTER);
      // [operator] is the type of operator+ or operator- on [target].
      DartType operator =
          lookupMemberType(node, getter, operatorName, MemberKind.OPERATOR);
      if (operator is FunctionType) {
        FunctionType operatorType = operator;
        // [result] is the type of target o value.
        DartType result = operatorType.returnType;
        DartType operatorArgument = firstType(operatorType.parameterTypes);
        // Check target o value.
        bool validValue = checkAssignable(valueNode, value, operatorArgument);
        if (validValue || !(node.isPrefix || node.isPostfix)) {
          // Check target = result.
          checkAssignable(node.assignmentOperator, result, setter);
        }
        return node.isPostfix ? getter : 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,
                                          String 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, '[]', MemberKind.OPERATOR);
      if (indexGet is FunctionType) {
        FunctionType indexGetType = indexGet;
        DartType indexGetKey = firstType(indexGetType.parameterTypes);
        // 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 = firstType(operatorType.parameterTypes);
          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, '[]=', MemberKind.OPERATOR);
          if (indexSet is FunctionType) {
            FunctionType indexSetType = indexSet;
            DartType indexSetKey = firstType(indexSetType.parameterTypes);
            DartType indexSetValue = secondType(indexSetType.parameterTypes);
  
            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);
            }
            // Check base[key] = result
            if (validValue || !(node.isPrefix || node.isPostfix)) {
              checkAssignable(node.assignmentOperator, result, indexSetValue);
            }
          }
          return node.isPostfix ? element : result;
        }
      }
      return types.dynamicType;
    }
  
    visitSendSet(SendSet node) {
      Element element = elements[node];
      Identifier selector = node.selector;
      final name = node.assignmentOperator.source;
      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, '[]=', MemberKind.OPERATOR);
          if (indexSet is FunctionType) {
            FunctionType indexSetType = indexSet;
            DartType indexSetKey = firstType(indexSetType.parameterTypes);
            checkAssignable(keyNode, key, indexSetKey);
            DartType indexSetValue = secondType(indexSetType.parameterTypes);
            checkAssignable(node.assignmentOperator, value, indexSetValue);
          }
          return value;
        } else {
          // target = value
          DartType target = computeAccessType(node, selector.source,
                                              element, MemberKind.SETTER);
          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--
        String operatorName = identical(name, '++') ? '+' : '-';
        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 {
        // e1 o= e2 for some operator o.
        String operatorName;
        switch (name) {
          case '+=': operatorName = '+'; break;
          case '-=': operatorName = '-'; break;
          case '*=': operatorName = '*'; break;
          case '/=': operatorName = '/'; break;
          case '%=': operatorName = '%'; break;
          case '~/=': operatorName = '~/'; break;
          case '&=': operatorName = '&'; break;
          case '|=': operatorName = '|'; break;
          case '^=': operatorName = '^'; break;
          case '<<=': operatorName = '<<'; break;
          case '>>=': operatorName = '>>'; 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);
        }
      }
    }
  
    DartType visitLiteralInt(LiteralInt node) {
      return intType;
    }
  
    DartType visitLiteralDouble(LiteralDouble node) {
      return doubleType;
    }
  
    DartType visitLiteralBool(LiteralBool node) {
      return boolType;
    }
  
    DartType visitLiteralString(LiteralString node) {
      return stringType;
    }
  
    DartType visitStringJuxtaposition(StringJuxtaposition node) {
      analyze(node.first);
      analyze(node.second);
      return stringType;
    }
  
    DartType visitLiteralNull(LiteralNull node) {
      return types.dynamicType;
    }
  
    DartType visitLiteralSymbol(LiteralSymbol node) {
      return compiler.symbolClass.computeType(compiler);
    }
  
    DartType computeConstructorType(Element constructor, DartType type) {
      if (Elements.isUnresolved(constructor)) return types.dynamicType;
      DartType constructorType = constructor.computeType(compiler);
      if (identical(type.kind, TypeKind.INTERFACE)) {
        InterfaceType interfaceType = type;
        constructorType = constructorType.subst(
            interfaceType.typeArguments,
            interfaceType.element.typeVariables);
      }
      return constructorType;
    }
  
    DartType visitNewExpression(NewExpression node) {
      Element element = elements[node.send];
      if (Elements.isUnresolved(element)) return types.dynamicType;
  
      checkPrivateAccess(node, element, element.name);
  
      DartType newType = elements.getType(node);
      DartType constructorType = computeConstructorType(element, newType);
      analyzeArguments(node.send, element, constructorType);
      return newType;
    }
  
    DartType visitLiteralList(LiteralList node) {
      InterfaceType listType = elements.getType(node);
      DartType listElementType = firstType(listType.typeArguments);
      for (Link<Node> link = node.elements.nodes;
           !link.isEmpty;
           link = link.tail) {
        Node element = link.head;
        DartType elementType = analyze(element);
        checkAssignable(element, elementType, listElementType,
            isConst: node.isConst());
      }
      return listType;
    }
  
    DartType visitNodeList(NodeList node) {
      DartType type = StatementType.NOT_RETURNING;
      bool reportedDeadCode = false;
      for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
        DartType nextType = analyze(link.head);
        if (type == StatementType.RETURNING) {
          if (!reportedDeadCode) {
            reportTypeWarning(link.head, MessageKind.UNREACHABLE_CODE);
            reportedDeadCode = true;
          }
        } else if (type == StatementType.MAYBE_RETURNING){
          if (nextType == StatementType.RETURNING) {
            type = nextType;
          }
        } else {
          type = nextType;
        }
      }
      return type;
    }
  
    DartType visitRethrow(Rethrow node) {
      return StatementType.RETURNING;
    }
  
    /** Dart Programming Language Specification: 11.10 Return */
    DartType visitReturn(Return node) {
      if (identical(node.getBeginToken().stringValue, 'native')) {
        return StatementType.RETURNING;
      }
      if (node.isRedirectingFactoryBody) {
        // TODO(lrn): Typecheck the body. It must refer to the constructor
        // of a subtype.
        return StatementType.RETURNING;
      }
  
      final expression = node.expression;
      final isVoidFunction = (identical(expectedReturnType, types.voidType));
  
      // Executing a return statement return e; [...] It is a static type warning
      // if the type of e may not be assigned to the declared return type of the
      // immediately enclosing function.
      if (expression != null) {
        final expressionType = analyze(expression);
        Element element = elements.currentElement;
        if (element != null && element.isGenerativeConstructor()) {
          // The resolver already emitted an error for this expression.
        } else if (isVoidFunction
            && !types.isAssignable(expressionType, types.voidType)) {
          reportTypeWarning(expression, MessageKind.RETURN_VALUE_IN_VOID);
        } else {
          checkAssignable(expression, expressionType, expectedReturnType);
        }
  
      // Let f be the function immediately enclosing a return statement of the
      // form 'return;' It is a static warning if both of the following conditions
      // hold:
      // - f is not a generative constructor.
      // - The return type of f may not be assigned to void.
      } else if (!types.isAssignable(expectedReturnType, types.voidType)) {
        reportTypeWarning(node, MessageKind.RETURN_NOTHING,
                          {'returnType': expectedReturnType});
      }
      return StatementType.RETURNING;
    }
  
    DartType visitThrow(Throw node) {
      // TODO(johnniwinther): Handle reachability.
      analyze(node.expression);
      return types.dynamicType;
    }
  
    DartType visitTypeAnnotation(TypeAnnotation node) {
      return elements.getType(node);
    }
  
    DartType visitVariableDefinitions(VariableDefinitions node) {
      DartType type = analyzeWithDefault(node.type, types.dynamicType);
      if (type == types.voidType) {
        reportTypeWarning(node.type, MessageKind.VOID_VARIABLE);
        type = types.dynamicType;
      }
      for (Link<Node> link = node.definitions.nodes; !link.isEmpty;
           link = link.tail) {
        Node definition = link.head;
        compiler.ensure(definition is Identifier || definition is SendSet);
        if (definition is SendSet) {
          SendSet initialization = definition;
          DartType initializer = analyzeNonVoid(initialization.arguments.head);
          checkAssignable(initialization.assignmentOperator, initializer, type);
        }
      }
      return StatementType.NOT_RETURNING;
    }
  
    DartType visitWhile(While node) {
      checkCondition(node.condition);
      StatementType bodyType = analyze(node.body);
      Expression cond = node.condition.asParenthesizedExpression().expression;
      if (cond.asLiteralBool() != null && cond.asLiteralBool().value == true) {
        // If the condition is a constant boolean expression denoting true,
        // control-flow always enters the loop body.
        // TODO(karlklose): this should be StatementType.RETURNING unless there
        // is a break in the loop body that has the loop or a label outside the
        // loop as a target.
        return bodyType;
      } else {
        return bodyType.join(StatementType.NOT_RETURNING);
      }
    }
  
    DartType visitParenthesizedExpression(ParenthesizedExpression node) {
      return analyze(node.expression);
    }
  
    DartType visitConditional(Conditional node) {
      Expression condition = node.condition;
      Expression thenExpression = node.thenExpression;
  
      checkCondition(condition);
  
      DartType thenType = analyzeInPromotedContext(condition, thenExpression);
  
      DartType elseType = analyzeNonVoid(node.elseExpression);
      if (types.isSubtype(thenType, elseType)) {
        return thenType;
      } else if (types.isSubtype(elseType, thenType)) {
        return elseType;
      } else {
        return objectType;
      }
    }
  
    visitStringInterpolation(StringInterpolation node) {
      node.visitChildren(this);
      return stringType;
    }
  
    visitStringInterpolationPart(StringInterpolationPart node) {
      node.visitChildren(this);
      return stringType;
    }
  
    visitEmptyStatement(EmptyStatement node) {
      return StatementType.NOT_RETURNING;
    }
  
    visitBreakStatement(BreakStatement node) {
      return StatementType.NOT_RETURNING;
    }
  
    visitContinueStatement(ContinueStatement node) {
      return StatementType.NOT_RETURNING;
    }
  
    visitForIn(ForIn node) {
      analyze(node.expression);
      StatementType bodyType = analyze(node.body);
      return bodyType.join(StatementType.NOT_RETURNING);
    }
  
    visitLabeledStatement(LabeledStatement node) {
      return analyze(node.statement);
    }
  
    visitLiteralMap(LiteralMap node) {
      InterfaceType mapType = elements.getType(node);
      DartType mapKeyType = firstType(mapType.typeArguments);
      DartType mapValueType = secondType(mapType.typeArguments);
      bool isConst = node.isConst();
      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, isConst: isConst);
        DartType valueType = analyze(entry.value);
        checkAssignable(entry.value, valueType, mapValueType, isConst: isConst);
      }
      return mapType;
    }
  
    visitNamedArgument(NamedArgument node) {
      // Named arguments are visited as part of analyzing invocations of
      // unresolved methods. For instance [: foo(a: 42); :] where 'foo' is neither
      // found in the enclosing scope nor through lookup on 'this' or
      // [: x.foo(b: 42); :] where 'foo' cannot be not found through lookup on
      // the static type of 'x'.
      return analyze(node.expression);
    }
  
    bool invalidSwitchExpressionType(Node diagnosticNode, DartType type) {
      if (type.kind == TypeKind.FUNCTION) return true;
      assert(invariant(diagnosticNode, type.kind == TypeKind.INTERFACE,
          message: "Expected interface type"));
      ClassElement cls = type.element;
      if (cls == compiler.doubleClass) return true;
      if (cls == compiler.intClass || cls == compiler.stringClass) return false;
      Element equals = cls.lookupMember('==');
      return equals.getEnclosingClass() != compiler.objectClass;
    }
  
    visitSwitchStatement(SwitchStatement node) {
      // TODO(johnniwinther): Handle reachability based on reachability of
      // switch cases.
      DartType expressionType = analyze(node.expression);
      Map<CaseMatch, DartType> caseTypeMap = new Map<CaseMatch, DartType>();
      for (SwitchCase switchCase in node.cases) {
        for (Node labelOrCase in switchCase.labelsAndCases) {
          CaseMatch caseMatch = labelOrCase.asCaseMatch();
          if (caseMatch == null) continue;
  
          DartType caseType = analyze(caseMatch.expression);
          caseTypeMap[caseMatch] = caseType;
          checkAssignable(caseMatch, expressionType, caseType);
        }
  
        analyze(switchCase);
      }
      // Check that all the case expressions have the same type.
      CaseMatch firstCase = null;
      DartType firstCaseType = null;
      bool hasReportedProblem = false;
      caseTypeMap.forEach((CaseMatch caseMatch, DartType caseType) {
        if (firstCaseType == null) {
          firstCase = caseMatch;
          firstCaseType = caseType;
        } else {
          if (caseType != firstCaseType) {
            if (!hasReportedProblem) {
              compiler.reportError(
                  node,
                  MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL,
                  {'type': firstCaseType});
              compiler.reportInfo(
                  firstCase.expression,
                  MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL_CASE,
                  {'type': firstCaseType});
              hasReportedProblem = true;
            }
            compiler.reportInfo(
                caseMatch.expression,
                MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL_CASE,
                {'type': caseType});
          }
        }
      });
      // Check that the type is either [int], [String], or a class that does not
      // implement `operator ==`.
      if (firstCaseType != null &&
          invalidSwitchExpressionType(firstCase, firstCaseType)) {
        compiler.reportError(firstCase.expression,
            MessageKind.SWITCH_CASE_VALUE_OVERRIDES_EQUALS);
      }
      return StatementType.NOT_RETURNING;
    }
  
    visitSwitchCase(SwitchCase node) {
      return analyze(node.statements);
    }
  
    visitTryStatement(TryStatement node) {
      // TODO(johnniwinther): Use reachability information of try-block,
      // catch-blocks and finally-block to compute the whether the try statement
      // is returning.
      analyze(node.tryBlock);
      for (CatchBlock catchBlock in node.catchBlocks) {
        analyze(catchBlock);
      }
      analyzeWithDefault(node.finallyBlock, null);
      return StatementType.NOT_RETURNING;
    }
  
    visitCatchBlock(CatchBlock node) {
      return analyze(node.block);
    }
  
    visitTypedef(Typedef node) {
      // Do not typecheck [Typedef] nodes.
    }
  
    visitNode(Node node) {
      compiler.internalError(
          'Unexpected node ${node.getObjectDescription()} in the type checker.',
          node: node);
    }
  }
  