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

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

/*
 *
 * ***** 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.common.base.Preconditions.checkState;

import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.QualifiedName;
import com.google.javascript.rhino.StaticScope;
import com.google.javascript.rhino.StaticSlot;
import com.google.javascript.rhino.jstype.JSTypeRegistry.ModuleSlot;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;

/**
 * A {@code NamedType} is a named reference to some other type.  This provides
 * a convenient mechanism for implementing forward references to types; a
 * {@code NamedType} can be used as a placeholder until its reference is
 * resolved.  It is also useful for representing type names in JsDoc type
 * annotations, some of which may never be resolved (as they may refer to
 * types in host systems not yet supported by JSCompiler, such as the JVM.)

* * An important distinction: {@code NamedType} is a type name reference, * whereas {@link ObjectType} is a named type object, such as an Enum name. * The Enum itself is typically used only in a dot operator to name one of its * constants, or in a declaration, where its name will appear in a * NamedType.

* * A {@code NamedType} is not currently a full-fledged typedef, because it * cannot resolve to any JavaScript type. It can only resolve to a named * {@link JSTypeRegistry} type, or to {@link FunctionType} or * {@link EnumType}.

* * If full typedefs are to be supported, then each method on each type class * needs to be reviewed to make sure that everything works correctly through * typedefs. Alternatively, we would need to walk through the parse tree and * unroll each reference to a {@code NamedType} to its resolved type before * applying the rest of the analysis.

* * TODO(user): Revisit all of this logic.

* * The existing typing logic is hacky. Unresolved types should get processed * in a more consistent way, but with the Rhino merge coming, there will be * much that has to be changed.

* */ public final class NamedType extends ProxyObjectType { private static final long serialVersionUID = 1L; static int nominalHashCode(ObjectType type) { checkState(type.hasReferenceName()); String name = checkNotNull(type.getReferenceName()); return name.hashCode(); } private final String reference; private final String sourceName; private final int lineno; private final int charno; private final boolean nonNull; /** * Validates the type resolution. */ private transient Predicate validator; /** Property-defining continuations. */ private transient List propertyContinuations = null; /** * Template types defined on a named, not yet resolved type, or {@code null} if none. These are * ignored during resolution, for backwards compatibility with existing usage. This field is not * used for JSCompiler's type checking; it is only needed by Clutz. */ @Nullable private final ImmutableList templateTypes; @Nullable private StaticTypedScope resolutionScope; /** Create a named type based on the reference. */ NamedType( StaticTypedScope scope, JSTypeRegistry registry, String reference, String sourceName, int lineno, int charno) { this(scope, registry, reference, sourceName, lineno, charno, null); } NamedType( StaticTypedScope scope, JSTypeRegistry registry, String reference, String sourceName, int lineno, int charno, ImmutableList templateTypes) { super(registry, registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE)); this.nonNull = reference.startsWith("!"); this.resolutionScope = scope; this.reference = nonNull ? reference.substring(1) : reference; this.sourceName = sourceName; this.lineno = lineno; this.charno = charno; this.templateTypes = templateTypes; } /** Returns a new non-null version of this type. */ JSType getBangType() { if (nonNull) { return this; } else if (resolutionScope == null) { // Already resolved, just restrict. return getReferencedType().restrictByNotNullOrUndefined(); } return new NamedType( resolutionScope, registry, "!" + reference, sourceName, lineno, charno, templateTypes); } @Override public ImmutableList getTemplateTypes() { return templateTypes; } @Override boolean defineProperty(String propertyName, JSType type, boolean inferred, Node propertyNode) { if (!isResolved()) { // If this is an unresolved object type, we need to save all its // properties and define them when it is resolved. if (propertyContinuations == null) { propertyContinuations = new ArrayList<>(); } propertyContinuations.add( new PropertyContinuation( propertyName, type, inferred, propertyNode)); return true; } else { return super.defineProperty( propertyName, type, inferred, propertyNode); } } private void finishPropertyContinuations() { ObjectType referencedObjType = getReferencedObjTypeInternal(); if (referencedObjType != null && !referencedObjType.isUnknownType() && propertyContinuations != null) { for (PropertyContinuation c : propertyContinuations) { c.commit(this); } } propertyContinuations = null; } /** Returns the type to which this refers (which is unknown if unresolved). */ public JSType getReferencedType() { return getReferencedTypeInternal(); } @Override public String getReferenceName() { return reference; } @Override StringBuilder appendTo(StringBuilder sb, boolean forAnnotations) { JSType type = this.getReferencedType(); if (!isResolved() || type.isNoResolvedType()) { return sb.append(this.reference); } else { return type.appendTo(sb, forAnnotations); } } @Override public NamedType toMaybeNamedType() { return this; } @Override public boolean isNominalType() { return true; } @Override int recursionUnsafeHashCode() { // Recall that equality on `NamedType` uses only the name until successful resolution, then // delegates to the resolved type. return isSuccessfullyResolved() ? super.recursionUnsafeHashCode() : nominalHashCode(this); } /** * Resolve the referenced type within the enclosing scope. */ @Override JSType resolveInternal(ErrorReporter reporter) { if (!getReferencedType().isUnknownType()) { // In some cases (e.g. typeof(ns) when the actual type is just a literal object), a NamedType // is created solely for the purpose of naming an already-known type. When that happens, // there's nothing to look up, so just resolve the referenced type. return super.resolveInternal(reporter); } boolean resolved = false; if (reference.startsWith("typeof ")) { resolveTypeof(reporter); resolved = true; } // TODO(user): Investigate whether it is really necessary to keep two // different mechanisms for resolving named types, and if so, which order // makes more sense. // The `resolveViaClosureNamespace` mechanism can probably be deleted (or reworked) once the // compiler supports type annotations via path. The `resolveViaProperties` and // `resolveViaRegistry` are, unfortunately, both needed now with no migration plan. resolved = resolved || resolveViaClosureNamespace(reporter) || resolveViaRegistry(reporter); if (!resolved) { resolveViaProperties(reporter); } if (detectInheritanceCycle()) { handleTypeCycle(reporter); } super.resolveInternal(reporter); finishPropertyContinuations(); JSType result = getReferencedType(); if (isSuccessfullyResolved()) { int numKeys = result.getTemplateParamCount(); if (result.isObjectType() && (templateTypes != null && !templateTypes.isEmpty()) && numKeys > 0) { ImmutableList typeArgs = this.templateTypes; // Ignore any extraneous type args // TODO(johnlenz): report an error if (numKeys < this.templateTypes.size()) { typeArgs = typeArgs.subList(0, numKeys); } result = registry.createTemplatizedType(result.toMaybeObjectType(), typeArgs); setReferencedType(result); } resolutionScope = null; } return result; } /** * Resolves a named type by looking it up in the registry. * @return True if we resolved successfully. */ private boolean resolveViaRegistry(ErrorReporter reporter) { JSType type = registry.getType(resolutionScope, reference); if (type != null) { setReferencedAndResolvedType(type, reporter); return true; } return false; } /** * Resolves a named type by looking up its first component in the scope, and subsequent components * as properties. The scope must have been fully parsed and a symbol table constructed. */ private void resolveViaProperties(ErrorReporter reporter) { List componentNames = Splitter.on('.').splitToList(reference); if (componentNames.get(0).isEmpty()) { handleUnresolvedType(reporter, /* ignoreForwardReferencedTypes= */ true); return; } StaticTypedSlot slot = checkNotNull(resolutionScope, "resolutionScope") .getSlot(checkNotNull(componentNames, "componentNames").get(0)); if (slot == null) { handleUnresolvedType(reporter, /* ignoreForwardReferencedTypes= */ true); return; } Node definitionNode = slot.getDeclaration() != null ? slot.getDeclaration().getNode() : null; resolveViaPropertyGivenSlot( slot.getType(), definitionNode, componentNames, reporter, /* componentIndex= */ 1); } /** * Resolve a type using a given StaticTypedSlot and list of properties on that type. * * @param slotType the JSType of teh slot, possibly null * @param definitionNode If known, the Node representing the type definition. * @param componentIndex the index into {@code componentNames} at which to start resolving */ private void resolveViaPropertyGivenSlot( JSType slotType, Node definitionNode, List componentNames, ErrorReporter reporter, int componentIndex) { if (resolveTypeFromNodeIfTypedef(definitionNode, reporter)) { return; } // If the first component has a type of 'Unknown', then any type // names using it should be regarded as silently 'Unknown' rather than be // noisy about it. if (slotType == null || slotType.isAllType() || slotType.isNoType()) { handleUnresolvedType(reporter, /* ignoreForwardReferencedTypes= */ true); return; } // resolving component by component for (int i = componentIndex; i < componentNames.size(); i++) { String component = componentNames.get(i); ObjectType parentObj = ObjectType.cast(slotType); if (parentObj == null || component.length() == 0) { handleUnresolvedType(reporter, /* ignoreForwardReferencedTypes= */ true); return; } if (i == componentNames.size() - 1) { // Look for a typedefTypeProp on the definition node of the last component. Node def = parentObj.getPropertyDefSite(component); if (resolveTypeFromNodeIfTypedef(def, reporter)) { return; } } slotType = parentObj.getPropertyType(component); } // Translate "constructor" types to "instance" types. if (slotType == null) { handleUnresolvedType(reporter, /* ignoreForwardReferencedTypes= */ true); } else if (slotType.isFunctionType() && (slotType.isConstructor() || slotType.isInterface())) { setReferencedAndResolvedType(slotType.toMaybeFunctionType().getInstanceType(), reporter); } else if (slotType.isNoObjectType()) { setReferencedAndResolvedType( registry.getNativeObjectType(JSTypeNative.NO_OBJECT_TYPE), reporter); } else if (slotType instanceof EnumType) { setReferencedAndResolvedType(((EnumType) slotType).getElementsType(), reporter); } else { // We've been running into issues where people forward-declare // non-named types. (This is legitimate...our dependency management // code doubles as our forward-declaration code.) // // So if the type does resolve to an actual value, but it's not named, // then don't respect the forward declaration. handleUnresolvedType(reporter, slotType.isUnknownType()); } } private void resolveTypeof(ErrorReporter reporter) { String name = reference.substring("typeof ".length()); // TODO(sdh): require var to be const? JSType type = resolutionScope.lookupQualifiedName(QualifiedName.of(name)); if (type == null || type.isUnknownType()) { warning(reporter, "Missing type for `typeof` value. The value must be declared and const."); setReferencedAndResolvedType(registry.getNativeType(JSTypeNative.UNKNOWN_TYPE), reporter); } else { if (type.isLiteralObject()) { // Create an extra layer of wrapping so that the "typeof" name is preserved for namespaces. // This is depended on by Clutz to prevent infinite loops in self-referential typeof types. JSType objlit = type; type = registry.createNamedType(resolutionScope, reference, sourceName, lineno, charno); ((NamedType) type).setReferencedType(objlit); } setReferencedAndResolvedType(type, reporter); } } /** * Resolves a named type by checking for the longest prefix that matches some Closure namespace, * if any, then attempting to resolve via properties based on the type of the `exports` object in * that namespace. */ private boolean resolveViaClosureNamespace(ErrorReporter reporter) { List componentNames = Splitter.on('.').splitToList(reference); if (componentNames.get(0).isEmpty()) { return false; } StaticTypedSlot slot = resolutionScope.getSlot(componentNames.get(0)); // Skip types whose root component is defined in a local scope (not a global scope). Those will // follow the normal resolution scheme. (For legacy compatibility reasons we don't check for // global names that are the same as the module root). if (slot != null && slot.getScope() != null && slot.getScope().getParentScope() != null) { return false; } // Find the `exports` type of the longest prefix match of this namespace, if any. Then resolve // it via property. String prefix = reference; for (int remainingComponentIndex = componentNames.size(); remainingComponentIndex > 0; remainingComponentIndex--) { ModuleSlot module = registry.getModuleSlot(prefix); if (module == null) { int lastDot = prefix.lastIndexOf("."); if (lastDot >= 0) { prefix = prefix.substring(0, lastDot); } continue; } if (module.isLegacyModule()) { // Try to resolve this name via registry or properties. return false; } else { // Always stop resolution here whether successful or not, instead of continuing with // resolution via registry or via properties, to match legacy behavior. resolveViaPropertyGivenSlot( module.type(), module.definitionNode(), componentNames, reporter, remainingComponentIndex); return true; } } return false; // Keep trying to resolve this name. } /** Checks the given Node for a typedef annotation, resolving to that type if existent. */ private boolean resolveTypeFromNodeIfTypedef(Node node, ErrorReporter reporter) { if (node == null) { return false; } JSType typedefType = node.getTypedefTypeProp(); if (typedefType == null) { return false; } setReferencedAndResolvedType(typedefType, reporter); return true; } private void setReferencedAndResolvedType( JSType type, ErrorReporter reporter) { if (nonNull) { type = type.restrictByNotNullOrUndefined(); } if (validator != null) { validator.apply(type); } setReferencedType(type); checkEnumElementCycle(reporter); checkProtoCycle(reporter); setResolvedTypeInternal(getReferencedType()); } private void handleTypeCycle(ErrorReporter reporter) { setReferencedType( registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE)); warning(reporter, "Cycle detected in inheritance chain of type " + reference); setResolvedTypeInternal(getReferencedType()); } private void checkEnumElementCycle(ErrorReporter reporter) { JSType referencedType = getReferencedType(); if (referencedType instanceof EnumElementType && areIdentical(this, ((EnumElementType) referencedType).getPrimitiveType())) { handleTypeCycle(reporter); } } private void checkProtoCycle(ErrorReporter reporter) { JSType referencedType = getReferencedType(); if (areIdentical(referencedType, this)) { handleTypeCycle(reporter); } } /** Warns about this type being unresolved iff it's not a forward-declared type name */ private void handleUnresolvedType( ErrorReporter reporter, boolean ignoreForwardReferencedTypes) { boolean isForwardDeclared = ignoreForwardReferencedTypes && registry.isForwardDeclaredType(reference); if (!isForwardDeclared) { String msg = "Bad type annotation. Unknown type " + reference; // Look for a local variable that shadows a global namespace to give a clearer message. String root = reference.contains(".") ? reference.substring(0, reference.indexOf(".")) : reference; if (localVariableShadowsGlobalNamespace(root)) { msg += "\nIt's possible that a local variable called '" + root + "' is shadowing the intended global namespace."; } warning(reporter, msg); } else { setReferencedType(new NoResolvedType(registry, getReferenceName(), getTemplateTypes())); if (validator != null) { validator.apply(getReferencedType()); } } setResolvedTypeInternal(getReferencedType()); } /** * Check for an obscure but very confusing error condition where a local variable shadows a * global namespace. */ private boolean localVariableShadowsGlobalNamespace(String root) { StaticSlot rootVar = resolutionScope.getSlot(root); if (rootVar != null) { StaticScope parent = rootVar.getScope().getParentScope(); if (parent != null) { StaticSlot globalVar = parent.getSlot(root); return globalVar != null; } } return false; } @Override public boolean setValidator(Predicate validator) { // If the type is already resolved, we can validate it now. If // the type has not been resolved yet, we need to wait till its // resolved before we can validate it. if (this.isResolved()) { return super.setValidator(validator); } else { this.validator = validator; return true; } } void warning(ErrorReporter reporter, String message) { reporter.warning(message, sourceName, lineno, charno); } /** Store enough information to define a property at a later time. */ private static final class PropertyContinuation { private final String propertyName; private final JSType type; private final boolean inferred; private final Node propertyNode; private PropertyContinuation( String propertyName, JSType type, boolean inferred, Node propertyNode) { this.propertyName = propertyName; this.type = type; this.inferred = inferred; this.propertyNode = propertyNode; } void commit(ObjectType target) { target.defineProperty( propertyName, type, inferred, propertyNode); } } @Override public boolean isObject() { if (isEnumElementType()) { return toMaybeEnumElementType().isObject(); } return super.isObject(); } @Override public T visit(Visitor visitor) { return visitor.caseNamedType(this); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy