com.google.javascript.jscomp.TypeInference.java Source code

Java tutorial

Introduction

Here is the source code for com.google.javascript.jscomp.TypeInference.java

Source

/*
 * Copyright 2008 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.javascript.jscomp;

import static com.google.javascript.rhino.jstype.JSTypeNative.ARRAY_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.BOOLEAN_OBJECT_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.BOOLEAN_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.CHECKED_UNKNOWN_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.NULL_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.NUMBER_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.NUMBER_VALUE_OR_OBJECT_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.STRING_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.UNKNOWN_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.VOID_TYPE;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.CodingConvention.AssertionFunctionSpec;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphEdge;
import com.google.javascript.jscomp.type.FlowScope;
import com.google.javascript.jscomp.type.ReverseAbstractInterpreter;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.BooleanLiteralSet;
import com.google.javascript.rhino.jstype.FunctionBuilder;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ModificationVisitor;
import com.google.javascript.rhino.jstype.ObjectType;
import com.google.javascript.rhino.jstype.StaticTypedSlot;
import com.google.javascript.rhino.jstype.TemplateType;
import com.google.javascript.rhino.jstype.TemplateTypeMap;
import com.google.javascript.rhino.jstype.TemplateTypeMapReplacer;
import com.google.javascript.rhino.jstype.UnionType;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Type inference within a script node or a function body, using the data-flow
 * analysis framework.
 *
 */
class TypeInference extends DataFlowAnalysis.BranchedForwardDataFlowAnalysis<Node, FlowScope> {

    // TODO(johnlenz): We no longer make this check, but we should.
    static final DiagnosticType FUNCTION_LITERAL_UNDEFINED_THIS = DiagnosticType.warning(
            "JSC_FUNCTION_LITERAL_UNDEFINED_THIS", "Function literal argument refers to undefined this argument");

    private final AbstractCompiler compiler;
    private final JSTypeRegistry registry;
    private final ReverseAbstractInterpreter reverseInterpreter;
    private final TypedScope syntacticScope;
    private final FlowScope functionScope;
    private final FlowScope bottomScope;
    private final Map<String, AssertionFunctionSpec> assertionFunctionsMap;

    // For convenience
    private final ObjectType unknownType;

    TypeInference(AbstractCompiler compiler, ControlFlowGraph<Node> cfg,
            ReverseAbstractInterpreter reverseInterpreter, TypedScope functionScope,
            Map<String, AssertionFunctionSpec> assertionFunctionsMap) {
        super(cfg, new LinkedFlowScope.FlowScopeJoinOp());
        this.compiler = compiler;
        this.registry = compiler.getTypeRegistry();
        this.reverseInterpreter = reverseInterpreter;
        this.unknownType = registry.getNativeObjectType(UNKNOWN_TYPE);

        this.syntacticScope = functionScope;
        inferArguments(functionScope);

        this.functionScope = LinkedFlowScope.createEntryLattice(functionScope);
        this.assertionFunctionsMap = assertionFunctionsMap;

        // For each local variable declared with the VAR keyword, the entry
        // type is VOID.
        Iterator<TypedVar> varIt = functionScope.getDeclarativelyUnboundVarsWithoutTypes();
        while (varIt.hasNext()) {
            TypedVar var = varIt.next();
            if (isUnflowable(var)) {
                continue;
            }

            this.functionScope.inferSlotType(var.getName(), getNativeType(VOID_TYPE));
        }

        this.bottomScope = LinkedFlowScope
                .createEntryLattice(TypedScope.createLatticeBottom(functionScope.getRootNode()));
    }

    /**
     * Infers all of a function's arguments if their types aren't declared.
     */
    private void inferArguments(TypedScope functionScope) {
        Node functionNode = functionScope.getRootNode();
        Node astParameters = functionNode.getFirstChild().getNext();
        Node iifeArgumentNode = null;

        if (NodeUtil.isCallOrNewTarget(functionNode)) {
            iifeArgumentNode = functionNode.getNext();
        }

        FunctionType functionType = JSType.toMaybeFunctionType(functionNode.getJSType());
        if (functionType != null) {
            Node parameterTypes = functionType.getParametersNode();
            if (parameterTypes != null) {
                Node parameterTypeNode = parameterTypes.getFirstChild();
                for (Node astParameter : astParameters.children()) {
                    TypedVar var = functionScope.getVar(astParameter.getString());
                    Preconditions.checkNotNull(var);
                    if (var.isTypeInferred() && var.getType() == unknownType) {
                        JSType newType = null;

                        if (iifeArgumentNode != null) {
                            newType = iifeArgumentNode.getJSType();
                        } else if (parameterTypeNode != null) {
                            newType = parameterTypeNode.getJSType();
                        }

                        if (newType != null) {
                            var.setType(newType);
                            astParameter.setJSType(newType);
                        }
                    }

                    if (parameterTypeNode != null) {
                        parameterTypeNode = parameterTypeNode.getNext();
                    }
                    if (iifeArgumentNode != null) {
                        iifeArgumentNode = iifeArgumentNode.getNext();
                    }
                }
            }
        }
    }

    @Override
    FlowScope createInitialEstimateLattice() {
        return bottomScope;
    }

    @Override
    FlowScope createEntryLattice() {
        return functionScope;
    }

    @Override
    FlowScope flowThrough(Node n, FlowScope input) {
        // If we have not walked a path from <entry> to <n>, then we don't
        // want to infer anything about this scope.
        if (input == bottomScope) {
            return input;
        }

        FlowScope output = input.createChildFlowScope();
        output = traverse(n, output);
        return output;
    }

    @Override
    @SuppressWarnings({ "fallthrough", "incomplete-switch" })
    List<FlowScope> branchedFlowThrough(Node source, FlowScope input) {
        // NOTE(nicksantos): Right now, we just treat ON_EX edges like UNCOND
        // edges. If we wanted to be perfect, we'd actually JOIN all the out
        // lattices of this flow with the in lattice, and then make that the out
        // lattice for the ON_EX edge. But it's probably too expensive to be
        // worthwhile.
        FlowScope output = flowThrough(source, input);
        Node condition = null;
        FlowScope conditionFlowScope = null;
        BooleanOutcomePair conditionOutcomes = null;

        List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(source);
        List<FlowScope> result = new ArrayList<>(branchEdges.size());
        for (DiGraphEdge<Node, Branch> branchEdge : branchEdges) {
            Branch branch = branchEdge.getValue();
            FlowScope newScope = output;

            switch (branch) {
            case ON_TRUE:
                if (NodeUtil.isForIn(source)) {
                    // item is assigned a property name, so its type should be string.
                    Node item = source.getFirstChild();
                    Node obj = item.getNext();

                    FlowScope informed = traverse(obj, output.createChildFlowScope());

                    if (item.isVar()) {
                        item = item.getFirstChild();
                    }
                    if (item.isName()) {
                        JSType iterKeyType = getNativeType(STRING_TYPE);
                        ObjectType objType = getJSType(obj).dereference();
                        JSType objIndexType = objType == null ? null
                                : objType.getTemplateTypeMap().getTemplateType(registry.getObjectIndexKey());
                        if (objIndexType != null && !objIndexType.isUnknownType()) {
                            JSType narrowedKeyType = iterKeyType.getGreatestSubtype(objIndexType);
                            if (!narrowedKeyType.isEmptyType()) {
                                iterKeyType = narrowedKeyType;
                            }
                        }
                        redeclareSimpleVar(informed, item, iterKeyType);
                    }
                    newScope = informed;
                    break;
                }

                // FALL THROUGH

            case ON_FALSE:
                if (condition == null) {
                    condition = NodeUtil.getConditionExpression(source);
                    if (condition == null && source.isCase()) {
                        condition = source;

                        // conditionFlowScope is cached from previous iterations
                        // of the loop.
                        if (conditionFlowScope == null) {
                            conditionFlowScope = traverse(condition.getFirstChild(), output.createChildFlowScope());
                        }
                    }
                }

                if (condition != null) {
                    if (condition.isAnd() || condition.isOr()) {
                        // When handling the short-circuiting binary operators,
                        // the outcome scope on true can be different than the outcome
                        // scope on false.
                        //
                        // TODO(nicksantos): The "right" way to do this is to
                        // carry the known outcome all the way through the
                        // recursive traversal, so that we can construct a
                        // different flow scope based on the outcome. However,
                        // this would require a bunch of code and a bunch of
                        // extra computation for an edge case. This seems to be
                        // a "good enough" approximation.

                        // conditionOutcomes is cached from previous iterations
                        // of the loop.
                        if (conditionOutcomes == null) {
                            conditionOutcomes = condition.isAnd()
                                    ? traverseAnd(condition, output.createChildFlowScope())
                                    : traverseOr(condition, output.createChildFlowScope());
                        }
                        newScope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome(condition,
                                conditionOutcomes.getOutcomeFlowScope(condition.getType(),
                                        branch == Branch.ON_TRUE),
                                branch == Branch.ON_TRUE);
                    } else {
                        // conditionFlowScope is cached from previous iterations
                        // of the loop.
                        if (conditionFlowScope == null) {
                            conditionFlowScope = traverse(condition, output.createChildFlowScope());
                        }
                        newScope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome(condition,
                                conditionFlowScope, branch == Branch.ON_TRUE);
                    }
                }
                break;
            }

            result.add(newScope.optimize());
        }
        return result;
    }

    private FlowScope traverse(Node n, FlowScope scope) {
        switch (n.getType()) {
        case Token.ASSIGN:
            scope = traverseAssign(n, scope);
            break;

        case Token.NAME:
            scope = traverseName(n, scope);
            break;

        case Token.GETPROP:
            scope = traverseGetProp(n, scope);
            break;

        case Token.AND:
            scope = traverseAnd(n, scope).getJoinedFlowScope().createChildFlowScope();
            break;

        case Token.OR:
            scope = traverseOr(n, scope).getJoinedFlowScope().createChildFlowScope();
            break;

        case Token.HOOK:
            scope = traverseHook(n, scope);
            break;

        case Token.OBJECTLIT:
            scope = traverseObjectLiteral(n, scope);
            break;

        case Token.CALL:
            scope = traverseCall(n, scope);
            break;

        case Token.NEW:
            scope = traverseNew(n, scope);
            break;

        case Token.ASSIGN_ADD:
        case Token.ADD:
            scope = traverseAdd(n, scope);
            break;

        case Token.POS:
        case Token.NEG:
            scope = traverse(n.getFirstChild(), scope); // Find types.
            n.setJSType(getNativeType(NUMBER_TYPE));
            break;

        case Token.ARRAYLIT:
            scope = traverseArrayLiteral(n, scope);
            break;

        case Token.THIS:
            n.setJSType(scope.getTypeOfThis());
            break;

        case Token.ASSIGN_LSH:
        case Token.ASSIGN_RSH:
        case Token.LSH:
        case Token.RSH:
        case Token.ASSIGN_URSH:
        case Token.URSH:
        case Token.ASSIGN_DIV:
        case Token.ASSIGN_MOD:
        case Token.ASSIGN_BITAND:
        case Token.ASSIGN_BITXOR:
        case Token.ASSIGN_BITOR:
        case Token.ASSIGN_MUL:
        case Token.ASSIGN_SUB:
        case Token.DIV:
        case Token.MOD:
        case Token.BITAND:
        case Token.BITXOR:
        case Token.BITOR:
        case Token.MUL:
        case Token.SUB:
        case Token.DEC:
        case Token.INC:
        case Token.BITNOT:
            scope = traverseChildren(n, scope);
            n.setJSType(getNativeType(NUMBER_TYPE));
            break;

        case Token.PARAM_LIST:
            scope = traverse(n.getFirstChild(), scope);
            n.setJSType(getJSType(n.getFirstChild()));
            break;

        case Token.COMMA:
            scope = traverseChildren(n, scope);
            n.setJSType(getJSType(n.getLastChild()));
            break;

        case Token.TYPEOF:
            scope = traverseChildren(n, scope);
            n.setJSType(getNativeType(STRING_TYPE));
            break;

        case Token.DELPROP:
        case Token.LT:
        case Token.LE:
        case Token.GT:
        case Token.GE:
        case Token.NOT:
        case Token.EQ:
        case Token.NE:
        case Token.SHEQ:
        case Token.SHNE:
        case Token.INSTANCEOF:
        case Token.IN:
            scope = traverseChildren(n, scope);
            n.setJSType(getNativeType(BOOLEAN_TYPE));
            break;

        case Token.GETELEM:
            scope = traverseGetElem(n, scope);
            break;

        case Token.EXPR_RESULT:
            scope = traverseChildren(n, scope);
            if (n.getFirstChild().isGetProp()) {
                ensurePropertyDeclared(n.getFirstChild());
            }
            break;

        case Token.SWITCH:
            scope = traverse(n.getFirstChild(), scope);
            break;

        case Token.RETURN:
            scope = traverseReturn(n, scope);
            break;

        case Token.VAR:
        case Token.THROW:
            scope = traverseChildren(n, scope);
            break;

        case Token.CATCH:
            scope = traverseCatch(n, scope);
            break;

        case Token.CAST:
            scope = traverseChildren(n, scope);
            JSDocInfo info = n.getJSDocInfo();
            if (info != null && info.hasType()) {
                n.setJSType(info.getType().evaluate(syntacticScope, registry));
            }
            break;
        }

        return scope;
    }

    /**
     * Traverse a return value.
     */
    private FlowScope traverseReturn(Node n, FlowScope scope) {
        scope = traverseChildren(n, scope);

        Node retValue = n.getFirstChild();
        if (retValue != null) {
            JSType type = functionScope.getRootNode().getJSType();
            if (type != null) {
                FunctionType fnType = type.toMaybeFunctionType();
                if (fnType != null) {
                    inferPropertyTypesToMatchConstraint(retValue.getJSType(), fnType.getReturnType());
                }
            }
        }
        return scope;
    }

    /**
     * Any value can be thrown, so it's really impossible to determine the type
     * of a CATCH param. Treat it as the UNKNOWN type.
     */
    private FlowScope traverseCatch(Node catchNode, FlowScope scope) {
        Node name = catchNode.getFirstChild();
        JSType type;
        // If the catch expression name was declared in the catch use that type,
        // otherwise use "unknown".
        JSDocInfo info = name.getJSDocInfo();
        if (info != null && info.hasType()) {
            type = info.getType().evaluate(syntacticScope, registry);
        } else {
            type = getNativeType(JSTypeNative.UNKNOWN_TYPE);
        }
        redeclareSimpleVar(scope, name, type);
        name.setJSType(type);
        return scope;
    }

    private FlowScope traverseAssign(Node n, FlowScope scope) {
        Node left = n.getFirstChild();
        Node right = n.getLastChild();
        scope = traverseChildren(n, scope);

        JSType leftType = left.getJSType();
        JSType rightType = getJSType(right);
        n.setJSType(rightType);

        updateScopeForTypeChange(scope, left, leftType, rightType);
        return scope;
    }

    /**
     * Updates the scope according to the result of a type change, like
     * an assignment or a type cast.
     */
    private void updateScopeForTypeChange(FlowScope scope, Node left, JSType leftType, JSType resultType) {
        Preconditions.checkNotNull(resultType);
        switch (left.getType()) {
        case Token.NAME:
            String varName = left.getString();
            TypedVar var = syntacticScope.getVar(varName);
            JSType varType = var == null ? null : var.getType();
            boolean isVarDeclaration = left.hasChildren() && varType != null && !var.isTypeInferred();

            boolean isTypelessConstDecl = isVarDeclaration && NodeUtil
                    .isConstantDeclaration(compiler.getCodingConvention(), var.getJSDocInfo(), var.getNameNode())
                    && !(var.getJSDocInfo() != null && var.getJSDocInfo().hasType());

            // When looking at VAR initializers for declared VARs, we tend
            // to use the declared type over the type it's being
            // initialized to in the global scope.
            //
            // For example,
            // /** @param {number} */ var f = goog.abstractMethod;
            // it's obvious that the programmer wants you to use
            // the declared function signature, not the inferred signature.
            //
            // Or,
            // /** @type {Object.<string>} */ var x = {};
            // the one-time anonymous object on the right side
            // is as narrow as it can possibly be, but we need to make
            // sure we back-infer the <string> element constraint on
            // the left hand side, so we use the left hand side.

            boolean isVarTypeBetter = isVarDeclaration
                    // Makes it easier to check for NPEs.
                    && !resultType.isNullType() && !resultType.isVoidType()
                    // Do not use the var type if the declaration looked like
                    // /** @const */ var x = 3;
                    // because this type was computed from the RHS
                    && !isTypelessConstDecl;

            // TODO(nicksantos): This might be a better check once we have
            // back-inference of object/array constraints.  It will probably
            // introduce more type warnings.  It uses the result type iff it's
            // strictly narrower than the declared var type.
            //
            //boolean isVarTypeBetter = isVarDeclaration &&
            //    (varType.restrictByNotNullOrUndefined().isSubtype(resultType)
            //     || !resultType.isSubtype(varType));

            if (isVarTypeBetter) {
                redeclareSimpleVar(scope, left, varType);
            } else {
                redeclareSimpleVar(scope, left, resultType);
            }
            left.setJSType(resultType);

            if (var != null && var.isTypeInferred()) {
                JSType oldType = var.getType();
                var.setType(oldType == null ? resultType : oldType.getLeastSupertype(resultType));
            } else if (isTypelessConstDecl) {
                // /** @const */ var x = y;
                // should be redeclared, so that the type of y
                // gets propagated to inner scopes.
                var.setType(resultType);
            }
            break;
        case Token.GETPROP:
            String qualifiedName = left.getQualifiedName();
            if (qualifiedName != null) {
                boolean declaredSlotType = false;
                JSType rawObjType = left.getFirstChild().getJSType();
                if (rawObjType != null) {
                    ObjectType objType = ObjectType.cast(rawObjType.restrictByNotNullOrUndefined());
                    if (objType != null) {
                        String propName = left.getLastChild().getString();
                        declaredSlotType = objType.isPropertyTypeDeclared(propName);
                    }
                }
                JSType safeLeftType = leftType == null ? unknownType : leftType;
                scope.inferQualifiedSlot(left, qualifiedName, safeLeftType, resultType, declaredSlotType);
            }

            left.setJSType(resultType);
            ensurePropertyDefined(left, resultType);
            break;
        }
    }

    /**
     * Defines a property if the property has not been defined yet.
     */
    private void ensurePropertyDefined(Node getprop, JSType rightType) {
        String propName = getprop.getLastChild().getString();
        Node obj = getprop.getFirstChild();
        JSType nodeType = getJSType(obj);
        ObjectType objectType = ObjectType.cast(nodeType.restrictByNotNullOrUndefined());
        boolean propCreationInConstructor = obj.isThis() && getJSType(syntacticScope.getRootNode()).isConstructor();

        if (objectType == null) {
            registry.registerPropertyOnType(propName, nodeType);
        } else {
            if (nodeType.isStruct() && !objectType.hasProperty(propName)) {
                // In general, we don't want to define a property on a struct object,
                // b/c TypeCheck will later check for improper property creation on
                // structs. There are two exceptions.
                // 1) If it's a property created inside the constructor, on the newly
                //    created instance, allow it.
                // 2) If it's a prototype property, allow it. For example:
                //    Foo.prototype.bar = baz;
                //    where Foo.prototype is a struct and the assignment happens at the
                //    top level and the constructor Foo is defined in the same file.
                boolean staticPropCreation = false;
                Node maybeAssignStm = getprop.getParent().getParent();
                if (syntacticScope.isGlobal() && NodeUtil.isPrototypePropertyDeclaration(maybeAssignStm)) {
                    String propCreationFilename = maybeAssignStm.getSourceFileName();
                    Node ctor = objectType.getOwnerFunction().getSource();
                    if (ctor != null && ctor.getSourceFileName().equals(propCreationFilename)) {
                        staticPropCreation = true;
                    }
                }
                if (!propCreationInConstructor && !staticPropCreation) {
                    return; // Early return to avoid creating the property below.
                }
            }

            if (ensurePropertyDeclaredHelper(getprop, objectType)) {
                return;
            }

            if (!objectType.isPropertyTypeDeclared(propName)) {
                // We do not want a "stray" assign to define an inferred property
                // for every object of this type in the program. So we use a heuristic
                // approach to determine whether to infer the property.
                //
                // 1) If the property is already defined, join it with the previously
                //    inferred type.
                // 2) If this isn't an instance object, define it.
                // 3) If the property of an object is being assigned in the constructor,
                //    define it.
                // 4) If this is a stub, define it.
                // 5) Otherwise, do not define the type, but declare it in the registry
                //    so that we can use it for missing property checks.
                if (objectType.hasProperty(propName) || !objectType.isInstanceType()) {
                    if ("prototype".equals(propName)) {
                        objectType.defineDeclaredProperty(propName, rightType, getprop);
                    } else {
                        objectType.defineInferredProperty(propName, rightType, getprop);
                    }
                } else if (propCreationInConstructor) {
                    objectType.defineInferredProperty(propName, rightType, getprop);
                } else {
                    registry.registerPropertyOnType(propName, objectType);
                }
            }
        }
    }

    /**
     * Defines a declared property if it has not been defined yet.
     *
     * This handles the case where a property is declared on an object where
     * the object type is inferred, and so the object type will not
     * be known in {@code TypedScopeCreator}.
     */
    private void ensurePropertyDeclared(Node getprop) {
        ObjectType ownerType = ObjectType.cast(getJSType(getprop.getFirstChild()).restrictByNotNullOrUndefined());
        if (ownerType != null) {
            ensurePropertyDeclaredHelper(getprop, ownerType);
        }
    }

    /**
     * Declares a property on its owner, if necessary.
     * @return True if a property was declared.
     */
    private boolean ensurePropertyDeclaredHelper(Node getprop, ObjectType objectType) {
        String propName = getprop.getLastChild().getString();
        String qName = getprop.getQualifiedName();
        if (qName != null) {
            TypedVar var = syntacticScope.getVar(qName);
            if (var != null && !var.isTypeInferred()) {
                // Handle normal declarations that could not be addressed earlier.
                if (propName.equals("prototype") ||
                // Handle prototype declarations that could not be addressed earlier.
                        (!objectType.hasOwnProperty(propName) && (!objectType.isInstanceType()
                                || (var.isExtern() && !objectType.isNativeObjectType())))) {
                    return objectType.defineDeclaredProperty(propName, var.getType(), getprop);
                }
            }
        }
        return false;
    }

    private FlowScope traverseName(Node n, FlowScope scope) {
        String varName = n.getString();
        Node value = n.getFirstChild();
        JSType type = n.getJSType();
        if (value != null) {
            scope = traverse(value, scope);
            updateScopeForTypeChange(scope, n, n.getJSType() /* could be null */, getJSType(value));
            return scope;
        } else {
            StaticTypedSlot<JSType> var = scope.getSlot(varName);
            if (var != null) {
                // There are two situations where we don't want to use type information
                // from the scope, even if we have it.

                // 1) The var is escaped and assigned in an inner scope, e.g.,
                // function f() { var x = 3; function g() { x = null } (x); }
                boolean isInferred = var.isTypeInferred();
                boolean unflowable = isInferred && isUnflowable(syntacticScope.getVar(varName));

                // 2) We're reading type information from another scope for an
                // inferred variable. That variable is assigned more than once,
                // and we can't know which type we're getting.
                //
                // var t = null; function f() { (t); } doStuff(); t = {};
                //
                // Notice that this heuristic isn't perfect. For example, you might
                // have:
                //
                // function f() { (t); } f(); var t = 3;
                //
                // In this case, we would infer the first reference to t as
                // type {number}, even though it's undefined.
                boolean nonLocalInferredSlot = false;
                if (isInferred && syntacticScope.isLocal()) {
                    TypedVar maybeOuterVar = syntacticScope.getParent().getVar(varName);
                    if (var == maybeOuterVar && !maybeOuterVar.isMarkedAssignedExactlyOnce()) {
                        nonLocalInferredSlot = true;
                    }
                }

                if (!unflowable && !nonLocalInferredSlot) {
                    type = var.getType();
                    if (type == null) {
                        type = unknownType;
                    }
                }
            }
        }
        n.setJSType(type);
        return scope;
    }

    /** Traverse each element of the array. */
    private FlowScope traverseArrayLiteral(Node n, FlowScope scope) {
        scope = traverseChildren(n, scope);
        n.setJSType(getNativeType(ARRAY_TYPE));
        return scope;
    }

    private FlowScope traverseObjectLiteral(Node n, FlowScope scope) {
        JSType type = n.getJSType();
        Preconditions.checkNotNull(type);

        for (Node name = n.getFirstChild(); name != null; name = name.getNext()) {
            scope = traverse(name.getFirstChild(), scope);
        }

        // Object literals can be reflected on other types.
        // See CodingConvention#getObjectLiteralCast and goog.reflect.object
        // Ignore these types of literals.
        ObjectType objectType = ObjectType.cast(type);
        if (objectType == null || n.getBooleanProp(Node.REFLECTED_OBJECT) || objectType.isEnumType()) {
            return scope;
        }

        String qObjName = NodeUtil.getBestLValueName(NodeUtil.getBestLValue(n));
        for (Node name = n.getFirstChild(); name != null; name = name.getNext()) {
            String memberName = NodeUtil.getObjectLitKeyName(name);
            if (memberName != null) {
                JSType rawValueType = name.getFirstChild().getJSType();
                JSType valueType = TypeCheck.getObjectLitKeyTypeFromValueType(name, rawValueType);
                if (valueType == null) {
                    valueType = unknownType;
                }
                objectType.defineInferredProperty(memberName, valueType, name);

                // Do normal flow inference if this is a direct property assignment.
                if (qObjName != null && name.isStringKey()) {
                    String qKeyName = qObjName + "." + memberName;
                    TypedVar var = syntacticScope.getVar(qKeyName);
                    JSType oldType = var == null ? null : var.getType();
                    if (var != null && var.isTypeInferred()) {
                        var.setType(oldType == null ? valueType : oldType.getLeastSupertype(oldType));
                    }

                    scope.inferQualifiedSlot(name, qKeyName, oldType == null ? unknownType : oldType, valueType,
                            false);
                }
            } else {
                n.setJSType(unknownType);
            }
        }
        return scope;
    }

    private FlowScope traverseAdd(Node n, FlowScope scope) {
        Node left = n.getFirstChild();
        Node right = left.getNext();
        scope = traverseChildren(n, scope);

        JSType leftType = left.getJSType();
        JSType rightType = right.getJSType();

        JSType type = unknownType;
        if (leftType != null && rightType != null) {
            boolean leftIsUnknown = leftType.isUnknownType();
            boolean rightIsUnknown = rightType.isUnknownType();
            if (leftIsUnknown && rightIsUnknown) {
                type = unknownType;
            } else if ((!leftIsUnknown && leftType.isString()) || (!rightIsUnknown && rightType.isString())) {
                type = getNativeType(STRING_TYPE);
            } else if (leftIsUnknown || rightIsUnknown) {
                type = unknownType;
            } else if (isAddedAsNumber(leftType) && isAddedAsNumber(rightType)) {
                type = getNativeType(NUMBER_TYPE);
            } else {
                type = registry.createUnionType(STRING_TYPE, NUMBER_TYPE);
            }
        }
        n.setJSType(type);

        if (n.isAssignAdd()) {
            updateScopeForTypeChange(scope, left, leftType, type);
        }

        return scope;
    }

    private boolean isAddedAsNumber(JSType type) {
        return type.isSubtype(registry.createUnionType(VOID_TYPE, NULL_TYPE, NUMBER_VALUE_OR_OBJECT_TYPE,
                BOOLEAN_TYPE, BOOLEAN_OBJECT_TYPE));
    }

    private FlowScope traverseHook(Node n, FlowScope scope) {
        Node condition = n.getFirstChild();
        Node trueNode = condition.getNext();
        Node falseNode = n.getLastChild();

        // verify the condition
        scope = traverse(condition, scope);

        // reverse abstract interpret the condition to produce two new scopes
        FlowScope trueScope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome(condition, scope, true);
        FlowScope falseScope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome(condition, scope, false);

        // traverse the true node with the trueScope
        traverse(trueNode, trueScope.createChildFlowScope());

        // traverse the false node with the falseScope
        traverse(falseNode, falseScope.createChildFlowScope());

        // meet true and false nodes' types and assign
        JSType trueType = trueNode.getJSType();
        JSType falseType = falseNode.getJSType();
        if (trueType != null && falseType != null) {
            n.setJSType(trueType.getLeastSupertype(falseType));
        } else {
            n.setJSType(null);
        }

        return scope.createChildFlowScope();
    }

    private FlowScope traverseCall(Node n, FlowScope scope) {
        scope = traverseChildren(n, scope);

        Node left = n.getFirstChild();
        JSType functionType = getJSType(left).restrictByNotNullOrUndefined();
        if (functionType.isFunctionType()) {
            FunctionType fnType = functionType.toMaybeFunctionType();
            n.setJSType(fnType.getReturnType());
            backwardsInferenceFromCallSite(n, fnType);
        } else if (functionType.isEquivalentTo(getNativeType(CHECKED_UNKNOWN_TYPE))) {
            n.setJSType(getNativeType(CHECKED_UNKNOWN_TYPE));
        }

        scope = tightenTypesAfterAssertions(scope, n);
        return scope;
    }

    private FlowScope tightenTypesAfterAssertions(FlowScope scope, Node callNode) {
        Node left = callNode.getFirstChild();
        Node firstParam = left.getNext();
        AssertionFunctionSpec assertionFunctionSpec = assertionFunctionsMap.get(left.getQualifiedName());
        if (assertionFunctionSpec == null || firstParam == null) {
            return scope;
        }
        Node assertedNode = assertionFunctionSpec.getAssertedParam(firstParam);
        if (assertedNode == null) {
            return scope;
        }
        JSType assertedType = assertionFunctionSpec.getAssertedOldType(callNode, registry);
        String assertedNodeName = assertedNode.getQualifiedName();

        JSType narrowed;
        // Handle assertions that enforce expressions evaluate to true.
        if (assertedType == null) {
            // Handle arbitrary expressions within the assert.
            scope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome(assertedNode, scope, true);
            // Build the result of the assertExpression
            narrowed = getJSType(assertedNode).restrictByNotNullOrUndefined();
        } else {
            // Handle assertions that enforce expressions are of a certain type.
            JSType type = getJSType(assertedNode);
            if (assertedType.isUnknownType() || type.isUnknownType()) {
                narrowed = assertedType;
            } else {
                narrowed = type.getGreatestSubtype(assertedType);
            }
            if (assertedNodeName != null && type.differsFrom(narrowed)) {
                scope = narrowScope(scope, assertedNode, narrowed);
            }
        }

        callNode.setJSType(narrowed);
        return scope;
    }

    private FlowScope narrowScope(FlowScope scope, Node node, JSType narrowed) {
        if (node.isThis()) {
            // "this" references don't need to be modeled in the control flow graph.
            return scope;
        }

        scope = scope.createChildFlowScope();
        if (node.isGetProp()) {
            scope.inferQualifiedSlot(node, node.getQualifiedName(), getJSType(node), narrowed, false);
        } else {
            redeclareSimpleVar(scope, node, narrowed);
        }
        return scope;
    }

    /**
     * We only do forward type inference. We do not do full backwards
     * type inference.
     *
     * In other words, if we have,
     * <code>
     * var x = f();
     * g(x);
     * </code>
     * a forward type-inference engine would try to figure out the type
     * of "x" from the return type of "f". A backwards type-inference engine
     * would try to figure out the type of "x" from the parameter type of "g".
     *
     * However, there are a few special syntactic forms where we do some
     * some half-assed backwards type-inference, because programmers
     * expect it in this day and age. To take an example from Java,
     * <code>
     * List<String> x = Lists.newArrayList();
     * </code>
     * The Java compiler will be able to infer the generic type of the List
     * returned by newArrayList().
     *
     * In much the same way, we do some special-case backwards inference for
     * JS. Those cases are enumerated here.
     */
    private void backwardsInferenceFromCallSite(Node n, FunctionType fnType) {
        boolean updatedFnType = inferTemplatedTypesForCall(n, fnType);
        if (updatedFnType) {
            fnType = n.getFirstChild().getJSType().toMaybeFunctionType();
        }
        updateTypeOfParameters(n, fnType);
        updateBind(n);
    }

    /**
     * When "bind" is called on a function, we infer the type of the returned
     * "bound" function by looking at the number of parameters in the call site.
     * We also infer the "this" type of the target, if it's a function expression.
     */
    private void updateBind(Node n) {
        CodingConvention.Bind bind = compiler.getCodingConvention().describeFunctionBind(n, false, true);
        if (bind == null) {
            return;
        }

        Node target = bind.target;
        FunctionType callTargetFn = getJSType(target).restrictByNotNullOrUndefined().toMaybeFunctionType();
        if (callTargetFn == null) {
            return;
        }

        if (bind.thisValue != null && target.isFunction()) {
            JSType thisType = getJSType(bind.thisValue);
            if (thisType.toObjectType() != null && !thisType.isUnknownType()
                    && callTargetFn.getTypeOfThis().isUnknownType()) {
                callTargetFn = new FunctionBuilder(registry).copyFromOtherFunction(callTargetFn)
                        .withTypeOfThis(thisType.toObjectType()).build();
                target.setJSType(callTargetFn);
            }
        }

        n.setJSType(callTargetFn.getBindReturnType(
                // getBindReturnType expects the 'this' argument to be included.
                bind.getBoundParameterCount() + 1));
    }

    /**
     * For functions with function parameters, type inference will set the type of
     * a function literal argument from the function parameter type.
     */
    private void updateTypeOfParameters(Node n, FunctionType fnType) {
        int i = 0;
        int childCount = n.getChildCount();
        for (Node iParameter : fnType.getParameters()) {
            if (i + 1 >= childCount) {
                // TypeCheck#visitParametersList will warn so we bail.
                return;
            }

            JSType iParameterType = getJSType(iParameter);
            Node iArgument = n.getChildAtIndex(i + 1);
            JSType iArgumentType = getJSType(iArgument);
            inferPropertyTypesToMatchConstraint(iArgumentType, iParameterType);

            // If the parameter to the call is a function expression, propagate the
            // function signature from the call site to the function node.

            // Filter out non-function types (such as null and undefined) as
            // we only care about FUNCTION subtypes here.
            FunctionType restrictedParameter = null;
            if (iParameterType.isUnionType()) {
                UnionType union = iParameterType.toMaybeUnionType();
                for (JSType alternative : union.getAlternates()) {
                    if (alternative.isFunctionType()) {
                        // There is only one function type per union.
                        restrictedParameter = alternative.toMaybeFunctionType();
                        break;
                    }
                }
            } else {
                restrictedParameter = iParameterType.toMaybeFunctionType();
            }

            if (restrictedParameter != null && iArgument.isFunction() && iArgumentType.isFunctionType()) {
                FunctionType argFnType = iArgumentType.toMaybeFunctionType();
                boolean declared = iArgument.getJSDocInfo() != null;
                iArgument.setJSType(matchFunction(restrictedParameter, argFnType, declared));
            }
            i++;
        }
    }

    /**
     * Take the current function type, and try to match the expected function
     * type. This is a form of backwards-inference, like record-type constraint
     * matching.
     */
    private FunctionType matchFunction(FunctionType expectedType, FunctionType currentType, boolean declared) {
        if (declared) {
            // If the function was declared but it doesn't have a known "this"
            // but the expected type does, back fill it.
            if (currentType.getTypeOfThis().isUnknownType() && !expectedType.getTypeOfThis().isUnknownType()) {
                FunctionType replacement = new FunctionBuilder(registry).copyFromOtherFunction(currentType)
                        .withTypeOfThis(expectedType.getTypeOfThis()).build();
                return replacement;
            }
        } else {
            // For now, we just make sure the current type has enough
            // arguments to match the expected type, and return the
            // expected type if it does.
            if (currentType.getMaxArguments() <= expectedType.getMaxArguments()) {
                return expectedType;
            }
        }
        return currentType;
    }

    private Map<TemplateType, JSType> inferTemplateTypesFromParameters(FunctionType fnType, Node call) {
        if (fnType.getTemplateTypeMap().getTemplateKeys().isEmpty()) {
            return Collections.emptyMap();
        }

        Map<TemplateType, JSType> resolvedTypes = Maps.newIdentityHashMap();
        Set<JSType> seenTypes = Sets.newIdentityHashSet();

        Node callTarget = call.getFirstChild();
        if (NodeUtil.isGet(callTarget)) {
            Node obj = callTarget.getFirstChild();
            maybeResolveTemplatedType(fnType.getTypeOfThis(), getJSType(obj), resolvedTypes, seenTypes);
        }

        if (call.hasMoreThanOneChild()) {
            maybeResolveTemplateTypeFromNodes(fnType.getParameters(), call.getChildAtIndex(1).siblings(),
                    resolvedTypes, seenTypes);
        }
        return resolvedTypes;
    }

    private void maybeResolveTemplatedType(JSType paramType, JSType argType,
            Map<TemplateType, JSType> resolvedTypes, Set<JSType> seenTypes) {
        if (paramType.isTemplateType()) {
            // @param {T}
            resolvedTemplateType(resolvedTypes, paramType.toMaybeTemplateType(), argType);
        } else if (paramType.isUnionType()) {
            // @param {Array.<T>|NodeList|Arguments|{length:number}}
            UnionType unionType = paramType.toMaybeUnionType();
            for (JSType alernative : unionType.getAlternates()) {
                maybeResolveTemplatedType(alernative, argType, resolvedTypes, seenTypes);
            }
        } else if (paramType.isFunctionType()) {
            FunctionType paramFunctionType = paramType.toMaybeFunctionType();
            FunctionType argFunctionType = argType.restrictByNotNullOrUndefined().collapseUnion()
                    .toMaybeFunctionType();
            if (argFunctionType != null && argFunctionType.isSubtype(paramType)) {
                // infer from return type of the function type
                maybeResolveTemplatedType(paramFunctionType.getTypeOfThis(), argFunctionType.getTypeOfThis(),
                        resolvedTypes, seenTypes);
                // infer from return type of the function type
                maybeResolveTemplatedType(paramFunctionType.getReturnType(), argFunctionType.getReturnType(),
                        resolvedTypes, seenTypes);
                // infer from parameter types of the function type
                maybeResolveTemplateTypeFromNodes(paramFunctionType.getParameters(),
                        argFunctionType.getParameters(), resolvedTypes, seenTypes);
            }
        } else if (paramType.isRecordType() && !paramType.isNominalType()) {
            // @param {{foo:T}}
            if (seenTypes.add(paramType)) {
                ObjectType paramRecordType = paramType.toObjectType();
                ObjectType argObjectType = argType.restrictByNotNullOrUndefined().toObjectType();
                if (argObjectType != null && !argObjectType.isUnknownType() && !argObjectType.isEmptyType()) {
                    Set<String> names = paramRecordType.getPropertyNames();
                    for (String name : names) {
                        if (paramRecordType.hasOwnProperty(name) && argObjectType.hasProperty(name)) {
                            maybeResolveTemplatedType(paramRecordType.getPropertyType(name),
                                    argObjectType.getPropertyType(name), resolvedTypes, seenTypes);
                        }
                    }
                }
                seenTypes.remove(paramType);
            }
        } else if (paramType.isTemplatizedType()) {
            // @param {Array.<T>}
            ObjectType referencedParamType = paramType.toMaybeTemplatizedType().getReferencedType();
            JSType argObjectType = argType.restrictByNotNullOrUndefined().collapseUnion();

            if (argObjectType.isSubtype(referencedParamType)) {
                // If the argument type is a subtype of the parameter type, resolve any
                // template types amongst their templatized types.
                TemplateTypeMap paramTypeMap = paramType.getTemplateTypeMap();
                TemplateTypeMap argTypeMap = argObjectType.getTemplateTypeMap();
                for (TemplateType key : paramTypeMap.getTemplateKeys()) {
                    maybeResolveTemplatedType(paramTypeMap.getTemplateType(key), argTypeMap.getTemplateType(key),
                            resolvedTypes, seenTypes);
                }
            }
        }
    }

    private void maybeResolveTemplateTypeFromNodes(Iterable<Node> declParams, Iterable<Node> callParams,
            Map<TemplateType, JSType> resolvedTypes, Set<JSType> seenTypes) {
        maybeResolveTemplateTypeFromNodes(declParams.iterator(), callParams.iterator(), resolvedTypes, seenTypes);
    }

    private void maybeResolveTemplateTypeFromNodes(Iterator<Node> declParams, Iterator<Node> callParams,
            Map<TemplateType, JSType> resolvedTypes, Set<JSType> seenTypes) {
        while (declParams.hasNext() && callParams.hasNext()) {
            Node declParam = declParams.next();
            maybeResolveTemplatedType(getJSType(declParam), getJSType(callParams.next()), resolvedTypes, seenTypes);
            if (declParam.isVarArgs()) {
                while (callParams.hasNext()) {
                    maybeResolveTemplatedType(getJSType(declParam), getJSType(callParams.next()), resolvedTypes,
                            seenTypes);
                }
            }
        }
    }

    private static void resolvedTemplateType(Map<TemplateType, JSType> map, TemplateType template,
            JSType resolved) {
        JSType previous = map.get(template);
        if (!resolved.isUnknownType()) {
            if (previous == null) {
                map.put(template, resolved);
            } else {
                JSType join = previous.getLeastSupertype(resolved);
                map.put(template, join);
            }
        }
    }

    private static class TemplateTypeReplacer extends ModificationVisitor {
        private final Map<TemplateType, JSType> replacements;
        private final JSTypeRegistry registry;
        boolean madeChanges = false;

        TemplateTypeReplacer(JSTypeRegistry registry, Map<TemplateType, JSType> replacements) {
            super(registry, true);
            this.registry = registry;
            this.replacements = replacements;
        }

        @Override
        public JSType caseTemplateType(TemplateType type) {
            madeChanges = true;
            JSType replacement = replacements.get(type);
            return replacement != null ? replacement : registry.getNativeType(UNKNOWN_TYPE);
        }
    }

    /**
     * Build the type environment where type transformations will be evaluated.
     * It only considers the template type variables that do not have a type
     * transformation.
     */
    private Map<String, JSType> buildTypeVariables(Map<TemplateType, JSType> inferredTypes) {
        Map<String, JSType> typeVars = new HashMap<>();
        for (Entry<TemplateType, JSType> e : inferredTypes.entrySet()) {
            // Only add the template type that do not have a type transformation
            if (!e.getKey().isTypeTransformation()) {
                typeVars.put(e.getKey().getReferenceName(), e.getValue());
            }
        }
        return typeVars;
    }

    /**
     * This function will evaluate the type transformations associated to the
     * template types
     */
    private Map<TemplateType, JSType> evaluateTypeTransformations(ImmutableList<TemplateType> templateTypes,
            Map<TemplateType, JSType> inferredTypes) {

        Map<String, JSType> typeVars = null;
        Map<TemplateType, JSType> result = null;
        TypeTransformation ttlObj = null;

        for (TemplateType type : templateTypes) {
            if (type.isTypeTransformation()) {
                // Lazy initialization when the first type transformation is found
                if (ttlObj == null) {
                    ttlObj = new TypeTransformation(compiler, syntacticScope);
                    typeVars = buildTypeVariables(inferredTypes);
                    result = new HashMap<>();
                }
                // Evaluate the type transformation expression using the current
                // known types for the template type variables
                JSType transformedType = ttlObj.eval(type.getTypeTransformation(),
                        ImmutableMap.<String, JSType>copyOf(typeVars));
                result.put(type, transformedType);
                // Add the transformed type to the type variables
                typeVars.put(type.getReferenceName(), transformedType);
            }
        }
        return result;
    }

    /**
     * For functions with function(this: T, ...) and T as parameters, type
     * inference will set the type of this on a function literal argument to the
     * the actual type of T.
     */
    private boolean inferTemplatedTypesForCall(Node n, FunctionType fnType) {
        final ImmutableList<TemplateType> keys = fnType.getTemplateTypeMap().getTemplateKeys();
        if (keys.isEmpty()) {
            return false;
        }

        // Try to infer the template types
        Map<TemplateType, JSType> rawInferrence = inferTemplateTypesFromParameters(fnType, n);
        Map<TemplateType, JSType> inferred = Maps.newIdentityHashMap();
        for (TemplateType key : keys) {
            JSType type = rawInferrence.get(key);
            if (type == null) {
                type = unknownType;
            }
            inferred.put(key, type);
        }

        // Try to infer the template types using the type transformations
        Map<TemplateType, JSType> typeTransformations = evaluateTypeTransformations(keys, inferred);
        if (typeTransformations != null) {
            inferred.putAll(typeTransformations);
        }

        // Replace all template types. If we couldn't find a replacement, we
        // replace it with UNKNOWN.
        TemplateTypeReplacer replacer = new TemplateTypeReplacer(registry, inferred);
        Node callTarget = n.getFirstChild();

        FunctionType replacementFnType = fnType.visit(replacer).toMaybeFunctionType();
        Preconditions.checkNotNull(replacementFnType);

        callTarget.setJSType(replacementFnType);
        n.setJSType(replacementFnType.getReturnType());

        return replacer.madeChanges;
    }

    private FlowScope traverseNew(Node n, FlowScope scope) {
        scope = traverseChildren(n, scope);

        Node constructor = n.getFirstChild();
        JSType constructorType = constructor.getJSType();
        JSType type = null;
        if (constructorType != null) {
            constructorType = constructorType.restrictByNotNullOrUndefined();
            if (constructorType.isUnknownType()) {
                type = unknownType;
            } else {
                FunctionType ct = constructorType.toMaybeFunctionType();
                if (ct == null && constructorType instanceof FunctionType) {
                    // If constructorType is a NoObjectType, then toMaybeFunctionType will
                    // return null. But NoObjectType implements the FunctionType
                    // interface, precisely because it can validly construct objects.
                    ct = (FunctionType) constructorType;
                }
                if (ct != null && ct.isConstructor()) {
                    backwardsInferenceFromCallSite(n, ct);

                    // If necessary, create a TemplatizedType wrapper around the instance
                    // type, based on the types of the constructor parameters.
                    ObjectType instanceType = ct.getInstanceType();
                    Map<TemplateType, JSType> inferredTypes = inferTemplateTypesFromParameters(ct, n);
                    if (inferredTypes.isEmpty()) {
                        type = instanceType;
                    } else {
                        type = registry.createTemplatizedType(instanceType, inferredTypes);
                    }
                }
            }
        }
        n.setJSType(type);
        return scope;
    }

    private BooleanOutcomePair traverseAnd(Node n, FlowScope scope) {
        return traverseShortCircuitingBinOp(n, scope);
    }

    private FlowScope traverseChildren(Node n, FlowScope scope) {
        for (Node el = n.getFirstChild(); el != null; el = el.getNext()) {
            scope = traverse(el, scope);
        }
        return scope;
    }

    private FlowScope traverseGetElem(Node n, FlowScope scope) {
        scope = traverseChildren(n, scope);
        JSType type = getJSType(n.getFirstChild()).restrictByNotNullOrUndefined();
        TemplateTypeMap typeMap = type.getTemplateTypeMap();
        // check the template value for IObject
        if (typeMap.hasTemplateType(registry.getIObjectValueKey())) {
            n.setJSType(typeMap.getConcreteTypeOfTemplateType(registry.getIObjectValueKey()));
        } else if (typeMap.hasTemplateType(registry.getObjectElementKey())) {
            n.setJSType(typeMap.getTemplateType(registry.getObjectElementKey()));
        }
        return dereferencePointer(n.getFirstChild(), scope);
    }

    private FlowScope traverseGetProp(Node n, FlowScope scope) {
        Node objNode = n.getFirstChild();
        Node property = n.getLastChild();
        scope = traverseChildren(n, scope);

        n.setJSType(getPropertyType(objNode.getJSType(), property.getString(), n, scope));
        return dereferencePointer(n.getFirstChild(), scope);
    }

    /**
     * Suppose X is an object with inferred properties.
     * Suppose also that X is used in a way where it would only type-check
     * correctly if some of those properties are widened.
     * Then we should be polite and automatically widen X's properties for him.
     *
     * For a concrete example, consider:
     * param x {{prop: (number|undefined)}}
     * function f(x) {}
     * f({});
     *
     * If we give the anonymous object an inferred property of (number|undefined),
     * then this code will type-check appropriately.
     */
    private static void inferPropertyTypesToMatchConstraint(JSType type, JSType constraint) {
        if (type == null || constraint == null) {
            return;
        }

        type.matchConstraint(constraint);
    }

    /**
     * If we access a property of a symbol, then that symbol is not
     * null or undefined.
     */
    private FlowScope dereferencePointer(Node n, FlowScope scope) {
        if (n.isQualifiedName()) {
            JSType type = getJSType(n);
            JSType narrowed = type.restrictByNotNullOrUndefined();
            if (type != narrowed) {
                scope = narrowScope(scope, n, narrowed);
            }
        }
        return scope;
    }

    private JSType getPropertyType(JSType objType, String propName, Node n, FlowScope scope) {
        // We often have a couple of different types to choose from for the
        // property. Ordered by accuracy, we have
        // 1) A locally inferred qualified name (which is in the FlowScope)
        // 2) A globally declared qualified name (which is in the FlowScope)
        // 3) A property on the owner type (which is on objType)
        // 4) A name in the type registry (as a last resort)
        JSType propertyType = null;
        boolean isLocallyInferred = false;

        // Scopes sometimes contain inferred type info about qualified names.
        String qualifiedName = n.getQualifiedName();
        StaticTypedSlot<JSType> var = scope.getSlot(qualifiedName);
        if (var != null) {
            JSType varType = var.getType();
            if (varType != null) {
                boolean isDeclared = !var.isTypeInferred();
                isLocallyInferred = (var != syntacticScope.getSlot(qualifiedName));
                if (isDeclared || isLocallyInferred) {
                    propertyType = varType;
                }
            }
        }

        if (propertyType == null && objType != null) {
            JSType foundType = objType.findPropertyType(propName);
            if (foundType != null) {
                propertyType = foundType;
            }
        }

        if (propertyType != null && objType != null) {
            JSType restrictedObjType = objType.restrictByNotNullOrUndefined();
            if (!restrictedObjType.getTemplateTypeMap().isEmpty() && propertyType.hasAnyTemplateTypes()) {
                TemplateTypeMap typeMap = restrictedObjType.getTemplateTypeMap();
                TemplateTypeMapReplacer replacer = new TemplateTypeMapReplacer(registry, typeMap);
                propertyType = propertyType.visit(replacer);
            }
        }

        if ((propertyType == null || propertyType.isUnknownType()) && qualifiedName != null) {
            // If we find this node in the registry, then we can infer its type.
            ObjectType regType = ObjectType.cast(registry.getType(qualifiedName));
            if (regType != null) {
                propertyType = regType.getConstructor();
            }
        }

        if (propertyType == null) {
            return unknownType;
        } else if (propertyType.isEquivalentTo(unknownType) && isLocallyInferred) {
            // If the type has been checked in this scope,
            // then use CHECKED_UNKNOWN_TYPE instead to indicate that.
            return getNativeType(CHECKED_UNKNOWN_TYPE);
        } else {
            return propertyType;
        }
    }

    private BooleanOutcomePair traverseOr(Node n, FlowScope scope) {
        return traverseShortCircuitingBinOp(n, scope);
    }

    private BooleanOutcomePair traverseShortCircuitingBinOp(Node n, FlowScope scope) {
        Preconditions.checkArgument(n.isAnd() || n.isOr());
        boolean nIsAnd = n.isAnd();
        Node left = n.getFirstChild();
        Node right = n.getLastChild();

        // type the left node
        BooleanOutcomePair leftOutcome = traverseWithinShortCircuitingBinOp(left, scope.createChildFlowScope());
        JSType leftType = left.getJSType();

        // reverse abstract interpret the left node to produce the correct
        // scope in which to verify the right node
        FlowScope rightScope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome(left,
                leftOutcome.getOutcomeFlowScope(left.getType(), nIsAnd), nIsAnd);

        // type the right node
        BooleanOutcomePair rightOutcome = traverseWithinShortCircuitingBinOp(right,
                rightScope.createChildFlowScope());
        JSType rightType = right.getJSType();

        JSType type;
        BooleanOutcomePair outcome;
        if (leftType != null && rightType != null) {
            leftType = leftType.getRestrictedTypeGivenToBooleanOutcome(!nIsAnd);
            if (leftOutcome.toBooleanOutcomes == BooleanLiteralSet.get(!nIsAnd)) {
                // Either n is && and lhs is false, or n is || and lhs is true.
                // Use the restricted left type; the right side never gets evaluated.
                type = leftType;
                outcome = leftOutcome;
            } else {
                // Use the join of the restricted left type knowing the outcome of the
                // ToBoolean predicate and of the right type.
                type = leftType.getLeastSupertype(rightType);
                outcome = new BooleanOutcomePair(
                        joinBooleanOutcomes(nIsAnd, leftOutcome.toBooleanOutcomes, rightOutcome.toBooleanOutcomes),
                        joinBooleanOutcomes(nIsAnd, leftOutcome.booleanValues, rightOutcome.booleanValues),
                        leftOutcome.getJoinedFlowScope(), rightOutcome.getJoinedFlowScope());
            }
            // Exclude the boolean type if the literal set is empty because a boolean
            // can never actually be returned.
            if (outcome.booleanValues == BooleanLiteralSet.EMPTY && getNativeType(BOOLEAN_TYPE).isSubtype(type)) {
                // Exclusion only makes sense for a union type.
                if (type.isUnionType()) {
                    type = type.toMaybeUnionType().getRestrictedUnion(getNativeType(BOOLEAN_TYPE));
                }
            }
        } else {
            type = null;
            outcome = new BooleanOutcomePair(BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH,
                    leftOutcome.getJoinedFlowScope(), rightOutcome.getJoinedFlowScope());
        }
        n.setJSType(type);
        return outcome;
    }

    private BooleanOutcomePair traverseWithinShortCircuitingBinOp(Node n, FlowScope scope) {
        switch (n.getType()) {
        case Token.AND:
            return traverseAnd(n, scope);

        case Token.OR:
            return traverseOr(n, scope);

        default:
            scope = traverse(n, scope);
            return newBooleanOutcomePair(n.getJSType(), scope);
        }
    }

    private static BooleanLiteralSet joinBooleanOutcomes(boolean isAnd, BooleanLiteralSet left,
            BooleanLiteralSet right) {
        // A truthy value on the lhs of an {@code &&} can never make it to the
        // result. Same for a falsy value on the lhs of an {@code ||}.
        // Hence the intersection.
        return right.union(left.intersection(BooleanLiteralSet.get(!isAnd)));
    }

    /**
     * When traversing short-circuiting binary operations, we need to keep track
     * of two sets of boolean literals:
     * 1. {@code toBooleanOutcomes}: boolean literals as converted from any types,
     * 2. {@code booleanValues}: boolean literals from just boolean types.
     */
    private final class BooleanOutcomePair {
        final BooleanLiteralSet toBooleanOutcomes;
        final BooleanLiteralSet booleanValues;

        // The scope if only half of the expression executed, when applicable.
        final FlowScope leftScope;

        // The scope when the whole expression executed.
        final FlowScope rightScope;

        // The scope when we don't know how much of the expression is executed.
        FlowScope joinedScope = null;

        BooleanOutcomePair(BooleanLiteralSet toBooleanOutcomes, BooleanLiteralSet booleanValues,
                FlowScope leftScope, FlowScope rightScope) {
            this.toBooleanOutcomes = toBooleanOutcomes;
            this.booleanValues = booleanValues;
            this.leftScope = leftScope;
            this.rightScope = rightScope;
        }

        /**
         * Gets the safe estimated scope without knowing if all of the
         * subexpressions will be evaluated.
         */
        FlowScope getJoinedFlowScope() {
            if (joinedScope == null) {
                if (leftScope == rightScope) {
                    joinedScope = rightScope;
                } else {
                    joinedScope = join(leftScope, rightScope);
                }
            }
            return joinedScope;
        }

        /**
         * Gets the outcome scope if we do know the outcome of the entire
         * expression.
         */
        FlowScope getOutcomeFlowScope(int nodeType, boolean outcome) {
            if (nodeType == Token.AND && outcome || nodeType == Token.OR && !outcome) {
                // We know that the whole expression must have executed.
                return rightScope;
            } else {
                return getJoinedFlowScope();
            }
        }
    }

    private BooleanOutcomePair newBooleanOutcomePair(JSType jsType, FlowScope flowScope) {
        if (jsType == null) {
            return new BooleanOutcomePair(BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH, flowScope, flowScope);
        }
        return new BooleanOutcomePair(jsType.getPossibleToBooleanOutcomes(),
                registry.getNativeType(BOOLEAN_TYPE).isSubtype(jsType) ? BooleanLiteralSet.BOTH
                        : BooleanLiteralSet.EMPTY,
                flowScope, flowScope);
    }

    private void redeclareSimpleVar(FlowScope scope, Node nameNode, JSType varType) {
        Preconditions.checkState(nameNode.isName());
        String varName = nameNode.getString();
        if (varType == null) {
            varType = getNativeType(JSTypeNative.UNKNOWN_TYPE);
        }
        if (isUnflowable(syntacticScope.getVar(varName))) {
            return;
        }
        scope.inferSlotType(varName, varType);
    }

    private boolean isUnflowable(TypedVar v) {
        return v != null && v.isLocal() && v.isMarkedEscaped() &&
        // It's OK to flow a variable in the scope where it's escaped.
                v.getScope() == syntacticScope;
    }

    /**
     * This method gets the JSType from the Node argument and verifies that it is
     * present.
     */
    private JSType getJSType(Node n) {
        JSType jsType = n.getJSType();
        if (jsType == null) {
            // TODO(nicksantos): This branch indicates a compiler bug, not worthy of
            // halting the compilation but we should log this and analyze to track
            // down why it happens. This is not critical and will be resolved over
            // time as the type checker is extended.
            return unknownType;
        } else {
            return jsType;
        }
    }

    private JSType getNativeType(JSTypeNative typeId) {
        return registry.getNativeType(typeId);
    }
}