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

com.google.javascript.rhino.jstype.ObjectType 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.

There is a newer version: v20240317
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.common.base.Preconditions.checkNotNull;
import static com.google.javascript.jscomp.base.JSCompObjects.identical;

import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.javascript.jscomp.base.Tri;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.Property.OwnedProperty;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.jspecify.nullness.Nullable;

/**
 * Object type.
 *
 * 

In JavaScript, all object types have properties, and each of those properties has a type. * Property types may be DECLARED, INFERRED, or UNKNOWN. * *

DECLARED properties have an explicit type annotation, as in: * /xx @type {number} x/ * Foo.prototype.bar = 1; * This property may only hold number values, and an assignment to any other type of value * is an error. * *

INFERRED properties do not have an explicit type annotation. Rather, we try to find all the * possible types that this property can hold. * Foo.prototype.bar = 1; * If the programmer assigns other types of values to this property, the property will take * on the union of all these types. * *

UNKNOWN properties are properties on the UNKNOWN type. The UNKNOWN type has all properties, * but we do not know whether they are declared or inferred. * */ public abstract class ObjectType extends JSType { private boolean visited; private @Nullable JSDocInfo docInfo = null; private boolean unknown = true; ObjectType(JSTypeRegistry registry) { super(registry); } ObjectType(JSTypeRegistry registry, TemplateTypeMap templateTypeMap) { super(registry, templateTypeMap); } /** * Returns the property map that manages the set of properties for an object. */ PropertyMap getPropertyMap() { return PropertyMap.immutableEmptyMap(); } /** * Default getSlot implementation. This gets overridden by FunctionType for lazily-resolved * prototypes. */ public @Nullable Property getSlot(String name) { OwnedProperty property = getPropertyMap().findClosest(name); return property == null ? null : property.getValue(); } public final Property getOwnSlot(String name) { return getPropertyMap().getOwnProperty(name); } public JSType getTypeOfThis() { return null; } /** * Gets the declared default element type. * * @see TemplatizedType */ public ImmutableList getTemplateTypes() { return ImmutableList.of(); } /** * Gets the docInfo for this type. */ @Override public JSDocInfo getJSDocInfo() { return docInfo; } /** * Sets the docInfo for this type from the given * {@link JSDocInfo}. The {@code JSDocInfo} may be {@code null}. */ public void setJSDocInfo(JSDocInfo info) { docInfo = info; } /** * Detects a cycle in the implicit prototype chain. This method accesses * the {@link #getImplicitPrototype()} method and must therefore be * invoked only after the object is sufficiently initialized to respond to * calls to this method.

* * @return True iff an implicit prototype cycle was detected. */ final boolean detectImplicitPrototypeCycle() { // detecting cycle this.visited = true; ObjectType p = getImplicitPrototype(); while (p != null) { if (p.visited) { return true; } else { p.visited = true; } p = p.getImplicitPrototype(); } // clean up p = this; do { p.visited = false; p = p.getImplicitPrototype(); } while (p != null); return false; } /** * Detects cycles in either the implicit prototype chain, or the implemented/extended * interfaces.

* * @return True iff a cycle was detected. */ final boolean detectInheritanceCycle() { if (detectImplicitPrototypeCycle() || Iterables.contains(this.getCtorImplementedInterfaces(), this)) { return true; } FunctionType fnType = this.getConstructor(); return fnType != null && fnType.checkExtendsLoop() != null; } /** * Gets the reference name for this object. This includes named types like constructors, * prototypes, and enums. It notably does not include literal types like strings and booleans and * structural types. * *

Returning an empty string means something different than returning null. An empty string may * indicate an anonymous constructor, which we treat differently than a literal type without a * reference name. e.g. in {@link InstanceObjectType#appendTo(TypeStringBuilder)} * * @return the object's name or {@code null} if this is an anonymous object */ public abstract @Nullable String getReferenceName(); /** * INVARIANT: {@code hasReferenceName()} is true if and only if {@code getReferenceName()} returns * a non-null string. * * @return true if the object is named, false if it is anonymous */ public final boolean hasReferenceName() { return getReferenceName() != null; } /** * Due to the complexity of some of our internal type systems, sometimes we have different types * constructed by the same constructor. In other parts of the type system, these are called * delegates. We construct these types by appending suffixes to the constructor name. * *

The normalized reference name does not have these suffixes, and as such, recollapses these * implicit types back to their real type. Note that suffixes such as ".prototype" can be added * after the delegate suffix, so anything after the parentheses must still be retained. */ public final @Nullable String getNormalizedReferenceName() { String name = getReferenceName(); if (name != null) { int start = name.indexOf('('); if (start != -1) { int end = name.lastIndexOf(')'); String prefix = name.substring(0, start); return end + 1 % name.length() == 0 ? prefix : prefix + name.substring(end + 1); } } return name; } @Override public String getDisplayName() { return getNormalizedReferenceName(); } /** * Creates a suffix for a proxy delegate. * @see #getNormalizedReferenceName */ public static String createDelegateSuffix(String suffix) { return "(" + suffix + ")"; } public final ObjectType getRawType() { TemplatizedType t = toMaybeTemplatizedType(); return t == null ? this : t.getReferencedType(); } @Override public Tri testForEquality(JSType that) { // super Tri result = super.testForEquality(that); if (result != null) { return result; } // TODO: consider tighten "testForEquality" for subtypes of Object: if Foo and Bar // are not related we don't want to allow "==" on them (similiarly we should disallow // number == for non-number context values, etc). if (that.isUnknownType() || that.isSubtypeOf(getNativeType(JSTypeNative.OBJECT_TYPE)) || that.isSubtypeOf(getNativeType(JSTypeNative.NUMBER_TYPE)) || that.isSubtypeOf(getNativeType(JSTypeNative.STRING_TYPE)) || that.isSubtypeOf(getNativeType(JSTypeNative.BOOLEAN_TYPE)) || that.isSubtypeOf(getNativeType(JSTypeNative.SYMBOL_TYPE)) || that.isSubtypeOf(getNativeType(JSTypeNative.BIGINT_TYPE))) { return Tri.UNKNOWN; } return Tri.FALSE; } /** * Gets this object's constructor. * @return this object's constructor or {@code null} if it is a native * object (constructed natively v.s. by instantiation of a function) */ public abstract FunctionType getConstructor(); public @Nullable FunctionType getSuperClassConstructor() { ObjectType iproto = getImplicitPrototype(); if (iproto == null) { return null; } iproto = iproto.getImplicitPrototype(); return iproto == null ? null : iproto.getConstructor(); } /** Returns the closest ancestor that defines the property including this type itself. */ public final @Nullable ObjectType getClosestDefiningType(String propertyName) { OwnedProperty property = getPropertyMap().findClosest(propertyName); return property == null ? null : property.getOwner(); } /** Returns the closest definition of the property including this type itself. */ public final OwnedProperty findClosestDefinition(String propertyName) { return getPropertyMap().findClosest(propertyName); } /** * Gets the implicit prototype (a.k.a. the {@code [[Prototype]]} property). */ public abstract ObjectType getImplicitPrototype(); /** * Returns a lazy, dynamic {@link Iterable} for the types forming the implicit prototype chain of * this type. * *

The chain is iterated bottom to top; from the nearest ancestor to the most distant. * Iteration stops when the next ancestor would be a {@code null} reference. * *

The created {@link Iterator}s will not reflect changes to the prototype chain of elements it * has already iterated past, but will reflect those of upcoming elements. Neither the {@link * Iterable} nor its {@link Iterator} support mutation. */ public final Iterable getImplicitPrototypeChain() { final ObjectType self = this; return () -> new AbstractIterator() { private ObjectType next = self; // We increment past this type before first access. @Override public ObjectType computeNext() { next = next.getImplicitPrototype(); return (next != null) ? next : endOfData(); } }; } /** * Defines a property whose type is explicitly declared by the programmer. * @param propertyName the property's name * @param type the type * @param propertyNode the node corresponding to the declaration of property * which might later be accessed using {@code getPropertyNode}. */ public final boolean defineDeclaredProperty(String propertyName, JSType type, Node propertyNode) { boolean result = defineProperty(propertyName, type, false, propertyNode); // All property definitions go through this method // or defineInferredProperty. Because the properties defined an an // object can affect subtyping, it's slightly more efficient // to register this after defining the property. registry.registerPropertyOnType(propertyName, this); return result; } /** * Defines a property whose type is on a synthesized object. These objects * don't actually exist in the user's program. They're just used for * bookkeeping in the type system. */ public final boolean defineSynthesizedProperty(String propertyName, JSType type, Node propertyNode) { return defineProperty(propertyName, type, false, propertyNode); } /** * Defines a property whose type is inferred. * @param propertyName the property's name * @param type the type * @param propertyNode the node corresponding to the inferred definition of * property that might later be accessed using {@code getPropertyNode}. */ public final boolean defineInferredProperty(String propertyName, JSType type, Node propertyNode) { if (hasProperty(propertyName)) { if (isPropertyTypeDeclared(propertyName)) { // We never want to hide a declared property with an inferred property. return true; } JSType originalType = checkNotNull(getPropertyType(propertyName)); type = originalType.getLeastSupertype(type); } // TODO(b/140764208): verify that if isResolved() then type.isResolved(). // Defining unresolved properties on resolved types is dangerous because the property type // may never be resolved. boolean result = defineProperty(propertyName, type, true, propertyNode); // All property definitions go through this method // or defineDeclaredProperty. Because the properties defined an an // object can affect subtyping, it's slightly more efficient // to register this after defining the property. registry.registerPropertyOnType(propertyName, this); return result; } /** * Defines a property.

* * For clarity, callers should prefer {@link #defineDeclaredProperty} and * {@link #defineInferredProperty}. * * @param propertyName the property's name * @param type the type * @param inferred {@code true} if this property's type is inferred * @param propertyNode the node that represents the definition of property. * Depending on the actual sub-type the node type might be different. * The general idea is to have an estimate of where in the source code * this property is defined. * @return True if the property was registered successfully, false if this * conflicts with a previous property type declaration. */ abstract boolean defineProperty(String propertyName, JSType type, boolean inferred, Node propertyNode); /** * Removes the declared or inferred property from this ObjectType. * * @param propertyName the property's name * @return true if the property was removed successfully. False if the * property did not exist, or could not be removed. */ public boolean removeProperty(String propertyName) { return false; } /** * Gets the node corresponding to the definition of the specified property. This could be the node * corresponding to declaration of the property or the node corresponding to the first reference * to this property, e.g., "this.propertyName" in a constructor. Note this is mainly intended to * be an estimate of where in the source code a property is defined. Sometime the returned node is * not even part of the global AST but in the AST of the JsDoc that defines a type. * * @param propertyName the name of the property * @return the {@code Node} corresponding to the property or null. */ public final @Nullable Node getPropertyNode(String propertyName) { Property p = getSlot(propertyName); return p == null ? null : p.getNode(); } public final Node getPropertyDefSite(String propertyName) { return getPropertyNode(propertyName); } public final @Nullable JSDocInfo getPropertyJSDocInfo(String propertyName) { Property p = getSlot(propertyName); return p == null ? null : p.getJSDocInfo(); } /** * Gets the docInfo on the specified property on this type. This should not be implemented * recursively, as you generally need to know exactly on which type in the prototype chain the * JSDocInfo exists. */ public final @Nullable JSDocInfo getOwnPropertyJSDocInfo(String propertyName) { Property p = getOwnSlot(propertyName); return p == null ? null : p.getJSDocInfo(); } public final @Nullable Node getOwnPropertyDefSite(String propertyName) { Property p = getOwnSlot(propertyName); return p == null ? null : p.getNode(); } /** * Sets the docInfo for the specified property from the * {@link JSDocInfo} on its definition. * @param info {@code JSDocInfo} for the property definition. May be * {@code null}. */ public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) { // by default, do nothing } /** Sets the node where the property was defined. */ public void setPropertyNode(String propertyName, Node defSite) { // by default, do nothing } @Override protected @Nullable JSType findPropertyTypeWithoutConsideringTemplateTypes(String propertyName) { return hasProperty(propertyName) ? getPropertyType(propertyName) : null; } /** * Gets the property type of the property whose name is given. If the * underlying object does not have this property, the Unknown type is * returned to indicate that no information is available on this property. * * This gets overridden by FunctionType for lazily-resolved call() and * bind() functions. * * @return the property's type or {@link UnknownType}. This method never * returns {@code null}. */ public JSType getPropertyType(String propertyName) { StaticTypedSlot slot = getSlot(propertyName); if (slot == null) { if (isNoResolvedType() || isCheckedUnknownType()) { return getNativeType(JSTypeNative.CHECKED_UNKNOWN_TYPE); } else if (isEmptyType()) { return getNativeType(JSTypeNative.NO_TYPE); } return getNativeType(JSTypeNative.UNKNOWN_TYPE); } return slot.getType(); } @Override public HasPropertyKind getPropertyKind(String propertyName, boolean autobox) { // Unknown types have all properties. return HasPropertyKind.of(isEmptyType() || isUnknownType() || getSlot(propertyName) != null); } /** * Checks whether the property whose name is given is present directly on * the object. Returns false even if it is declared on a supertype. */ public final HasPropertyKind getOwnPropertyKind(String propertyName) { return getOwnSlot(propertyName) != null ? HasPropertyKind.KNOWN_PRESENT : HasPropertyKind.ABSENT; } /** * Checks whether the property whose name is given is present directly on * the object. Returns false even if it is declared on a supertype. */ public final boolean hasOwnProperty(String propertyName) { return !getOwnPropertyKind(propertyName).equals(HasPropertyKind.ABSENT); } /** * Returns the names of all the properties directly on this type. * * Overridden by FunctionType to add "prototype". */ public Set getOwnPropertyNames() { // TODO(sdh): ObjectType specifies that this should include prototype properties, // but currently it does not. Check if this is a constructor and add them, but // this could possibly break things so it should be done separately. return getPropertyMap().getOwnPropertyNames(); } /** * Checks whether the property's type is inferred. */ public final boolean isPropertyTypeInferred(String propertyName) { StaticTypedSlot slot = getSlot(propertyName); return slot == null ? false : slot.isTypeInferred(); } /** * Checks whether the property's type is declared. */ public final boolean isPropertyTypeDeclared(String propertyName) { StaticTypedSlot slot = getSlot(propertyName); return slot == null ? false : !slot.isTypeInferred(); } @Override public boolean isStructuralType() { FunctionType constructor = this.getConstructor(); return constructor != null && constructor.isStructuralInterface(); } /** * Whether the given property is declared on this object. */ public final boolean hasOwnDeclaredProperty(String name) { return hasOwnProperty(name) && isPropertyTypeDeclared(name); } /** Checks whether the property was defined in the externs. */ public final boolean isPropertyInExterns(String propertyName) { Property p = getSlot(propertyName); return p == null ? false : p.isFromExterns(); } /** * Gets the number of properties of this object. */ public final int getPropertiesCount() { return getPropertyMap().getPropertiesCount(); } /** Returns a list of properties defined or inferred on this type and any of its supertypes. */ public final ImmutableSortedSet getPropertyNames() { return getPropertyMap().keySet(); } @Override public T visit(Visitor visitor) { return visitor.caseObjectType(this); } @Override T visit(RelationshipVisitor visitor, JSType that) { return visitor.caseObjectType(this, that); } /** * Returns whether {@code this} is on the implicit prototype chain of {@code other}. * *

{@code this} need not be the immediate prototype of {@code other}. */ final boolean isImplicitPrototypeOf(ObjectType other) { ObjectType unwrappedThis = deeplyUnwrap(this); for (other = deeplyUnwrap(other); other != null; other = deeplyUnwrap(other.getImplicitPrototype())) { // The prototype should match exactly. // NOTE: the use of "==" here rather than equals is deliberate. This method // is very hot in the type checker and relying on identity improves performance of both // type checking/type inferrence and property disambiguation. if (identical(unwrappedThis, other)) { return true; } } return false; } /** * Recursively unwraps all {@link ProxyObjectType}s, including unwrapping the raw type out of a * templatized type. * *

Guaranteed to return a non-proxy type (exception: will return an unresolved NamedType). */ static ObjectType deeplyUnwrap(ObjectType original) { ObjectType current = original; while (current instanceof ProxyObjectType) { if (current.isTemplatizedType()) { current = current.toMaybeTemplatizedType().getReferencedType(); } else if (current.isNamedType()) { if (!current.isSuccessfullyResolved()) { break; } current = current.toMaybeNamedType().getReferencedObjTypeInternal(); } else { // TODO(lharker): remove this case and instead fail. Only the Rhino unit tests are // triggering this by creating new ProxyObjectTypes. current = ((ProxyObjectType) current).getReferencedObjTypeInternal(); } } return current; } @Override public BooleanLiteralSet getPossibleToBooleanOutcomes() { return BooleanLiteralSet.TRUE; } /** * We treat this as the unknown type if any of its implicit prototype * properties is unknown. */ @Override public boolean isUnknownType() { // If the object is unknown now, check the supertype again, // because it might have been resolved since the last check. if (unknown) { ObjectType implicitProto = getImplicitPrototype(); if (implicitProto == null || implicitProto.isNativeObjectType()) { unknown = false; for (ObjectType interfaceType : getCtorExtendedInterfaces()) { if (interfaceType.isUnknownType()) { unknown = true; break; } } } else { unknown = implicitProto.isUnknownType(); } } return unknown; } @Override public boolean isObject() { return true; } /** * Returns true if any cached values have been set for this type. If true, * then the prototype chain should not be changed, as it might invalidate the * cached values. */ public boolean hasCachedValues() { return !unknown; } /** * Clear cached values. Should be called before making changes to a prototype * that may have been changed since creation. */ public void clearCachedValues() { unknown = true; } /** Whether this is a built-in object. */ @Override public boolean isNativeObjectType() { return false; } /** A null-safe version of JSType#toObjectType. */ public static @Nullable ObjectType cast(JSType type) { return type == null ? null : type.toObjectType(); } @Override public final boolean isFunctionPrototypeType() { return getOwnerFunction() != null; } public FunctionType getOwnerFunction() { return null; } /** Sets the owner function. By default, does nothing. */ void setOwnerFunction(FunctionType type) {} /** * Gets the interfaces implemented by the ctor associated with this type. * Intended to be overridden by subclasses. */ public Iterable getCtorImplementedInterfaces() { return ImmutableSet.of(); } /** * Gets the interfaces extended by the interface associated with this type. * Intended to be overridden by subclasses. */ public Iterable getCtorExtendedInterfaces() { return ImmutableSet.of(); } /** * get the map of properties to types covered in an object type * @return a Map that maps the property's name to the property's type */ public Map getPropertyTypeMap() { ImmutableMap.Builder propTypeMap = ImmutableMap.builder(); for (String name : this.getPropertyNames()) { propTypeMap.put(name, this.getPropertyType(name)); } return propTypeMap.buildOrThrow(); } public JSType getEnumeratedTypeOfEnumObject() { return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy