All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.rhino.jstype.JSTypeRegistry Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs. This binary checks for style issues such as incorrect or missing JSDoc usage, and missing goog.require() statements. It does not do more advanced checks such as typechecking.

There is a newer version: v20200830
Show newest version
/*
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Rhino code, released
 * May 6, 1999.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1997-1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Bob Jervis
 *   Google Inc.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License Version 2 or later (the "GPL"), in which
 * case the provisions of the GPL are applicable instead of those above. If
 * you wish to allow use of your version of this file only under the terms of
 * the GPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replacing
 * them with the notice and other provisions required by the GPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the GPL.
 *
 * ***** END LICENSE BLOCK ***** */

package com.google.javascript.rhino.jstype;

import static com.google.javascript.rhino.jstype.JSTypeNative.ALL_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.NO_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.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSTypeExpression;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.ObjectTypeI;
import com.google.javascript.rhino.SimpleErrorReporter;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TypeI;
import com.google.javascript.rhino.TypeIEnv;
import com.google.javascript.rhino.TypeIRegistry;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * The type registry is used to resolve named types.
 *
 * 

This class is not thread-safe. * */ public class JSTypeRegistry implements TypeIRegistry, Serializable { private static final long serialVersionUID = 1L; /** * The template variable corresponding to the KEY type in {@code IObject} * (plus the builtin Javascript Object). */ private TemplateType iObjectIndexTemplateKey; /** * The template variable corresponding to the VALUE type in {@code IObject} * (plus the builtin Javascript Object). */ private TemplateType iObjectElementTemplateKey; private static final String I_OBJECT_ELEMENT_TEMPLATE = "IObject#VALUE"; /** * The template variable in {@code Array} */ private TemplateType arrayElementTemplateKey; @Deprecated public static final String OBJECT_ELEMENT_TEMPLATE = I_OBJECT_ELEMENT_TEMPLATE; /** * The UnionTypeBuilder caps the maximum number of alternate types it * remembers and then defaults to "?" (unknown type). By default this max * is 20, but it's very easy for the same property to appear on more than 20 * types. Use larger unions for property checking. 3000 was picked * semi-randomly for use by the Google+ FE project. */ private static final int PROPERTY_CHECKING_UNION_SIZE = 3000; // TODO(user): An instance of this class should be used during // compilation. We also want to make all types' constructors package private // and force usage of this registry instead. This will allow us to evolve the // types without being tied by an open API. private final transient ErrorReporter reporter; // We use an Array instead of an immutable list because this lookup needs // to be very fast. When it was an immutable list, we were spending 5% of // CPU time on bounds checking inside get(). private final JSType[] nativeTypes; private final Map namesToTypes; // NOTE(nicksantos): This is a terrible terrible hack. When type expressions // are evaluated, we need to be able to decide whether that type name // resolves to a nullable type or a non-nullable type. Object types are // nullable, but enum types are not. // // Notice that it's not good enough to just declare enum types sooner. // For example, if we have // /** @enum {MyObject} */ var MyEnum = ...; // we won't be to declare "MyEnum" without evaluating the expression // {MyObject}, and following those dependencies starts to lead us into // undecidable territory. Instead, we "pre-declare" enum types and typedefs, // so that the expression resolver can decide whether a given name is // nullable or not. private final Set nonNullableTypeNames = new LinkedHashSet<>(); // Types that have been "forward-declared." // If these types are not declared anywhere in the binary, we shouldn't // try to type-check them at all. private final Set forwardDeclaredTypes; // A map of properties to the types on which those properties have been // declared. private final Map typesIndexedByProperty = new HashMap<>(); private JSType sentinelObjectLiteral; private boolean optimizePropertyIndex = false; // To avoid blowing up the size of typesIndexedByProperty, we use the sentinel object // literal instead of registering arbitrarily many types. // But because of the way unions are constructed, some properties of record types in unions // are getting dropped and cause spurious "non-existent property" warnings. // The next two fields avoid the warnings. The first field contains property names of records // that participate in unions, and have caused properties to be dropped. // The second field contains the names of the dropped properties. When checking // canPropertyBeDefined, if the type has a property in propertiesOfSupertypesInUnions, we // consider it to possibly have any property in droppedPropertiesOfUnions. This is a loose // check, but we restrict it to records that may be present in unions, and it allows us to // keep typesIndexedByProperty small. private final Set propertiesOfSupertypesInUnions = new HashSet<>(); private final Set droppedPropertiesOfUnions = new HashSet<>(); // A map of properties to each reference type on which those // properties have been declared. Each type has a unique name used // for de-duping. private final Map> eachRefTypeIndexedByProperty = new LinkedHashMap<>(); // A map of properties to the greatest subtype on which those properties have // been declared. This is filled lazily from the types declared in // typesIndexedByProperty. private final Map greatestSubtypeByProperty = new HashMap<>(); // A map from interface name to types that implement it. private final Multimap interfaceToImplementors = LinkedHashMultimap.create(); // All the unresolved named types. private final Multimap, NamedType> unresolvedNamedTypes = ArrayListMultimap.create(); // All the resolved named types. private final Multimap, NamedType> resolvedNamedTypes = ArrayListMultimap.create(); // The template type name. private final Map templateTypes = new HashMap<>(); // A single empty TemplateTypeMap, which can be safely reused in cases where // there are no template types. private final TemplateTypeMap emptyTemplateTypeMap; public JSTypeRegistry(ErrorReporter reporter) { this(reporter, ImmutableSet.of()); } /** Constructs a new type registry populated with the built-in types. */ public JSTypeRegistry(ErrorReporter reporter, Set forwardDeclaredTypes) { this.reporter = reporter; this.forwardDeclaredTypes = forwardDeclaredTypes; this.emptyTemplateTypeMap = new TemplateTypeMap( this, ImmutableList.of(), ImmutableList.of()); nativeTypes = new JSType[JSTypeNative.values().length]; namesToTypes = new HashMap<>(); resetForTypeCheck(); } private JSType getSentinelObjectLiteral() { if (this.sentinelObjectLiteral == null) { this.sentinelObjectLiteral = createAnonymousObjectType(null); } return this.sentinelObjectLiteral; } public void setOptimizePropertyIndex_TRANSITIONAL_METHOD(boolean optimizePropIndex) { this.optimizePropertyIndex = optimizePropIndex; } /** * @return The template variable corresponding to the property value type for * Javascript Objects and Arrays. */ public TemplateType getObjectElementKey() { return this.iObjectElementTemplateKey; } /** * @return The template variable corresponding to the * property key type of the built-in Javascript object. */ public TemplateType getObjectIndexKey() { Preconditions.checkNotNull(iObjectIndexTemplateKey); return this.iObjectIndexTemplateKey; } /** * Check if a function declaration is one of the templated builitin contructor/interfaces, * namely one of IObject, IArrayLike, or Array * @param fnName the function's name * @param info the JSDoc from the function declaration */ public boolean isTemplatedBuiltin(String fnName, JSDocInfo info) { ImmutableList requiredTemplateTypes = getTemplateTypesOfBuiltin(fnName); ImmutableList infoTemplateTypeNames = info.getTemplateTypeNames(); if (requiredTemplateTypes == null || infoTemplateTypeNames.size() != requiredTemplateTypes.size()) { return false; } return true; } /** * @return return an immutable list of template types of the given builtin. */ public ImmutableList getTemplateTypesOfBuiltin(String fnName) { switch (fnName) { case "IObject": return ImmutableList.of(iObjectIndexTemplateKey, iObjectElementTemplateKey); case "Array": return ImmutableList.of(arrayElementTemplateKey); default: return null; } } public ErrorReporter getErrorReporter() { return reporter; } /** * Reset to run the TypeCheck pass. */ public void resetForTypeCheck() { typesIndexedByProperty.clear(); eachRefTypeIndexedByProperty.clear(); initializeBuiltInTypes(); namesToTypes.clear(); initializeRegistry(); } private void initializeBuiltInTypes() { // These locals shouldn't be all caps. BooleanType BOOLEAN_TYPE = new BooleanType(this); registerNativeType(JSTypeNative.BOOLEAN_TYPE, BOOLEAN_TYPE); NullType NULL_TYPE = new NullType(this); registerNativeType(JSTypeNative.NULL_TYPE, NULL_TYPE); NumberType NUMBER_TYPE = new NumberType(this); registerNativeType(JSTypeNative.NUMBER_TYPE, NUMBER_TYPE); StringType STRING_TYPE = new StringType(this); registerNativeType(JSTypeNative.STRING_TYPE, STRING_TYPE); UnknownType UNKNOWN_TYPE = new UnknownType(this, false); registerNativeType(JSTypeNative.UNKNOWN_TYPE, UNKNOWN_TYPE); UnknownType checkedUnknownType = new UnknownType(this, true); registerNativeType( JSTypeNative.CHECKED_UNKNOWN_TYPE, checkedUnknownType); VoidType VOID_TYPE = new VoidType(this); registerNativeType(JSTypeNative.VOID_TYPE, VOID_TYPE); AllType ALL_TYPE = new AllType(this); registerNativeType(JSTypeNative.ALL_TYPE, ALL_TYPE); // Template Types iObjectIndexTemplateKey = new TemplateType(this, "IObject#KEY1"); iObjectElementTemplateKey = new TemplateType(this, I_OBJECT_ELEMENT_TEMPLATE); arrayElementTemplateKey = new TemplateType(this, "T"); // Top Level Prototype (the One) // The initializations of TOP_LEVEL_PROTOTYPE and OBJECT_FUNCTION_TYPE // use each other's results, so at least one of them will get null // instead of an actual type; however, this seems to be benign. PrototypeObjectType TOP_LEVEL_PROTOTYPE = new PrototypeObjectType(this, null, null, true, null); registerNativeType(JSTypeNative.TOP_LEVEL_PROTOTYPE, TOP_LEVEL_PROTOTYPE); // Object FunctionType OBJECT_FUNCTION_TYPE = new FunctionType( this, "Object", null, createArrowType(createOptionalParameters(ALL_TYPE), null), null, createTemplateTypeMap( ImmutableList.of(iObjectIndexTemplateKey, iObjectElementTemplateKey), null), true, true, false); OBJECT_FUNCTION_TYPE.getInternalArrowType().returnType = OBJECT_FUNCTION_TYPE.getInstanceType(); OBJECT_FUNCTION_TYPE.setPrototype(TOP_LEVEL_PROTOTYPE, null); registerNativeType(JSTypeNative.OBJECT_FUNCTION_TYPE, OBJECT_FUNCTION_TYPE); ObjectType OBJECT_TYPE = OBJECT_FUNCTION_TYPE.getInstanceType(); registerNativeType(JSTypeNative.OBJECT_TYPE, OBJECT_TYPE); ObjectType OBJECT_PROTOTYPE = OBJECT_FUNCTION_TYPE.getPrototype(); registerNativeType(JSTypeNative.OBJECT_PROTOTYPE, OBJECT_PROTOTYPE); // Function FunctionType FUNCTION_FUNCTION_TYPE = new FunctionType( this, "Function", null, createArrowType(createParametersWithVarArgs(ALL_TYPE), UNKNOWN_TYPE), null, null, true, true, false); FUNCTION_FUNCTION_TYPE.setPrototypeBasedOn(OBJECT_TYPE); registerNativeType( JSTypeNative.FUNCTION_FUNCTION_TYPE, FUNCTION_FUNCTION_TYPE); ObjectType FUNCTION_PROTOTYPE = FUNCTION_FUNCTION_TYPE.getPrototype(); registerNativeType(JSTypeNative.FUNCTION_PROTOTYPE, FUNCTION_PROTOTYPE); NoType NO_TYPE = new NoType(this); registerNativeType(JSTypeNative.NO_TYPE, NO_TYPE); NoObjectType NO_OBJECT_TYPE = new NoObjectType(this); registerNativeType(JSTypeNative.NO_OBJECT_TYPE, NO_OBJECT_TYPE); NoObjectType NO_RESOLVED_TYPE = new NoResolvedType(this); registerNativeType(JSTypeNative.NO_RESOLVED_TYPE, NO_RESOLVED_TYPE); // Array FunctionType ARRAY_FUNCTION_TYPE = new FunctionType( this, "Array", null, createArrowType(createParametersWithVarArgs(ALL_TYPE), null), null, createTemplateTypeMap(ImmutableList.of(arrayElementTemplateKey), null) .extend( createTemplateTypeMap( ImmutableList.of(iObjectElementTemplateKey), ImmutableList.of(arrayElementTemplateKey))), true, true, false); ARRAY_FUNCTION_TYPE.getInternalArrowType().returnType = ARRAY_FUNCTION_TYPE.getInstanceType(); ARRAY_FUNCTION_TYPE.getPrototype(); // Force initialization registerNativeType(JSTypeNative.ARRAY_FUNCTION_TYPE, ARRAY_FUNCTION_TYPE); ObjectType ARRAY_TYPE = ARRAY_FUNCTION_TYPE.getInstanceType(); registerNativeType(JSTypeNative.ARRAY_TYPE, ARRAY_TYPE); // Boolean FunctionType BOOLEAN_OBJECT_FUNCTION_TYPE = new FunctionType( this, "Boolean", null, createArrowType(createOptionalParameters(ALL_TYPE), BOOLEAN_TYPE), null, null, true, true, false); BOOLEAN_OBJECT_FUNCTION_TYPE.getPrototype(); // Force initialization registerNativeType( JSTypeNative.BOOLEAN_OBJECT_FUNCTION_TYPE, BOOLEAN_OBJECT_FUNCTION_TYPE); ObjectType BOOLEAN_OBJECT_TYPE = BOOLEAN_OBJECT_FUNCTION_TYPE.getInstanceType(); registerNativeType(JSTypeNative.BOOLEAN_OBJECT_TYPE, BOOLEAN_OBJECT_TYPE); // Date FunctionType DATE_FUNCTION_TYPE = new FunctionType( this, "Date", null, createArrowType( createOptionalParameters( UNKNOWN_TYPE, UNKNOWN_TYPE, UNKNOWN_TYPE, UNKNOWN_TYPE, UNKNOWN_TYPE, UNKNOWN_TYPE, UNKNOWN_TYPE), STRING_TYPE), null, null, true, true, false); DATE_FUNCTION_TYPE.getPrototype(); // Force initialization registerNativeType(JSTypeNative.DATE_FUNCTION_TYPE, DATE_FUNCTION_TYPE); ObjectType DATE_TYPE = DATE_FUNCTION_TYPE.getInstanceType(); registerNativeType(JSTypeNative.DATE_TYPE, DATE_TYPE); // Error FunctionType ERROR_FUNCTION_TYPE = new ErrorFunctionType(this, "Error"); registerNativeType(JSTypeNative.ERROR_FUNCTION_TYPE, ERROR_FUNCTION_TYPE); ObjectType ERROR_TYPE = ERROR_FUNCTION_TYPE.getInstanceType(); registerNativeType(JSTypeNative.ERROR_TYPE, ERROR_TYPE); // EvalError FunctionType EVAL_ERROR_FUNCTION_TYPE = new ErrorFunctionType(this, "EvalError"); EVAL_ERROR_FUNCTION_TYPE.setPrototypeBasedOn(ERROR_TYPE); registerNativeType( JSTypeNative.EVAL_ERROR_FUNCTION_TYPE, EVAL_ERROR_FUNCTION_TYPE); ObjectType EVAL_ERROR_TYPE = EVAL_ERROR_FUNCTION_TYPE.getInstanceType(); registerNativeType(JSTypeNative.EVAL_ERROR_TYPE, EVAL_ERROR_TYPE); // RangeError FunctionType RANGE_ERROR_FUNCTION_TYPE = new ErrorFunctionType(this, "RangeError"); RANGE_ERROR_FUNCTION_TYPE.setPrototypeBasedOn(ERROR_TYPE); registerNativeType( JSTypeNative.RANGE_ERROR_FUNCTION_TYPE, RANGE_ERROR_FUNCTION_TYPE); ObjectType RANGE_ERROR_TYPE = RANGE_ERROR_FUNCTION_TYPE.getInstanceType(); registerNativeType(JSTypeNative.RANGE_ERROR_TYPE, RANGE_ERROR_TYPE); // ReferenceError FunctionType REFERENCE_ERROR_FUNCTION_TYPE = new ErrorFunctionType(this, "ReferenceError"); REFERENCE_ERROR_FUNCTION_TYPE.setPrototypeBasedOn(ERROR_TYPE); registerNativeType( JSTypeNative.REFERENCE_ERROR_FUNCTION_TYPE, REFERENCE_ERROR_FUNCTION_TYPE); ObjectType REFERENCE_ERROR_TYPE = REFERENCE_ERROR_FUNCTION_TYPE.getInstanceType(); registerNativeType(JSTypeNative.REFERENCE_ERROR_TYPE, REFERENCE_ERROR_TYPE); // SyntaxError FunctionType SYNTAX_ERROR_FUNCTION_TYPE = new ErrorFunctionType(this, "SyntaxError"); SYNTAX_ERROR_FUNCTION_TYPE.setPrototypeBasedOn(ERROR_TYPE); registerNativeType( JSTypeNative.SYNTAX_ERROR_FUNCTION_TYPE, SYNTAX_ERROR_FUNCTION_TYPE); ObjectType SYNTAX_ERROR_TYPE = SYNTAX_ERROR_FUNCTION_TYPE.getInstanceType(); registerNativeType(JSTypeNative.SYNTAX_ERROR_TYPE, SYNTAX_ERROR_TYPE); // TypeError FunctionType TYPE_ERROR_FUNCTION_TYPE = new ErrorFunctionType(this, "TypeError"); TYPE_ERROR_FUNCTION_TYPE.setPrototypeBasedOn(ERROR_TYPE); registerNativeType( JSTypeNative.TYPE_ERROR_FUNCTION_TYPE, TYPE_ERROR_FUNCTION_TYPE); ObjectType TYPE_ERROR_TYPE = TYPE_ERROR_FUNCTION_TYPE.getInstanceType(); registerNativeType(JSTypeNative.TYPE_ERROR_TYPE, TYPE_ERROR_TYPE); // URIError FunctionType URI_ERROR_FUNCTION_TYPE = new ErrorFunctionType(this, "URIError"); URI_ERROR_FUNCTION_TYPE.setPrototypeBasedOn(ERROR_TYPE); registerNativeType( JSTypeNative.URI_ERROR_FUNCTION_TYPE, URI_ERROR_FUNCTION_TYPE); ObjectType URI_ERROR_TYPE = URI_ERROR_FUNCTION_TYPE.getInstanceType(); registerNativeType(JSTypeNative.URI_ERROR_TYPE, URI_ERROR_TYPE); // Number FunctionType NUMBER_OBJECT_FUNCTION_TYPE = new FunctionType( this, "Number", null, createArrowType(createOptionalParameters(ALL_TYPE), NUMBER_TYPE), null, null, true, true, false); NUMBER_OBJECT_FUNCTION_TYPE.getPrototype(); // Force initialization registerNativeType( JSTypeNative.NUMBER_OBJECT_FUNCTION_TYPE, NUMBER_OBJECT_FUNCTION_TYPE); ObjectType NUMBER_OBJECT_TYPE = NUMBER_OBJECT_FUNCTION_TYPE.getInstanceType(); registerNativeType(JSTypeNative.NUMBER_OBJECT_TYPE, NUMBER_OBJECT_TYPE); // RegExp FunctionType REGEXP_FUNCTION_TYPE = new FunctionType( this, "RegExp", null, createArrowType(createOptionalParameters(ALL_TYPE, ALL_TYPE)), null, null, true, true, false); REGEXP_FUNCTION_TYPE.getInternalArrowType().returnType = REGEXP_FUNCTION_TYPE.getInstanceType(); REGEXP_FUNCTION_TYPE.getPrototype(); // Force initialization registerNativeType(JSTypeNative.REGEXP_FUNCTION_TYPE, REGEXP_FUNCTION_TYPE); ObjectType REGEXP_TYPE = REGEXP_FUNCTION_TYPE.getInstanceType(); registerNativeType(JSTypeNative.REGEXP_TYPE, REGEXP_TYPE); // String FunctionType STRING_OBJECT_FUNCTION_TYPE = new FunctionType( this, "String", null, createArrowType(createOptionalParameters(ALL_TYPE), STRING_TYPE), null, null, true, true, false); STRING_OBJECT_FUNCTION_TYPE.getPrototype(); // Force initialization registerNativeType( JSTypeNative.STRING_OBJECT_FUNCTION_TYPE, STRING_OBJECT_FUNCTION_TYPE); ObjectType STRING_OBJECT_TYPE = STRING_OBJECT_FUNCTION_TYPE.getInstanceType(); registerNativeType( JSTypeNative.STRING_OBJECT_TYPE, STRING_OBJECT_TYPE); // (null,void) JSType NULL_VOID = createUnionType(NULL_TYPE, VOID_TYPE); registerNativeType(JSTypeNative.NULL_VOID, NULL_VOID); // (Object,string,number) JSType OBJECT_NUMBER_STRING = createUnionType(OBJECT_TYPE, NUMBER_TYPE, STRING_TYPE); registerNativeType(JSTypeNative.OBJECT_NUMBER_STRING, OBJECT_NUMBER_STRING); // (Object,string,number,boolean) JSType OBJECT_NUMBER_STRING_BOOLEAN = createUnionType(OBJECT_TYPE, NUMBER_TYPE, STRING_TYPE, BOOLEAN_TYPE); registerNativeType(JSTypeNative.OBJECT_NUMBER_STRING_BOOLEAN, OBJECT_NUMBER_STRING_BOOLEAN); // (string,number,boolean) JSType NUMBER_STRING_BOOLEAN = createUnionType(NUMBER_TYPE, STRING_TYPE, BOOLEAN_TYPE); registerNativeType(JSTypeNative.NUMBER_STRING_BOOLEAN, NUMBER_STRING_BOOLEAN); // (string,number) JSType NUMBER_STRING = createUnionType(NUMBER_TYPE, STRING_TYPE); registerNativeType(JSTypeNative.NUMBER_STRING, NUMBER_STRING); // Native object properties are filled in by externs... // (String, string) JSType STRING_VALUE_OR_OBJECT_TYPE = createUnionType(STRING_OBJECT_TYPE, STRING_TYPE); registerNativeType( JSTypeNative.STRING_VALUE_OR_OBJECT_TYPE, STRING_VALUE_OR_OBJECT_TYPE); // (Number, number) JSType NUMBER_VALUE_OR_OBJECT_TYPE = createUnionType(NUMBER_OBJECT_TYPE, NUMBER_TYPE); registerNativeType( JSTypeNative.NUMBER_VALUE_OR_OBJECT_TYPE, NUMBER_VALUE_OR_OBJECT_TYPE); // unknown function type, i.e. (?...) -> ? FunctionType U2U_FUNCTION_TYPE = createFunctionTypeWithVarArgs(UNKNOWN_TYPE, UNKNOWN_TYPE); registerNativeType(JSTypeNative.U2U_FUNCTION_TYPE, U2U_FUNCTION_TYPE); // unknown constructor type, i.e. (?...) -> ? with the Unknown type // as instance type FunctionType U2U_CONSTRUCTOR_TYPE = // This is equivalent to // createConstructorType(UNKNOWN_TYPE, true, UNKNOWN_TYPE), but, // in addition, overrides getInstanceType() to return the NoObject type // instead of a new anonymous object. new FunctionType( this, "Function", null, createArrowType(createParametersWithVarArgs(UNKNOWN_TYPE), UNKNOWN_TYPE), UNKNOWN_TYPE, null, true, true, false) { private static final long serialVersionUID = 1L; @Override public FunctionType getConstructor() { return registry.getNativeFunctionType(JSTypeNative.FUNCTION_FUNCTION_TYPE); } }; // The U2U_CONSTRUCTOR is weird, because it's the supertype of its // own constructor. registerNativeType(JSTypeNative.U2U_CONSTRUCTOR_TYPE, U2U_CONSTRUCTOR_TYPE); registerNativeType( JSTypeNative.FUNCTION_INSTANCE_TYPE, U2U_CONSTRUCTOR_TYPE); FUNCTION_FUNCTION_TYPE.setInstanceType(U2U_CONSTRUCTOR_TYPE); U2U_CONSTRUCTOR_TYPE.setImplicitPrototype(FUNCTION_PROTOTYPE); // least function type, i.e. (All...) -> NoType FunctionType LEAST_FUNCTION_TYPE = createNativeFunctionTypeWithVarArgs(NO_TYPE, ALL_TYPE); registerNativeType(JSTypeNative.LEAST_FUNCTION_TYPE, LEAST_FUNCTION_TYPE); // the 'this' object in the global scope FunctionType GLOBAL_THIS_CTOR = new FunctionType( this, "global this", null, createArrowType(createParameters(false, ALL_TYPE), NUMBER_TYPE), null, null, true, true, false); ObjectType GLOBAL_THIS = GLOBAL_THIS_CTOR.getInstanceType(); registerNativeType(JSTypeNative.GLOBAL_THIS, GLOBAL_THIS); // greatest function type, i.e. (NoType...) -> All FunctionType GREATEST_FUNCTION_TYPE = createNativeFunctionTypeWithVarArgs(ALL_TYPE, NO_TYPE); registerNativeType(JSTypeNative.GREATEST_FUNCTION_TYPE, GREATEST_FUNCTION_TYPE); // Register the prototype property. See the comments below in // registerPropertyOnType about the bootstrapping process. registerPropertyOnType("prototype", OBJECT_FUNCTION_TYPE); } private void initializeRegistry() { register(getNativeType(JSTypeNative.ARRAY_TYPE)); register(getNativeType(JSTypeNative.BOOLEAN_OBJECT_TYPE)); register(getNativeType(JSTypeNative.BOOLEAN_TYPE)); register(getNativeType(JSTypeNative.DATE_TYPE)); register(getNativeType(JSTypeNative.NULL_TYPE)); register(getNativeType(JSTypeNative.NULL_TYPE), "Null"); register(getNativeType(JSTypeNative.NUMBER_OBJECT_TYPE)); register(getNativeType(JSTypeNative.NUMBER_TYPE)); register(getNativeType(JSTypeNative.OBJECT_TYPE)); register(getNativeType(JSTypeNative.ERROR_TYPE)); register(getNativeType(JSTypeNative.URI_ERROR_TYPE)); register(getNativeType(JSTypeNative.EVAL_ERROR_TYPE)); register(getNativeType(JSTypeNative.TYPE_ERROR_TYPE)); register(getNativeType(JSTypeNative.RANGE_ERROR_TYPE)); register(getNativeType(JSTypeNative.REFERENCE_ERROR_TYPE)); register(getNativeType(JSTypeNative.SYNTAX_ERROR_TYPE)); register(getNativeType(JSTypeNative.REGEXP_TYPE)); register(getNativeType(JSTypeNative.STRING_OBJECT_TYPE)); register(getNativeType(JSTypeNative.STRING_TYPE)); register(getNativeType(JSTypeNative.VOID_TYPE)); register(getNativeType(JSTypeNative.VOID_TYPE), "Undefined"); register(getNativeType(JSTypeNative.VOID_TYPE), "void"); register(getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE), "Function"); } private void register(JSType type) { register(type, type.toString()); } private void register(JSType type, String name) { Preconditions.checkArgument( !name.contains("<"), "Type names cannot contain template annotations."); namesToTypes.put(name, type); } private void registerNativeType(JSTypeNative typeId, JSType type) { nativeTypes[typeId.ordinal()] = type; } // When t is an object that is not the prototype of some class, // and its nominal type is Object, and it has some properties, // we don't need to store these properties in the propertyIndex separately. private static boolean isObjectLiteralThatCanBeSkipped(JSType t) { t = t.restrictByNotNullOrUndefined(); return t.isRecordType() || t.isLiteralObject(); } void registerDroppedPropertiesInUnion(RecordType subtype, RecordType supertype) { boolean foundDroppedProperty = false; for (String pname : subtype.getPropertyMap().getOwnPropertyNames()) { if (!supertype.hasProperty(pname)) { foundDroppedProperty = true; this.droppedPropertiesOfUnions.add(pname); } } if (foundDroppedProperty) { this.propertiesOfSupertypesInUnions.addAll(supertype.getPropertyMap().getOwnPropertyNames()); } } /** * Tells the type system that {@code owner} may have a property named * {@code propertyName}. This allows the registry to keep track of what * types a property is defined upon. * * This is NOT the same as saying that {@code owner} must have a property * named type. ObjectType#hasProperty attempts to minimize false positives * ("if we're not sure, then don't type check this property"). The type * registry, on the other hand, should attempt to minimize false negatives * ("if this property is assigned anywhere in the program, it must * show up in the type registry"). */ public void registerPropertyOnType(String propertyName, JSType type) { UnionTypeBuilder typeSet = typesIndexedByProperty.get(propertyName); if (typeSet == null) { typeSet = new UnionTypeBuilder(this, PROPERTY_CHECKING_UNION_SIZE); typesIndexedByProperty.put(propertyName, typeSet); } if (this.optimizePropertyIndex && isObjectLiteralThatCanBeSkipped(type)) { type = getSentinelObjectLiteral(); } typeSet.addAlternate(type); addReferenceTypeIndexedByProperty(propertyName, type); // Clear cached values that depend on typesIndexedByProperty. greatestSubtypeByProperty.remove(propertyName); } private void addReferenceTypeIndexedByProperty( String propertyName, JSType type) { if (type instanceof ObjectType && ((ObjectType) type).hasReferenceName()) { Map typeSet = eachRefTypeIndexedByProperty.get(propertyName); if (typeSet == null) { typeSet = new LinkedHashMap<>(); eachRefTypeIndexedByProperty.put(propertyName, typeSet); } ObjectType objType = (ObjectType) type; typeSet.put(objType.getReferenceName(), objType); } else if (type instanceof NamedType) { addReferenceTypeIndexedByProperty( propertyName, ((NamedType) type).getReferencedType()); } else if (type.isUnionType()) { for (JSType alternate : type.toMaybeUnionType().getAlternates()) { addReferenceTypeIndexedByProperty(propertyName, alternate); } } } /** * Removes the index's reference to a property on the given type (if it is * currently registered). If the property is not registered on the type yet, * this method will not change internal state. * * @param propertyName the name of the property to unregister * @param type the type to unregister the property on. */ public void unregisterPropertyOnType(String propertyName, JSType type) { // TODO(bashir): typesIndexedByProperty should also be updated! Map typeSet = eachRefTypeIndexedByProperty.get(propertyName); if (typeSet != null) { typeSet.remove(type.toObjectType().getReferenceName()); } } /** * Gets the greatest subtype of the {@code type} that has a property * {@code propertyName} defined on it. */ public JSType getGreatestSubtypeWithProperty( JSType type, String propertyName) { JSType withProperty = greatestSubtypeByProperty.get(propertyName); if (withProperty != null) { return withProperty.getGreatestSubtype(type); } UnionTypeBuilder typesWithProp = typesIndexedByProperty.get(propertyName); if (typesWithProp != null) { JSType built = typesWithProp.build(); greatestSubtypeByProperty.put(propertyName, built); return built.getGreatestSubtype(type); } return getNativeType(NO_TYPE); } /** * Returns whether the given property can possibly be set on the given type. */ public boolean canPropertyBeDefined(JSType type, String propertyName) { if (type.isStruct()) { // We are stricter about "struct" types and only allow access to // properties that to the best of our knowledge are available at creation // time and specifically not properties only defined on subtypes. return type.hasProperty(propertyName); } else { if (!type.isEmptyType() && !type.isUnknownType() && type.hasProperty(propertyName)) { return true; } if (typesIndexedByProperty.containsKey(propertyName)) { for (JSType alt : typesIndexedByProperty.get(propertyName).getAlternates()) { JSType greatestSubtype = alt.getGreatestSubtype(type); if (!greatestSubtype.isEmptyType()) { // We've found a type with this property. Now we just have to make // sure it's not a type used for internal bookkeeping. RecordType maybeRecordType = greatestSubtype.toMaybeRecordType(); if (maybeRecordType != null && maybeRecordType.isSynthetic()) { continue; } return true; } } } if (type.toMaybeRecordType() != null) { RecordType rec = type.toMaybeRecordType(); boolean mayBeInUnion = false; for (String pname : rec.getPropertyMap().getOwnPropertyNames()) { if (this.propertiesOfSupertypesInUnions.contains(pname)) { mayBeInUnion = true; break; } } return mayBeInUnion && this.droppedPropertiesOfUnions.contains(propertyName); } } return false; } /** * Returns each reference type that has a property {@code propertyName} * defined on it. * * Unlike most types in our type system, the collection of types returned * will not be collapsed. This means that if a type is defined on * {@code Object} and on {@code Array}, this method must return * {@code [Object, Array]}. It would not be correct to collapse them to * {@code [Object]}. */ public Iterable getEachReferenceTypeWithProperty( String propertyName) { if (eachRefTypeIndexedByProperty.containsKey(propertyName)) { return eachRefTypeIndexedByProperty.get(propertyName).values(); } else { return ImmutableList.of(); } } /** * Finds the common supertype of the two given object types. */ ObjectType findCommonSuperObject(ObjectType a, ObjectType b) { List stackA = getSuperStack(a); List stackB = getSuperStack(b); ObjectType result = getNativeObjectType(JSTypeNative.OBJECT_TYPE); while (!stackA.isEmpty() && !stackB.isEmpty()) { ObjectType currentA = stackA.remove(stackA.size() - 1); ObjectType currentB = stackB.remove(stackB.size() - 1); if (currentA.isEquivalentTo(currentB)) { result = currentA; } else { return result; } } return result; } private static List getSuperStack(ObjectType a) { List stack = new ArrayList<>(5); for (ObjectType current = a; current != null; current = current.getImplicitPrototype()) { stack.add(current); } return stack; } /** * Tells the type system that {@code type} implements interface {@code * interfaceInstance}. * {@code inter} must be an ObjectType for the instance of the interface as it * could be a named type and not yet have the constructor. */ void registerTypeImplementingInterface( FunctionType type, ObjectType interfaceInstance) { interfaceToImplementors.put(interfaceInstance.getReferenceName(), type); } /** * Returns a collection of types that directly implement {@code * interfaceInstance}. Subtypes of implementing types are not guaranteed to * be returned. {@code interfaceInstance} must be an ObjectType for the * instance of the interface. */ public Collection getDirectImplementors(ObjectType interfaceInstance) { return interfaceToImplementors.get(interfaceInstance.getReferenceName()); } /** * Records declared global type names. This makes resolution faster * and more robust in the common case. * * @param name The name of the type to be recorded. * @param t The actual type being associated with the name. * @return True if this name is not already defined, false otherwise. */ public boolean declareType(String name, JSType t) { if (namesToTypes.containsKey(name)) { return false; } register(t, name); return true; } /** * Overrides a declared global type name. Throws an exception if this * type name hasn't been declared yet. */ public void overwriteDeclaredType(String name, JSType t) { Preconditions.checkState(namesToTypes.containsKey(name)); register(t, name); } /** * Whether this is a forward-declared type name. */ public boolean isForwardDeclaredType(String name) { return forwardDeclaredTypes.contains(name); } /** * The nice API for this method is a single argument; dereference is a detail. In the old type * checker, most calls to getReadableJSTypeName are with true (do dereferencing). * When we implement this method in the new type checker, we won't do dereferencing, but that's * fine because we are stricter about null/undefined checking. * (So, null and undefined wouldn't be in the type in the first place.) */ @Override public String getReadableTypeName(Node n) { return getReadableJSTypeName(n, true); } public String getReadableTypeNameNoDeref(Node n) { return getReadableJSTypeName(n, false); } /** * Given a node, get a human-readable name for the type of that node so * that will be easy for the programmer to find the original declaration. * * For example, if SubFoo's property "bar" might have the human-readable * name "Foo.prototype.bar". * * @param n The node. * @param dereference If true, the type of the node will be dereferenced * to an Object type, if possible. */ private String getReadableJSTypeName(Node n, boolean dereference) { JSType type = getJSTypeOrUnknown(n); if (dereference) { ObjectType dereferenced = type.dereference(); if (dereferenced != null) { type = dereferenced; } } // The best type name is the actual type name. if (type.isFunctionPrototypeType()) { return type.toString(); } if (type.toObjectType() != null && type.toObjectType().getConstructor() != null) { Node source = type.toObjectType().getConstructor().getSource(); if (source == null) { return type.toString(); } Preconditions.checkState(source.isFunction(), source); String readable = source.getFirstChild().getOriginalName(); if (readable == null) { return type.toString(); } return readable; } // If we're analyzing a GETPROP, the property may be inherited by the // prototype chain. So climb the prototype chain and find out where // the property was originally defined. if (n.isGetProp()) { ObjectType objectType = getJSTypeOrUnknown(n.getFirstChild()).dereference(); if (objectType != null) { String propName = n.getLastChild().getString(); if (objectType.getConstructor() != null && objectType.getConstructor().isInterface()) { objectType = objectType.getTopDefiningInterface(propName); } else { // classes while (objectType != null && !objectType.hasOwnProperty(propName)) { objectType = objectType.getImplicitPrototype(); } } // Don't show complex function names or anonymous types. // Instead, try to get a human-readable type name. if (objectType != null && (objectType.getConstructor() != null || objectType.isFunctionPrototypeType())) { return objectType + "." + propName; } } } if (n.isQualifiedName()) { return n.getQualifiedName(); } else if (type.isFunctionType()) { // Don't show complex function names. return "function"; } else { return type.toString(); } } private JSType getJSTypeOrUnknown(Node n) { JSType jsType = n.getJSType(); if (jsType == null) { return getNativeType(UNKNOWN_TYPE); } else { return jsType; } } /** * Removes a type by name. * * @param jsTypeName The name string. */ public void removeType(String jsTypeName) { namesToTypes.remove(jsTypeName); } /** * Looks up a native type by name. * * @param jsTypeName The name string. * @return the corresponding JSType object or {@code null} it cannot be found */ // Unchecked conversion of the return type, from JSType to TypeI. @SuppressWarnings("unchecked") @Override public JSType getType(String jsTypeName) { // TODO(user): Push every local type name out of namesToTypes so that // NamedType#resolve is correct. TemplateType templateType = templateTypes.get(jsTypeName); if (templateType != null) { return templateType; } return namesToTypes.get(jsTypeName); } @SuppressWarnings("unchecked") @Override public JSType getNativeType(JSTypeNative typeId) { return nativeTypes[typeId.ordinal()]; } @SuppressWarnings("unchecked") @Override public ObjectType getNativeObjectType(JSTypeNative typeId) { return (ObjectType) getNativeType(typeId); } @SuppressWarnings("unchecked") @Override public FunctionType getNativeFunctionType(JSTypeNative typeId) { return (FunctionType) getNativeType(typeId); } /** * Looks up a type by name. To allow for forward references to types, an * unrecognized string has to be bound to a NamedType object that will be * resolved later. * * @param scope A scope for doing type name resolution. * @param jsTypeName The name string. * @param sourceName The name of the source file where this reference appears. * @param lineno The line number of the reference. * @return a NamedType if the string argument is not one of the known types, * otherwise the corresponding JSType object. */ public JSType getType(StaticTypedScope scope, String jsTypeName, String sourceName, int lineno, int charno) { return getType(scope, jsTypeName, sourceName, lineno, charno, true); } /** * @param recordUnresolvedTypes record unresolved named types and resolve * them later. Set to false if types should be ignored for backwards * compatibility (i.e. previously unparsed template type args). */ private JSType getType( StaticTypedScope scope, String jsTypeName, String sourceName, int lineno, int charno, boolean recordUnresolvedTypes) { switch (jsTypeName) { case "boolean": return getNativeType(JSTypeNative.BOOLEAN_TYPE); case "number": return getNativeType(JSTypeNative.NUMBER_TYPE); case "string": return getNativeType(JSTypeNative.STRING_TYPE); case "undefined": case "void": return getNativeType(JSTypeNative.VOID_TYPE); } // Resolve template type names JSType type = null; JSType thisType = null; if (scope != null && scope.getTypeOfThis() != null) { thisType = scope.getTypeOfThis().toObjectType(); } if (thisType != null) { type = thisType.getTemplateTypeMap().getTemplateTypeKeyByName(jsTypeName); if (type != null) { Preconditions.checkState(type.isTemplateType(), "expected:%s", type); return type; } } type = getType(jsTypeName); if (type == null) { // TODO(user): Each instance should support named type creation using // interning. NamedType namedType = createNamedType(jsTypeName, sourceName, lineno, charno); if (recordUnresolvedTypes) { unresolvedNamedTypes.put(scope, namedType); } type = namedType; } return type; } /** * Flushes out the current resolved and unresolved Named Types from * the type registry. This is intended to be used ONLY before a * compile is run. */ public void clearNamedTypes() { resolvedNamedTypes.clear(); unresolvedNamedTypes.clear(); } /** * Resolve all the unresolved types in the given scope. */ public void resolveTypesInScope(StaticTypedScope scope) { for (NamedType type : unresolvedNamedTypes.get(scope)) { type.resolve(reporter, scope); } resolvedNamedTypes.putAll(scope, unresolvedNamedTypes.removeAll(scope)); if (scope != null && scope.getParentScope() == null) { // By default, the global "this" type is just an anonymous object. // If the user has defined a Window type, make the Window the // implicit prototype of "this". PrototypeObjectType globalThis = (PrototypeObjectType) getNativeType( JSTypeNative.GLOBAL_THIS); JSType windowType = getType("Window"); if (globalThis.isUnknownType()) { ObjectType windowObjType = ObjectType.cast(windowType); if (windowObjType != null) { globalThis.setImplicitPrototype(windowObjType); } else { globalThis.setImplicitPrototype( getNativeObjectType(JSTypeNative.OBJECT_TYPE)); } } } } @Override public JSType evaluateTypeExpressionInGlobalScope(JSTypeExpression expr) { return expr.evaluate(null, this); } /** * Creates a type representing optional values of the given type. * @return the union of the type and the void type */ public JSType createOptionalType(JSType type) { if (type instanceof UnknownType || type.isAllType()) { return type; } else { return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE)); } } /** * Creates a type representing nullable values of the given type. * @return the union of the type and the Null type */ public JSType createDefaultObjectUnion(JSType type) { if (type.isTemplateType()) { // Template types represent the substituted type exactly and should // not be wrapped. return type; } else { return createNullableType(type); } } /** * Creates a type representing nullable values of the given type. * @return the union of the type and the Null type */ public JSType createNullableType(JSType type) { return createUnionType(type, getNativeType(JSTypeNative.NULL_TYPE)); } /** * Creates a nullable and undefine-able value of the given type. * @return The union of the type and null and undefined. */ public JSType createOptionalNullableType(JSType type) { return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE), getNativeType(JSTypeNative.NULL_TYPE)); } /** * Creates a union type whose variants are the arguments. */ public JSType createUnionType(JSType... variants) { UnionTypeBuilder builder = new UnionTypeBuilder(this); for (JSType type : variants) { builder.addAlternate(type); } return builder.build(); } @Override public JSType createUnionType(List variants) { return createUnionType(variants.toArray(new JSType[0])); } /** * Creates a union type whose variants are the built-in types specified * by the arguments. */ public JSType createUnionType(JSTypeNative... variants) { UnionTypeBuilder builder = new UnionTypeBuilder(this); for (JSTypeNative typeId : variants) { builder.addAlternate(getNativeType(typeId)); } return builder.build(); } /** * Creates an enum type. */ public EnumType createEnumType( String name, Node source, JSType elementsType) { return new EnumType(this, name, source, elementsType); } /** * Creates an arrow type, an abstract representation of the parameters * and return value of a function. * * @param parametersNode the parameters' types, formatted as a Node with * param names and optionality info. * @param returnType the function's return type */ ArrowType createArrowType(Node parametersNode, JSType returnType) { return new ArrowType(this, parametersNode, returnType); } /** * Creates an arrow type with an unknown return type. * * @param parametersNode the parameters' types, formatted as a Node with * param names and optionality info. */ ArrowType createArrowType(Node parametersNode) { return new ArrowType(this, parametersNode, null); } /** * Creates a function type. * * @param returnType the function's return type * @param parameterTypes the parameters' types */ public FunctionType createFunctionType( JSType returnType, JSType... parameterTypes) { return createFunctionType(returnType, createParameters(parameterTypes)); } /** * Creates a function type. The last parameter type of the function is * considered a variable length argument. * * @param returnType the function's return type * @param parameterTypes the parameters' types */ public FunctionType createFunctionTypeWithVarArgs( JSType returnType, JSType... parameterTypes) { return createFunctionType( returnType, createParametersWithVarArgs(parameterTypes)); } /** * Creates a function type. The last parameter type of the function is * considered a variable length argument. * * @param returnType the function's return type * @param parameterTypes the parameters' types */ private FunctionType createNativeFunctionTypeWithVarArgs( JSType returnType, JSType... parameterTypes) { return createNativeFunctionType( returnType, createParametersWithVarArgs(parameterTypes)); } /** * Creates a function type in which {@code this} refers to an object instance. * * @param instanceType the type of {@code this} * @param returnType the function's return type * @param parameterTypes the parameters' types */ public JSType createFunctionTypeWithInstanceType(ObjectType instanceType, JSType returnType, List parameterTypes) { Node paramsNode = createParameters(parameterTypes.toArray(new JSType[parameterTypes.size()])); return new FunctionBuilder(this) .withParamsNode(paramsNode) .withReturnType(returnType) .withTypeOfThis(instanceType) .build(); } /** * Creates a tree hierarchy representing a typed argument list. * * @param parameterTypes the parameter types. * @return a tree hierarchy representing a typed argument list. */ public Node createParameters(JSType... parameterTypes) { return createParameters(false, parameterTypes); } /** * Creates a tree hierarchy representing a typed argument list. The last * parameter type is considered a variable length argument. * * @param parameterTypes the parameter types. The last element of this array * is considered a variable length argument. * @return a tree hierarchy representing a typed argument list. */ public Node createParametersWithVarArgs(JSType... parameterTypes) { return createParameters(true, parameterTypes); } /** * Creates a tree hierarchy representing a typed parameter list in which * every parameter is optional. */ public Node createOptionalParameters(JSType... parameterTypes) { FunctionParamBuilder builder = new FunctionParamBuilder(this); builder.addOptionalParams(parameterTypes); return builder.build(); } /** * Creates a tree hierarchy representing a typed argument list. * * @param lastVarArgs whether the last type should considered as a variable * length argument. * @param parameterTypes the parameter types. The last element of this array * is considered a variable length argument is {@code lastVarArgs} is * {@code true}. * @return a tree hierarchy representing a typed argument list */ private Node createParameters(boolean lastVarArgs, JSType... parameterTypes) { FunctionParamBuilder builder = new FunctionParamBuilder(this); int max = parameterTypes.length - 1; for (int i = 0; i <= max; i++) { if (lastVarArgs && i == max) { builder.addVarArgs(parameterTypes[i]); } else { builder.addRequiredParams(parameterTypes[i]); } } return builder.build(); } /** * Creates a new function type based on an existing function type but * with a new return type. * @param existingFunctionType the existing function type. * @param returnType the new return type. */ public FunctionType createFunctionTypeWithNewReturnType( FunctionType existingFunctionType, JSType returnType) { return new FunctionBuilder(this) .copyFromOtherFunction(existingFunctionType) .withReturnType(returnType) .build(); } /** * @param parameters the function's parameters or {@code null} * to indicate that the parameter types are unknown. * @param returnType the function's return type or {@code null} to indicate * that the return type is unknown. */ public FunctionType createFunctionType( JSType returnType, Node parameters) { return new FunctionBuilder(this) .withParamsNode(parameters) .withReturnType(returnType) .build(); } private FunctionType createNativeFunctionType( JSType returnType, Node parameters) { return new FunctionBuilder(this) .withParamsNode(parameters) .withReturnType(returnType) .forNativeType() .build(); } @Override public JSType buildRecordTypeFromObject(ObjectTypeI obj) { ObjectType objType = (ObjectType) obj; RecordType recType = objType.toMaybeRecordType(); // If it can be casted to a record type then return if (recType != null) { return recType; } // TODO(lpino): Handle inherited properties Set propNames = objType.getOwnPropertyNames(); // If the type has no properties then return Object if (propNames.isEmpty()) { return getNativeType(JSTypeNative.OBJECT_TYPE); } ImmutableMap.Builder props = new ImmutableMap.Builder<>(); // Otherwise collect the properties and build a record type for (String propName : propNames) { props.put(propName, objType.getPropertyType(propName)); } return createRecordType(props.build()); } @Override public JSType createRecordType(Map props) { @SuppressWarnings("unchecked") Map propMap = (Map) props; RecordTypeBuilder builder = new RecordTypeBuilder(this); for (Entry e : propMap.entrySet()) { builder.addProperty(e.getKey(), e.getValue(), null); } return builder.build(); } /** * Create an object type. */ public ObjectType createObjectType(String name, ObjectType implicitPrototype) { return new PrototypeObjectType(this, name, implicitPrototype); } /** * Create an anonymous object type. * @param info Used to mark object literals as structs; can be {@code null} */ public ObjectType createAnonymousObjectType(JSDocInfo info) { PrototypeObjectType type = new PrototypeObjectType( this, null, null, true /* anonymousType */); type.setPrettyPrint(true); type.setJSDocInfo(info); return type; } /** * Set the implicit prototype if it's possible to do so. * @return True if we were able to set the implicit prototype successfully, * false if it was not possible to do so for some reason. There are * a few different reasons why this could fail: for example, numbers * can't be implicit prototypes, and we don't want to change the implicit * prototype if other classes have already subclassed this one. */ public boolean resetImplicitPrototype( JSType type, ObjectType newImplicitProto) { if (type instanceof PrototypeObjectType) { PrototypeObjectType poType = (PrototypeObjectType) type; poType.clearCachedValues(); poType.setImplicitPrototype(newImplicitProto); return true; } return false; } /** * Creates a constructor function type. * * @param name the function's name or {@code null} to indicate that the function is anonymous. * @param source the node defining this function. Its type ({@link Node#getToken()} ()}) must be * {@link Token#FUNCTION}. * @param parameters the function's parameters or {@code null} to indicate that the parameter * types are unknown. * @param returnType the function's return type or {@code null} to indicate that the return type * is unknown. * @param templateKeys the templatized types for the class. * @param isAbstract whether the function type represents an abstract class */ public FunctionType createConstructorType( String name, Node source, Node parameters, JSType returnType, ImmutableList templateKeys, boolean isAbstract) { Preconditions.checkArgument(source == null || source.isFunction()); return new FunctionType( this, name, source, createArrowType(parameters, returnType), null, createTemplateTypeMap(templateKeys, null), true, false, isAbstract); } /** * Creates an interface function type. * * @param name the function's name * @param source the node defining this function. Its type ({@link Node#getToken()}) must be * {@link Token#FUNCTION}. * @param templateKeys the templatized types for the interface. */ public FunctionType createInterfaceType( String name, Node source, ImmutableList templateKeys, boolean struct) { FunctionType fn = FunctionType.forInterface(this, name, source, createTemplateTypeMap(templateKeys, null)); if (struct) { fn.setStruct(); } return fn; } public TemplateType createTemplateType(String name) { return new TemplateType(this, name); } public TemplateType createTemplateTypeWithTransformation( String name, Node expr) { return new TemplateType(this, name, expr); } /** * Creates a template type map from the specified list of template keys and * template value types. */ public TemplateTypeMap createTemplateTypeMap( ImmutableList templateKeys, ImmutableList templateValues) { if (templateKeys == null) { templateKeys = ImmutableList.of(); } if (templateValues == null) { templateValues = ImmutableList.of(); } return (templateKeys.isEmpty() && templateValues.isEmpty()) ? emptyTemplateTypeMap : new TemplateTypeMap(this, templateKeys, templateValues); } public ObjectTypeI instantiateGenericsWithUnknown(ObjectType obj) { if (obj.isTemplatizedType()) { ImmutableList.Builder unknowns = ImmutableList.builder(); for (TemplateType ignore : obj.getTemplateTypeMap().getTemplateKeys()) { unknowns.add(getNativeType(UNKNOWN_TYPE)); } return createTemplatizedType(obj.toMaybeTemplatizedType().getRawType(), unknowns.build()); } return obj; } @SuppressWarnings("unchecked") @Override public TypeI instantiateGenericType( ObjectTypeI genericType, ImmutableList typeArgs) { return createTemplatizedType((ObjectType) genericType, (ImmutableList) typeArgs); } /** * Creates a templatized instance of the specified type. Only ObjectTypes * can currently be templatized; extend the logic in this function when * more types can be templatized. * @param baseType the type to be templatized. * @param templatizedTypes a list of the template JSTypes. Will be matched by * list order to the template keys on the base type. */ public TemplatizedType createTemplatizedType( ObjectType baseType, ImmutableList templatizedTypes) { // Only ObjectTypes can currently be templatized; extend this logic when // more types can be templatized. return new TemplatizedType(this, baseType, templatizedTypes); } /** * Creates a templatized instance of the specified type. Only ObjectTypes * can currently be templatized; extend the logic in this function when * more types can be templatized. * @param baseType the type to be templatized. * @param templatizedTypes a map from TemplateType to corresponding JSType * value. Any unfilled TemplateTypes on the baseType that are *not* * contained in this map will have UNKNOWN_TYPE used as their value. */ public TemplatizedType createTemplatizedType( ObjectType baseType, Map templatizedTypes) { ImmutableList.Builder builder = ImmutableList.builder(); TemplateTypeMap baseTemplateTypeMap = baseType.getTemplateTypeMap(); for (TemplateType key : baseTemplateTypeMap.getUnfilledTemplateKeys()) { JSType templatizedType = templatizedTypes.containsKey(key) ? templatizedTypes.get(key) : getNativeType(UNKNOWN_TYPE); builder.add(templatizedType); } return createTemplatizedType(baseType, builder.build()); } /** * Creates a templatized instance of the specified type. Only ObjectTypes * can currently be templatized; extend the logic in this function when * more types can be templatized. * @param baseType the type to be templatized. * @param templatizedTypes a list of the template JSTypes. Will be matched by * list order to the template keys on the base type. */ public TemplatizedType createTemplatizedType(ObjectType baseType, JSType... templatizedTypes) { return createTemplatizedType(baseType, ImmutableList.copyOf(templatizedTypes)); } /** * Creates a named type. */ @VisibleForTesting public NamedType createNamedType(String reference, String sourceName, int lineno, int charno) { if (reference.endsWith(".")) { return new NamespaceType(this, reference, sourceName, lineno, charno); } else { return new NamedType(this, reference, sourceName, lineno, charno); } } /** * Identifies the name of a typedef or enum before we actually declare it. */ public void identifyNonNullableName(String name) { Preconditions.checkNotNull(name); nonNullableTypeNames.add(name); } @SuppressWarnings("unchecked") @Override public JSType evaluateTypeExpression(JSTypeExpression expr, TypeIEnv scope) { return createTypeFromCommentNode( expr.getRoot(), expr.getSourceName(), (StaticTypedScope) scope); } @Override public JSType createTypeFromCommentNode(Node n) { return createTypeFromCommentNode(n, "[internal]", null); } /** * Creates a JSType from the nodes representing a type. * @param n The node with type info. * @param sourceName The source file name. * @param scope A scope for doing type name lookups. */ @SuppressWarnings("unchecked") public JSType createTypeFromCommentNode( Node n, String sourceName, StaticTypedScope scope) { return createFromTypeNodesInternal(n, sourceName, (StaticTypedScope) scope, true); } private JSType createFromTypeNodesInternal(Node n, String sourceName, StaticTypedScope scope, boolean recordUnresolvedTypes) { switch (n.getToken()) { case LC: // Record type. return createRecordTypeFromNodes( n.getFirstChild(), sourceName, scope); case BANG: // Not nullable return createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope, recordUnresolvedTypes) .restrictByNotNullOrUndefined(); case QMARK: // Nullable or unknown Node firstChild = n.getFirstChild(); if (firstChild == null) { return getNativeType(UNKNOWN_TYPE); } return createNullableType( createFromTypeNodesInternal( firstChild, sourceName, scope, recordUnresolvedTypes)); case EQUALS: // Optional return createOptionalType( createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope, recordUnresolvedTypes)); case ELLIPSIS: // Var args return createOptionalType( createFromTypeNodesInternal( n.getFirstChild(), sourceName, scope, recordUnresolvedTypes)); case STAR: // The AllType return getNativeType(ALL_TYPE); case PIPE: // Union type UnionTypeBuilder builder = new UnionTypeBuilder(this); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { builder.addAlternate( createFromTypeNodesInternal(child, sourceName, scope, recordUnresolvedTypes)); } return builder.build(); case EMPTY: // When the return value of a function is not specified return getNativeType(UNKNOWN_TYPE); case VOID: // Only allowed in the return value of a function. return getNativeType(VOID_TYPE); case STRING: // TODO(martinprobst): The new type syntax resolution should be separate. // Remove the NAME case then. case NAME: JSType namedType = getType( scope, n.getString(), sourceName, n.getLineno(), n.getCharno(), recordUnresolvedTypes); if ((namedType instanceof ObjectType) && !(namedType instanceof NamespaceType) && !(nonNullableTypeNames.contains(n.getString()))) { Node typeList = n.getFirstChild(); boolean isUnknownForwardDeclared = namedType.isUnknownType() && isForwardDeclaredType(n.getString()); if ((!namedType.isUnknownType() || isUnknownForwardDeclared) && typeList != null) { // Templatized types. ImmutableList.Builder templateTypes = ImmutableList.builder(); // Special case for Object, where Object implies Object. if ((n.getString().equals("Object") || n.getString().equals("window.Object")) && typeList.hasZeroOrOneChild()) { templateTypes.add(getNativeType(UNKNOWN_TYPE)); } int nAllowedTypes = isUnknownForwardDeclared ? Integer.MAX_VALUE : namedType.getTemplateTypeMap().numUnfilledTemplateKeys(); boolean recordTemplateArgs = recordUnresolvedTypes && !isUnknownForwardDeclared; int templateNodeIndex = 0; for (Node templateNode : typeList.children()) { // Don't parse more templatized type nodes than the type can // accommodate. This is because some existing clients have // template annotations on non-templatized classes, for instance: // goog.structs.Set // The problem in these cases is that the previously-unparsed // SomeType is not actually a valid type. To prevent these clients // from seeing unknown type errors, we explicitly don't parse // these types. // TODO(dimvar): Address this issue by removing bad template // annotations on non-templatized classes. if (++templateNodeIndex > nAllowedTypes) { reporter.warning( "Too many template parameters", sourceName, templateNode.getLineno(), templateNode.getCharno()); break; } templateTypes.add( createFromTypeNodesInternal(templateNode, sourceName, scope, recordTemplateArgs)); } if (isUnknownForwardDeclared) { // For backwards compatibility, construct a TemplatizedType but "hide" the template // arguments from further resolution. namedType = new NamedType( this, n.getString(), sourceName, n.getLineno(), n.getCharno(), templateTypes.build()); } else { namedType = createTemplatizedType((ObjectType) namedType, templateTypes.build()); } Preconditions.checkNotNull(namedType); } return createDefaultObjectUnion(namedType); } else { return namedType; } case FUNCTION: JSType thisType = null; boolean isConstructor = false; Node current = n.getFirstChild(); if (current.isThis() || current.isNew()) { Node contextNode = current.getFirstChild(); JSType candidateThisType = createFromTypeNodesInternal( contextNode, sourceName, scope, recordUnresolvedTypes); // Allow null/undefined 'this' types to indicate that // the function is not called in a deliberate context, // and 'this' access should raise warnings. if (candidateThisType.isNullType() || candidateThisType.isVoidType()) { thisType = candidateThisType; } else if (current.isThis()) { thisType = candidateThisType.restrictByNotNullOrUndefined(); } else if (current.isNew()) { thisType = ObjectType.cast( candidateThisType.restrictByNotNullOrUndefined()); if (thisType == null) { reporter.warning( SimpleErrorReporter.getMessage0( "msg.jsdoc.function.newnotobject"), sourceName, contextNode.getLineno(), contextNode.getCharno()); } } isConstructor = current.getToken() == Token.NEW; current = current.getNext(); } FunctionParamBuilder paramBuilder = new FunctionParamBuilder(this); if (current.getToken() == Token.PARAM_LIST) { for (Node arg = current.getFirstChild(); arg != null; arg = arg.getNext()) { if (arg.getToken() == Token.ELLIPSIS) { if (arg.getChildCount() == 0) { paramBuilder.addVarArgs(getNativeType(UNKNOWN_TYPE)); } else { paramBuilder.addVarArgs( createFromTypeNodesInternal( arg.getFirstChild(), sourceName, scope, recordUnresolvedTypes)); } } else { JSType type = createFromTypeNodesInternal( arg, sourceName, scope, recordUnresolvedTypes); if (arg.getToken() == Token.EQUALS) { boolean addSuccess = paramBuilder.addOptionalParams(type); if (!addSuccess) { reporter.warning( SimpleErrorReporter.getMessage0( "msg.jsdoc.function.varargs"), sourceName, arg.getLineno(), arg.getCharno()); } } else { paramBuilder.addRequiredParams(type); } } } current = current.getNext(); } JSType returnType = createFromTypeNodesInternal(current, sourceName, scope, recordUnresolvedTypes); return new FunctionBuilder(this) .withParamsNode(paramBuilder.build()) .withReturnType(returnType) .withTypeOfThis(thisType) .setIsConstructor(isConstructor) .build(); default: break; } throw new IllegalStateException("Unexpected node in type expression: " + n); } /** * Creates a RecordType from the nodes representing said record type. * @param n The node with type info. * @param sourceName The source file name. * @param scope A scope for doing type name lookups. */ private JSType createRecordTypeFromNodes(Node n, String sourceName, StaticTypedScope scope) { RecordTypeBuilder builder = new RecordTypeBuilder(this); // For each of the fields in the record type. for (Node fieldTypeNode = n.getFirstChild(); fieldTypeNode != null; fieldTypeNode = fieldTypeNode.getNext()) { // Get the property's name. Node fieldNameNode = fieldTypeNode; boolean hasType = false; if (fieldTypeNode.getToken() == Token.COLON) { fieldNameNode = fieldTypeNode.getFirstChild(); hasType = true; } String fieldName = fieldNameNode.getString(); // TODO(user): Move this into the lexer/parser. // Remove the string literal characters around a field name, // if any. if (fieldName.startsWith("'") || fieldName.startsWith("\"")) { fieldName = fieldName.substring(1, fieldName.length() - 1); } // Get the property's type. JSType fieldType = null; if (hasType) { // We have a declared type. fieldType = createFromTypeNodesInternal( fieldTypeNode.getLastChild(), sourceName, scope, true); } else { // Otherwise, the type is UNKNOWN. fieldType = getNativeType(JSTypeNative.UNKNOWN_TYPE); } builder.addProperty(fieldName, fieldType, fieldNameNode); } return builder.build(); } /** * Sets the template type name. */ public void setTemplateTypeNames(List keys) { Preconditions.checkNotNull(keys); for (TemplateType key : keys) { templateTypes.put(key.getReferenceName(), key); } } /** * Clears the template type name. */ public void clearTemplateTypeNames() { templateTypes.clear(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy