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

Java tutorial

Introduction

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

Source

/*
 * Copyright 2018 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.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
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.ObjectType;
import com.google.javascript.rhino.jstype.TemplateTypeMap;
import com.google.javascript.rhino.jstype.TemplateTypeMapReplacer;
import javax.annotation.Nullable;

/**
 * Creates AST nodes and subtrees.
 *
 * <p>This class supports creating nodes either with or without type information.
 *
 * <p>The idea is that client code can create the trees of nodes it needs without having to contain
 * logic for deciding whether type information should be added or not, and only minimal logic for
 * determining which types to add when they are necessary. Most methods in this class are able to
 * determine the correct type information from already existing AST nodes and the current scope.
 */
final class AstFactory {

    @Nullable
    private final JSTypeRegistry registry;
    // We need the unknown type so frequently, it's worth caching it.
    private final JSType unknownType;
    // We might not need Arguments type, but if we do, we should avoid redundant lookups
    private final Supplier<JSType> argumentsTypeSupplier;

    private AstFactory() {
        this.registry = null;
        unknownType = null;
        argumentsTypeSupplier = () -> null;
    }

    private AstFactory(JSTypeRegistry registry) {
        this.registry = registry;
        this.unknownType = getNativeType(JSTypeNative.UNKNOWN_TYPE);
        this.argumentsTypeSupplier = Suppliers.memoize(() -> {
            JSType globalType = registry.getGlobalType("Arguments");
            if (globalType != null) {
                return globalType;
            } else {
                return unknownType;
            }
        });
        ;
    }

    static AstFactory createFactoryWithoutTypes() {
        return new AstFactory();
    }

    static AstFactory createFactoryWithTypes(JSTypeRegistry registry) {
        return new AstFactory(registry);
    }

    /** Does this class instance add types to the nodes it creates? */
    boolean isAddingTypes() {
        return registry != null;
    }

    /**
     * Returns a new EXPR_RESULT node.
     *
     * <p>Statements have no type information, so this is functionally the same as calling {@code
     * IR.exprResult(expr)}. It exists so that a pass can be consistent about always using {@code
     * AstFactory} to create new nodes.
     */
    Node exprResult(Node expr) {
        return IR.exprResult(expr);
    }

    /**
     * Returns a new EMPTY node.
     *
     * <p>EMPTY Nodes have no type information, so this is functionally the same as calling {@code
     * IR.empty()}. It exists so that a pass can be consistent about always using {@code AstFactory}
     * to create new nodes.
     */
    Node createEmpty() {
        return IR.empty();
    }

    /**
     * Returns a new BLOCK node.
     *
     * <p>Blocks have no type information, so this is functionally the same as calling {@code
     * IR.block()}. It exists so that a pass can be consistent about always using {@code AstFactory}
     * to create new nodes.
     */
    Node createBlock() {
        return IR.block();
    }

    /**
     * Returns a new BLOCK node.
     *
     * <p>Blocks have no type information, so this is functionally the same as calling {@code
     * IR.block(statement)}. It exists so that a pass can be consistent about always using {@code
     * AstFactory} to create new nodes.
     */
    Node createBlock(Node statement) {
        return IR.block(statement);
    }

    /**
     * Returns a new {@code return} statement.
     *
     * <p>Return statements have no type information, so this is functionally the same as calling
     * {@code IR.return(value)}. It exists so that a pass can be consistent about always using {@code
     * AstFactory} to create new nodes.
     */
    Node createReturn(Node value) {
        return IR.returnNode(value);
    }

    /**
     * Returns a new {@code yield} expression.
     *
     * @param jsType Type we expect to get back after the yield
     * @param value value to yield
     */
    Node createYield(JSType jsType, Node value) {
        Node result = IR.yield(value);
        if (isAddingTypes()) {
            result.setJSType(checkNotNull(jsType));
        }
        return result;
    }

    Node createString(String value) {
        Node result = IR.string(value);
        if (isAddingTypes()) {
            result.setJSType(getNativeType(JSTypeNative.STRING_TYPE));
        }
        return result;
    }

    Node createNumber(double value) {
        Node result = IR.number(value);
        if (isAddingTypes()) {
            result.setJSType(getNativeType(JSTypeNative.NUMBER_TYPE));
        }
        return result;
    }

    Node createBoolean(boolean value) {
        Node result = value ? IR.trueNode() : IR.falseNode();
        if (isAddingTypes()) {
            result.setJSType(getNativeType(JSTypeNative.BOOLEAN_TYPE));
        }
        return result;
    }

    Node createNull() {
        Node result = IR.nullNode();
        if (isAddingTypes()) {
            result.setJSType(getNativeType(JSTypeNative.NULL_TYPE));
        }
        return result;
    }

    Node createThis(JSType thisType) {
        Node result = IR.thisNode();
        if (isAddingTypes()) {
            result.setJSType(checkNotNull(thisType));
        }
        return result;
    }

    /** Creates a THIS node with the correct type for the given function node. */
    Node createThisForFunction(Node functionNode) {
        final Node result = IR.thisNode();
        if (isAddingTypes()) {
            result.setJSType(getTypeOfThisForFunctionNode(functionNode));
        }
        return result;
    }

    @Nullable
    private JSType getTypeOfThisForFunctionNode(Node functionNode) {
        if (isAddingTypes()) {
            FunctionType functionType = getFunctionType(functionNode);
            return checkNotNull(functionType.getTypeOfThis(), functionType);
        } else {
            return null; // not adding type information
        }
    }

    private FunctionType getFunctionType(Node functionNode) {
        checkState(functionNode.isFunction(), "not a function: %s", functionNode);
        // If the function declaration was cast to a different type, we want the original type
        // from before the cast.
        final JSType typeBeforeCast = functionNode.getJSTypeBeforeCast();
        final FunctionType functionType;
        if (typeBeforeCast != null) {
            functionType = typeBeforeCast.assertFunctionType();
        } else {
            functionType = functionNode.getJSTypeRequired().assertFunctionType();
        }
        return functionType;
    }

    /** Creates a NAME node having the type of "this" appropriate for the given function node. */
    Node createThisAliasReferenceForFunction(String aliasName, Node functionNode) {
        final Node result = IR.name(aliasName);
        if (isAddingTypes()) {
            result.setJSType(getTypeOfThisForFunctionNode(functionNode));
        }
        return result;
    }

    /**
     * Creates a statement declaring a const alias for "this" to be used in the given function node.
     *
     * <p>e.g. `const aliasName = this;`
     */
    Node createThisAliasDeclarationForFunction(String aliasName, Node functionNode) {
        return createSingleConstNameDeclaration(aliasName, createThis(getTypeOfThisForFunctionNode(functionNode)));
    }

    /**
     * Creates a new `const` declaration statement for a single variable name.
     *
     * <p>Takes the type for the variable name from the value node.
     *
     * <p>e.g. `const variableName = value;`
     */
    Node createSingleConstNameDeclaration(String variableName, Node value) {
        return IR.constNode(createName(variableName, value.getJSType()), value);
    }

    /**
     * Creates a reference to "arguments" with the type specified in externs, or unknown if the
     * externs for it weren't included.
     */
    Node createArgumentsReference() {
        Node result = IR.name("arguments");
        if (isAddingTypes()) {
            result.setJSType(argumentsTypeSupplier.get());
        }
        return result;
    }

    /**
     * Creates a statement declaring a const alias for "arguments".
     *
     * <p>e.g. `const argsAlias = arguments;`
     */
    Node createArgumentsAliasDeclaration(String aliasName) {
        return createSingleConstNameDeclaration(aliasName, createArgumentsReference());
    }

    Node createName(String name, JSType type) {
        Node result = IR.name(name);
        if (isAddingTypes()) {
            result.setJSType(checkNotNull(type));
        }
        return result;
    }

    Node createName(String name, JSTypeNative nativeType) {
        Node result = IR.name(name);
        if (isAddingTypes()) {
            result.setJSType(getNativeType(nativeType));
        }
        return result;
    }

    Node createNameWithUnknownType(String name) {
        return createName(name, unknownType);
    }

    Node createName(Scope scope, String name) {
        Node result = IR.name(name);
        if (isAddingTypes()) {
            result.setJSType(getVarNameType(scope, name));
        }
        return result;
    }

    Node createQName(Scope scope, String qname) {
        return createQName(scope, Splitter.on(".").split(qname));
    }

    private Node createQName(Scope scope, Iterable<String> names) {
        String baseName = checkNotNull(Iterables.getFirst(names, null));
        Iterable<String> propertyNames = Iterables.skip(names, 1);
        Node baseNameNode = createName(scope, baseName);
        return createGetProps(baseNameNode, propertyNames);
    }

    Node createGetProp(Node receiver, String propertyName) {
        Node result = IR.getprop(receiver, IR.string(propertyName));
        if (isAddingTypes()) {
            result.setJSType(getJsTypeForProperty(receiver, propertyName));
        }
        return result;
    }

    /** Creates a tree of nodes representing `receiver.name1.name2.etc`. */
    private Node createGetProps(Node receiver, Iterable<String> propertyNames) {
        Node result = receiver;
        for (String propertyName : propertyNames) {
            result = createGetProp(result, propertyName);
        }
        return result;
    }

    /** Creates a tree of nodes representing `receiver.name1.name2.etc`. */
    Node createGetProps(Node receiver, String firstPropName, String... otherPropNames) {
        Node result = createGetProp(receiver, firstPropName);
        for (String propertyName : otherPropNames) {
            result = createGetProp(result, propertyName);
        }
        return result;
    }

    Node createGetElem(Node receiver, Node key) {
        Node result = IR.getelem(receiver, key);
        if (isAddingTypes()) {
            // In general we cannot assume we know the type we get from a GETELEM.
            // TODO(bradfordcsmith): When receiver is an Array<T> or an Object<K, V>, use the template
            // type here.
            result.setJSType(unknownType);
        }
        return result;
    }

    Node createDelProp(Node target) {
        Node result = IR.delprop(target);
        if (isAddingTypes()) {
            result.setJSType(getNativeType(JSTypeNative.BOOLEAN_TYPE));
        }
        return result;
    }

    Node createStringKey(String key, Node value) {
        Node result = IR.stringKey(key, value);
        if (isAddingTypes()) {
            result.setJSType(value.getJSType());
        }
        return result;
    }

    Node createComputedProperty(Node key, Node value) {
        Node result = IR.computedProp(key, value);
        if (isAddingTypes()) {
            result.setJSType(value.getJSType());
        }
        return result;
    }

    Node createIn(Node left, Node right) {
        Node result = IR.in(left, right);
        if (isAddingTypes()) {
            result.setJSType(getNativeType(JSTypeNative.BOOLEAN_TYPE));
        }
        return result;
    }

    Node createComma(Node left, Node right) {
        Node result = IR.comma(left, right);
        if (isAddingTypes()) {
            result.setJSType(right.getJSType());
        }
        return result;
    }

    Node createCommas(Node first, Node second, Node... rest) {
        Node result = createComma(first, second);
        for (Node next : rest) {
            result = createComma(result, next);
        }
        return result;
    }

    Node createAnd(Node left, Node right) {
        Node result = IR.and(left, right);
        if (isAddingTypes()) {
            JSType leftType = checkNotNull(left.getJSType(), left);
            JSType rightType = checkNotNull(right.getJSType(), right);

            BooleanLiteralSet possibleLhsBooleanValues = leftType.getPossibleToBooleanOutcomes();
            switch (possibleLhsBooleanValues) {
            case TRUE:
                // left cannot be false, so rhs will always be evaluated
                result.setJSType(rightType);
                break;
            case FALSE:
                // left cannot be true, so rhs will never be evaluated
                result.setJSType(leftType);
                break;
            case BOTH:
                // result could be the type of either the lhs or the rhs
                result.setJSType(leftType.getLeastSupertype(rightType));
                break;
            default:
                checkState(possibleLhsBooleanValues == BooleanLiteralSet.EMPTY, "unexpected enum value: %s",
                        possibleLhsBooleanValues);
                // TODO(bradfordcsmith): Should we be trying to determine whether we actually need
                // NO_OBJECT_TYPE or similar here? It probably doesn't matter since this code is
                // expected to execute only after all static type analysis has been done.
                result.setJSType(getNativeType(JSTypeNative.NO_TYPE));
                break;
            }
        }
        return result;
    }

    Node createOr(Node left, Node right) {
        Node result = IR.or(left, right);
        if (isAddingTypes()) {
            JSType leftType = checkNotNull(left.getJSType(), left);
            JSType rightType = checkNotNull(right.getJSType(), right);

            BooleanLiteralSet possibleLhsBooleanValues = leftType.getPossibleToBooleanOutcomes();
            switch (possibleLhsBooleanValues) {
            case TRUE:
                // left cannot be false, so rhs will never be evaluated
                result.setJSType(leftType);
                break;
            case FALSE:
                // left cannot be true, so rhs will always be evaluated
                result.setJSType(rightType);
                break;
            case BOTH:
                // result could be the type of either the lhs or the rhs
                result.setJSType(leftType.getLeastSupertype(rightType));
                break;
            default:
                checkState(possibleLhsBooleanValues == BooleanLiteralSet.EMPTY, "unexpected enum value: %s",
                        possibleLhsBooleanValues);
                // TODO(bradfordcsmith): Should we be trying to determine whether we actually need
                // NO_OBJECT_TYPE or similar here? It probably doesn't matter since this code is
                // expected to execute only after all static type analysis has been done.
                result.setJSType(getNativeType(JSTypeNative.NO_TYPE));
                break;
            }
        }
        return result;
    }

    Node createCall(Node callee, Node... args) {
        Node result = NodeUtil.newCallNode(callee, args);
        if (isAddingTypes()) {
            FunctionType calleeType = JSType.toMaybeFunctionType(callee.getJSType());
            // TODO(sdh): this does not handle generic functions - we'd need to unify the argument types.
            // checkState(calleeType == null || !calleeType.hasAnyTemplateTypes(), calleeType);
            // TODO(bradfordcsmith): Consider throwing an exception if calleeType is null.
            JSType returnType = calleeType != null ? calleeType.getReturnType() : unknownType;
            result.setJSType(returnType);
        }
        return result;
    }

    Node createObjectGetPrototypeOfCall(Node argObjectNode) {
        Node objectName = createName("Object", JSTypeNative.OBJECT_FUNCTION_TYPE);
        Node objectGetPrototypeOf = createGetProp(objectName, "getPrototypeOf");
        Node result = NodeUtil.newCallNode(objectGetPrototypeOf, argObjectNode);
        if (isAddingTypes()) {
            ObjectType typeOfArgObject = argObjectNode.getJSTypeRequired().assertObjectType();
            result.setJSType(getPrototypeObjectType(typeOfArgObject));
        }
        return result;
    }

    ObjectType getPrototypeObjectType(ObjectType objectType) {
        checkNotNull(objectType);
        if (objectType.isUnknownType()) {
            // Calling getImplicitPrototype() on the unknown type returns `null`, but we want
            // the prototype of an unknown type to also be unknown.
            // TODO(bradfordcsmith): Can we fix this behavior of the unknown type?
            return objectType;
        } else {
            return checkNotNull(objectType.getImplicitPrototype(), "null prototype: %s", objectType);
        }
    }

    /**
     * Create a call that returns an instance of the given class type.
     *
     * <p>This method is intended for use in special cases, such as calling `super()` in a
     * constructor.
     */
    Node createConstructorCall(@Nullable JSType classType, Node callee, Node... args) {
        Node result = NodeUtil.newCallNode(callee, args);
        if (isAddingTypes()) {
            checkNotNull(classType);
            FunctionType constructorType = checkNotNull(classType.toMaybeFunctionType());
            ObjectType instanceType = checkNotNull(constructorType.getInstanceType());
            result.setJSType(instanceType);
        }
        return result;
    }

    /** Creates a statement `lhs = rhs;`. */
    Node createAssignStatement(Node lhs, Node rhs) {
        return exprResult(createAssign(lhs, rhs));
    }

    /** Creates an assignment expression `lhs = rhs` */
    Node createAssign(Node lhs, Node rhs) {
        Node result = IR.assign(lhs, rhs);
        if (isAddingTypes()) {
            result.setJSType(rhs.getJSType());
        }
        return result;
    }

    /** Creates an empty object literal, `{}`. */
    Node createEmptyObjectLit() {
        Node result = IR.objectlit();
        if (isAddingTypes()) {
            result.setJSType(registry.createAnonymousObjectType(null));
        }
        return result;
    }

    /** Creates an empty function `function() {}` */
    Node createEmptyFunction(JSType type) {
        Node result = NodeUtil.emptyFunction();
        if (isAddingTypes()) {
            checkNotNull(type);
            checkArgument(type.isFunctionType(), type);
            result.setJSType(checkNotNull(type));
        }
        return result;
    }

    /**
     * Creates a function `function name(paramList) { body }`
     *
     * @param name STRING node - empty string if no name
     * @param paramList PARAM_LIST node
     * @param body BLOCK node
     * @param type type to apply to the function itself
     */
    Node createFunction(String name, Node paramList, Node body, JSType type) {
        Node nameNode = createName(name, type);
        Node result = IR.function(nameNode, paramList, body);
        if (isAddingTypes()) {
            checkArgument(type.isFunctionType(), type);
            result.setJSType(type);
        }
        return result;
    }

    Node createZeroArgFunction(String name, Node body, @Nullable JSType returnType) {
        FunctionType functionType = isAddingTypes() ? registry.createFunctionType(returnType) : null;
        return createFunction(name, IR.paramList(), body, functionType);
    }

    Node createZeroArgGeneratorFunction(String name, Node body, @Nullable JSType returnType) {
        Node result = createZeroArgFunction(name, body, returnType);
        result.setIsGeneratorFunction(true);
        return result;
    }

    Node createZeroArgArrowFunctionForExpression(Node expression) {
        Node result = IR.arrowFunction(IR.name(""), IR.paramList(), expression);
        if (isAddingTypes()) {
            // It feels like we should be adding type-of-this here, but it should remain unknown,
            // because you're allowed to supply any kind of value of `this` when calling an arrow
            // function. It will just be ignored in favor of the `this` in the scope where the
            // arrow was defined.
            FunctionType functionType = new FunctionBuilder(registry).withReturnType(expression.getJSTypeRequired())
                    .withParamsNode(IR.paramList()).build();
            result.setJSType(functionType);
        }
        return result;
    }

    Node createMemberFunctionDef(String name, Node function) {
        // A function used for a member function definition must have an empty name,
        // because the name string goes on the MEMBER_FUNCTION_DEF node.
        checkArgument(function.getFirstChild().getString().isEmpty(), function);
        Node result = IR.memberFunctionDef(name, function);
        if (isAddingTypes()) {
            // member function definition must share the type of the function that implements it
            result.setJSType(function.getJSType());
        }
        return result;
    }

    Node createSheq(Node expr1, Node expr2) {
        Node result = IR.sheq(expr1, expr2);
        if (isAddingTypes()) {
            result.setJSType(getNativeType(JSTypeNative.BOOLEAN_TYPE));
        }
        return result;
    }

    Node createHook(Node condition, Node expr1, Node expr2) {
        Node result = IR.hook(condition, expr1, expr2);
        if (isAddingTypes()) {
            result.setJSType(registry.createUnionType(expr1.getJSType(), expr2.getJSType()));
        }
        return result;
    }

    Node createArraylit(Node... elements) {
        Node result = IR.arraylit(elements);
        if (isAddingTypes()) {
            result.setJSType(registry.createTemplatizedType(registry.getNativeObjectType(JSTypeNative.ARRAY_TYPE),
                    // TODO(nickreid): Use a reasonable template type. Remeber to consider SPREAD.
                    getNativeType(JSTypeNative.UNKNOWN_TYPE)));
        }
        return result;
    }

    Node createJSCompMakeIteratorCall(Node iterable, Scope scope) {
        String function = "makeIterator";
        Node makeIteratorName = createQName(scope, "$jscomp." + function);
        // Since createCall (currently) doesn't handle templated functions, fill in the template types
        // of makeIteratorName manually.
        if (isAddingTypes() && !makeIteratorName.getJSType().isUnknownType()) {
            // if makeIteratorName has the unknown type, we must have not injected the required runtime
            // libraries - hopefully because this is in a test using NonInjectingCompiler.

            // e.g get `number` from `Iterable<number>`
            JSType iterableType = iterable.getJSType()
                    .getInstantiatedTypeArgument(getNativeType(JSTypeNative.ITERABLE_TYPE));
            JSType makeIteratorType = makeIteratorName.getJSType();
            // e.g. replace
            //   function(Iterable<T>): Iterator<T>
            // with
            //   function(Iterable<number>): Iterator<number>
            TemplateTypeMap typeMap = registry.createTemplateTypeMap(
                    makeIteratorType.getTemplateTypeMap().getTemplateKeys(), ImmutableList.of(iterableType));
            TemplateTypeMapReplacer replacer = new TemplateTypeMapReplacer(registry, typeMap);
            makeIteratorName.setJSType(makeIteratorType.visit(replacer));
        }
        return createCall(makeIteratorName, iterable);
    }

    Node createJscompArrayFromIteratorCall(Node iterator, Scope scope) {
        String function = "arrayFromIterator";
        Node makeIteratorName = createQName(scope, "$jscomp." + function);
        // Since createCall (currently) doesn't handle templated functions, fill in the template types
        // of makeIteratorName manually.
        if (isAddingTypes() && !makeIteratorName.getJSType().isUnknownType()) {
            // if makeIteratorName has the unknown type, we must have not injected the required runtime
            // libraries - hopefully because this is in a test using NonInjectingCompiler.

            // e.g get `number` from `Iterator<number>`
            JSType iterableType = iterator.getJSType()
                    .getInstantiatedTypeArgument(getNativeType(JSTypeNative.ITERATOR_TYPE));
            JSType makeIteratorType = makeIteratorName.getJSType();
            // e.g. replace
            //   function(Iterator<T>): Array<T>
            // with
            //   function(Iterator<number>): Array<number>
            TemplateTypeMap typeMap = registry.createTemplateTypeMap(
                    makeIteratorType.getTemplateTypeMap().getTemplateKeys(), ImmutableList.of(iterableType));
            TemplateTypeMapReplacer replacer = new TemplateTypeMapReplacer(registry, typeMap);
            makeIteratorName.setJSType(makeIteratorType.visit(replacer));
        }
        return createCall(makeIteratorName, iterator);
    }

    Node createJscompAsyncExecutePromiseGeneratorFunctionCall(Scope scope, Node generatorFunction) {
        String function = "asyncExecutePromiseGeneratorFunction";
        Node jscompDotAsyncExecutePromiseGeneratorFunction = createQName(scope, "$jscomp." + function);
        // TODO(bradfordcsmith): Maybe update the type to be more specific
        // Currently this method expects `function(): !Generator<?>` and returns `Promise<?>`.
        // Since we propagate type information only if type checking has already run,
        // these unknowns probably don't matter, but we should be able to be more specific with the
        // return type at least.
        return createCall(jscompDotAsyncExecutePromiseGeneratorFunction, generatorFunction);
    }

    private JSType getNativeType(JSTypeNative nativeType) {
        checkNotNull(registry, "registry is null");
        return checkNotNull(registry.getNativeType(nativeType), "native type not found: %s", nativeType);
    }

    /**
     * Look up the correct type for the given name in the given scope.
     *
     * <p>Returns the unknown type if no type can be found
     */
    private JSType getVarNameType(Scope scope, String name) {
        Var var = scope.getVar(name);
        JSType type = null;
        if (var != null) {
            Node nameDefinitionNode = var.getNode();
            if (nameDefinitionNode != null) {
                type = nameDefinitionNode.getJSType();
            }
        }
        if (type == null) {
            // TODO(bradfordcsmith): Consider throwing an error if the type cannot be found.
            type = unknownType;
        }
        return type;
    }

    private JSType getJsTypeForProperty(Node receiver, String propertyName) {
        // NOTE: we use both findPropertyType and getPropertyType because they are subtly
        // different: findPropertyType works on JSType, autoboxing scalars and joining unions,
        // but it returns null if the type is not found and does not handle dynamic types of
        // Function.prototype.call and .apply; whereas getPropertyType does not autobox nor
        // iterate over unions, but it does synthesize the function properties correctly, and
        // it returns unknown instead of null if the property is missing.
        JSType getpropType = null;
        JSType receiverJSType = receiver.getJSType();
        if (receiverJSType != null) {
            getpropType = receiverJSType.findPropertyType(propertyName);
            if (getpropType == null) {
                ObjectType receiverObjectType = ObjectType.cast(receiverJSType.autobox());
                getpropType = receiverObjectType == null ? unknownType
                        : receiverObjectType.getPropertyType(propertyName);
            }
        }
        if (getpropType == null) {
            getpropType = unknownType;
        }
        // TODO(bradfordcsmith): Special case $jscomp.global until we annotate its type correctly.
        if (getpropType.isUnknownType() && propertyName.equals("global")
                && receiver.matchesQualifiedName("$jscomp")) {
            getpropType = getNativeType(JSTypeNative.GLOBAL_THIS);
        }
        return getpropType;
    }
}