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

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

package com.google.javascript.jscomp;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.rhino.jstype.JSTypeNative.ARRAY_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.ASYNC_GENERATOR_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.BIGINT_NUMBER;
import static com.google.javascript.rhino.jstype.JSTypeNative.BIGINT_NUMBER_OBJECT;
import static com.google.javascript.rhino.jstype.JSTypeNative.BIGINT_NUMBER_STRING;
import static com.google.javascript.rhino.jstype.JSTypeNative.BIGINT_NUMBER_STRING_OBJECT;
import static com.google.javascript.rhino.jstype.JSTypeNative.BOOLEAN_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.GENERATOR_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.ITERABLE_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.I_TEMPLATE_ARRAY_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.NO_OBJECT_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.NULL_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.NUMBER_STRING_SYMBOL;
import static com.google.javascript.rhino.jstype.JSTypeNative.NUMBER_SYMBOL;
import static com.google.javascript.rhino.jstype.JSTypeNative.NUMBER_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.OBJECT_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.STRING_SYMBOL;
import static com.google.javascript.rhino.jstype.JSTypeNative.STRING_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.UNKNOWN_TYPE;
import static com.google.javascript.rhino.jstype.JSTypeNative.VOID_TYPE;

import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.JsIterables.MaybeBoxedType;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.EnumElementType;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSType.Nullability;
import com.google.javascript.rhino.jstype.JSType.SubtypingMode;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.NamedType;
import com.google.javascript.rhino.jstype.ObjectType;
import com.google.javascript.rhino.jstype.Property;
import com.google.javascript.rhino.jstype.Property.OwnedProperty;
import com.google.javascript.rhino.jstype.TemplateType;
import com.google.javascript.rhino.jstype.TemplateTypeMap;
import com.google.javascript.rhino.jstype.TemplateTypeReplacer;
import com.google.javascript.rhino.jstype.TemplatizedType;
import com.google.javascript.rhino.jstype.UnionType;
import com.google.javascript.rhino.jstype.UnknownType;
import com.google.javascript.rhino.jstype.Visitor;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import org.jspecify.nullness.Nullable;

/**
 * A central reporter for all type violations: places where the programmer has annotated a variable
 * (or property) with one type, but has assigned another type to it.
 *
 * 

Also doubles as a central repository for all type violations, so that type-based optimizations * (like AmbiguateProperties) can be fault-tolerant. */ class TypeValidator implements Serializable { private final transient AbstractCompiler compiler; private final JSTypeRegistry typeRegistry; private final JSType allBitwisableValueTypes; private final JSType nullOrUndefined; private final JSType promiseOfUnknownType; private final JSType iterableOrAsyncIterable; // In TypeCheck, when we are analyzing a file with .java.js suffix, we set // this field to IGNORE_NULL_UNDEFINED private SubtypingMode subtypingMode = SubtypingMode.NORMAL; private final TypeMismatch.Accumulator mismatches = new TypeMismatch.Accumulator(); // User warnings private static final String FOUND_REQUIRED = "{0}\n" + "found : {1}\n" + "required: {2}"; private static final String FOUND_REQUIRED_MISSING = "{0}\n" + "found : {1}\n" + "required: {2}\n" + "missing : [{3}]\n" + "mismatch: [{4}]"; static final DiagnosticType INVALID_CAST = DiagnosticType.warning( "JSC_INVALID_CAST", "invalid cast - must be a subtype or supertype\n" + "from: {0}\n" + "to : {1}"); static final DiagnosticType TYPE_MISMATCH_WARNING = DiagnosticType.warning("JSC_TYPE_MISMATCH", "{0}"); static final DiagnosticType INVALID_ASYNC_RETURN_TYPE = DiagnosticType.warning( "JSC_INVALID_ASYNC_RETURN_TYPE", "The return type of an async function must be a supertype of Promise\n" + "found: {0}"); static final DiagnosticType INVALID_OPERAND_TYPE = DiagnosticType.disabled("JSC_INVALID_OPERAND_TYPE", "{0}"); static final DiagnosticType MISSING_EXTENDS_TAG_WARNING = DiagnosticType.warning("JSC_MISSING_EXTENDS_TAG", "Missing @extends tag on type {0}"); static final DiagnosticType DUP_VAR_DECLARATION = DiagnosticType.warning( "JSC_DUP_VAR_DECLARATION", "variable {0} redefined, original definition at {1}:{2}"); static final DiagnosticType DUP_VAR_DECLARATION_TYPE_MISMATCH = DiagnosticType.warning( "JSC_DUP_VAR_DECLARATION_TYPE_MISMATCH", "variable {0} redefined with type {1}, original definition at {2}:{3} with type {4}"); static final DiagnosticType INTERFACE_METHOD_NOT_IMPLEMENTED = DiagnosticType.warning( "JSC_INTERFACE_METHOD_NOT_IMPLEMENTED", "property {0} on interface {1} is not implemented by type {2}"); static final DiagnosticType HIDDEN_INTERFACE_PROPERTY_MISMATCH = DiagnosticType.warning( "JSC_HIDDEN_INTERFACE_PROPERTY_MISMATCH", "mismatch of the {0} property on type {4} and the type " + "of the property it overrides from interface {1}\n" + "original: {2}\n" + "override: {3}"); // TODO(goktug): consider updating super class / interface property mismatch to follow the same // pattern. static final DiagnosticType HIDDEN_SUPERCLASS_PROPERTY_MISMATCH = DiagnosticType.warning( "JSC_HIDDEN_SUPERCLASS_PROPERTY_MISMATCH", "mismatch of the {0} property type and the type " + "of the property it overrides from superclass {1}\n" + "original: {2}\n" + "override: {3}"); static final DiagnosticType ABSTRACT_METHOD_NOT_IMPLEMENTED = DiagnosticType.warning( "JSC_ABSTRACT_METHOD_NOT_IMPLEMENTED", "property {0} on abstract class {1} is not implemented by type {2}"); static final DiagnosticType UNKNOWN_TYPEOF_VALUE = DiagnosticType.warning("JSC_UNKNOWN_TYPEOF_VALUE", "unknown type: {0}"); static final DiagnosticType ILLEGAL_PROPERTY_ACCESS = DiagnosticType.warning("JSC_ILLEGAL_PROPERTY_ACCESS", "Cannot do {0} access on a {1}"); static final DiagnosticGroup ALL_DIAGNOSTICS = new DiagnosticGroup( ABSTRACT_METHOD_NOT_IMPLEMENTED, DUP_VAR_DECLARATION, DUP_VAR_DECLARATION_TYPE_MISMATCH, HIDDEN_INTERFACE_PROPERTY_MISMATCH, ILLEGAL_PROPERTY_ACCESS, INTERFACE_METHOD_NOT_IMPLEMENTED, INVALID_ASYNC_RETURN_TYPE, INVALID_CAST, MISSING_EXTENDS_TAG_WARNING, TYPE_MISMATCH_WARNING, UNKNOWN_TYPEOF_VALUE); TypeValidator(AbstractCompiler compiler) { this.compiler = compiler; this.typeRegistry = compiler.getTypeRegistry(); this.allBitwisableValueTypes = typeRegistry.createUnionType(STRING_TYPE, NUMBER_TYPE, BOOLEAN_TYPE, NULL_TYPE, VOID_TYPE); this.nullOrUndefined = typeRegistry.getNativeType(JSTypeNative.NULL_VOID); this.promiseOfUnknownType = typeRegistry.createTemplatizedType( typeRegistry.getNativeObjectType(JSTypeNative.PROMISE_TYPE), typeRegistry.getNativeType(JSTypeNative.UNKNOWN_TYPE)); this.iterableOrAsyncIterable = typeRegistry.createUnionType( typeRegistry.getNativeObjectType(JSTypeNative.ITERATOR_TYPE), typeRegistry.getNativeObjectType(JSTypeNative.ASYNC_ITERATOR_TYPE)); } /** Utility function that attempts to get an instance type from a potential constructor type */ static @Nullable ObjectType getInstanceOfCtor(@Nullable JSType t) { if (t == null) { return null; } FunctionType ctor = JSType.toMaybeFunctionType(t.dereference()); if (ctor != null && ctor.isConstructor()) { return ctor.getInstanceType(); } return null; } /** * Gets a list of type violations. * *

For each violation, one element is the expected type and the other is the type that is * actually found. Order is not significant. * *

NOTE(dimvar): Even though TypeMismatch is a pair, the passes that call this method never use * it as a pair; they just add both its elements to a set of invalidating types. Consider just * maintaining a set of types here instead of a set of type pairs. */ Iterable getMismatches() { return this.mismatches.getMismatches(); } void setSubtypingMode(SubtypingMode mode) { this.subtypingMode = mode; } // All non-private methods should have the form: // expectCondition(Node n, ...); // If there is a mismatch, the {@code expect} method should issue // a warning and attempt to correct the mismatch, when possible. void expectValidTypeofName(Node n, String found) { this.compiler.report(JSError.make(n, UNKNOWN_TYPEOF_VALUE, found)); } /** * Expect the type to be an object, or a type convertible to object. If the expectation is not * met, issue a warning at the provided node's source code position. * * @return True if there was no warning, false if there was a mismatch. */ boolean expectObject(Node n, JSType type, String msg) { if (!type.matchesObjectContext()) { mismatch(n, msg, type, OBJECT_TYPE); return false; } return true; } /** * Expect the type to be an object. Unlike expectObject, a type convertible to object is not * acceptable. */ void expectActualObject(Node n, JSType type, String msg) { if (!type.isObject()) { mismatch(n, msg, type, OBJECT_TYPE); } } /** * Expect the type to contain an object sometimes. If the expectation is not met, issue a warning * at the provided node's source code position. */ void expectAnyObject(Node n, JSType type, String msg) { JSType anyObjectType = getNativeType(NO_OBJECT_TYPE); if (!anyObjectType.isSubtypeOf(type) && !type.isEmptyType()) { mismatch(n, msg, type, anyObjectType); } } /** * Expect the type to autobox to be an Iterable. * * @return True if there was no warning, false if there was a mismatch. */ boolean expectAutoboxesToIterable(Node n, JSType type, String msg) { // Note: we don't just use JSType.autobox() here because that removes null and undefined. // We want to keep null and undefined around. if (type.isUnionType()) { for (JSType alt : type.toMaybeUnionType().getAlternates()) { alt = alt.isBoxableScalar() ? alt.autoboxesTo() : alt; if (!alt.isSubtypeOf(getNativeType(ITERABLE_TYPE))) { mismatch(n, msg, type, ITERABLE_TYPE); return false; } } } else { JSType autoboxedType = type.isBoxableScalar() ? type.autoboxesTo() : type; if (!autoboxedType.isSubtypeOf(getNativeType(ITERABLE_TYPE))) { mismatch(n, msg, type, ITERABLE_TYPE); return false; } } return true; } /** * Expect the type to autobox to be an Iterable or AsyncIterable. * * @return The unwrapped variants of the iterable(s), or empty if not iterable. */ Optional expectAutoboxesToIterableOrAsyncIterable(Node n, JSType type, String msg) { MaybeBoxedType maybeBoxed = JsIterables.maybeBoxIterableOrAsyncIterable(type, typeRegistry); if (maybeBoxed.isMatch()) { return Optional.of(maybeBoxed.getTemplatedType()); } mismatch(n, msg, type, iterableOrAsyncIterable); return Optional.empty(); } /** Expect the type to be a Generator or supertype of Generator. */ void expectGeneratorSupertype(Node n, JSType type, String msg) { if (!getNativeType(GENERATOR_TYPE).isSubtypeOf(type)) { mismatch(n, msg, type, GENERATOR_TYPE); } } /** Expect the type to be a AsyncGenerator or supertype of AsyncGenerator. */ void expectAsyncGeneratorSupertype(Node n, JSType type, String msg) { if (!getNativeType(ASYNC_GENERATOR_TYPE).isSubtypeOf(type)) { mismatch(n, msg, type, ASYNC_GENERATOR_TYPE); } } /** * Expect the type to be a supertype of `Promise`. * *

`Promise` is the lower bound of the declared return type, since that's what async * functions always return; the user can't return an instance of a more specific type. */ void expectValidAsyncReturnType(Node n, JSType type) { if (promiseOfUnknownType.isSubtypeOf(type)) { return; } JSError err = JSError.make(n, INVALID_ASYNC_RETURN_TYPE, type.toString()); registerMismatchAndReport(type, promiseOfUnknownType, err); } /** Expect the type to be an ITemplateArray or supertype of ITemplateArray. */ void expectITemplateArraySupertype(Node n, JSType type, String msg) { if (!getNativeType(I_TEMPLATE_ARRAY_TYPE).isSubtypeOf(type)) { mismatch(n, msg, type, I_TEMPLATE_ARRAY_TYPE); } } /** * Expect the type to be a string, or a type convertible to string. If the expectation is not met, * issue a warning at the provided node's source code position. */ void expectString(Node n, JSType type, String msg) { if (!type.matchesStringContext()) { mismatch(n, msg, type, STRING_TYPE); } } /** * Expect the type to be a number, or a type convertible to number. If the expectation is not met, * issue a warning at the provided node's source code position. */ void expectNumber(Node n, JSType type, String msg) { if (!type.matchesNumberContext()) { mismatch(n, msg, type, NUMBER_TYPE); } else { expectNumberStrict(n, type, msg); } } /** Expect the type to be a number or a subtype. */ void expectNumberStrict(Node n, JSType type, String msg) { if (!type.isSubtypeOf(getNativeType(NUMBER_TYPE))) { registerMismatchAndReport( n, INVALID_OPERAND_TYPE, msg, type, getNativeType(NUMBER_TYPE), null, null); } } void expectMatchingTypesStrict(Node n, JSType left, JSType right, String msg) { if (!left.isSubtypeOf(right) && !right.isSubtypeOf(left)) { registerMismatchAndReport(n, INVALID_OPERAND_TYPE, msg, right, left, null, null); } } /** * Expect the type to be a valid operand to a bitwise operator. This includes numbers, any type * convertible to a number, or any other primitive type (undefined|null|boolean|string). */ void expectBitwiseable(Node n, JSType type, String msg) { if (!type.matchesNumberContext() && !type.isSubtypeOf(allBitwisableValueTypes)) { mismatch(n, msg, type, allBitwisableValueTypes); } else { expectNumberStrict(n, type, msg); } } /** * Expect the type to be a number or string, or a type convertible to a number or symbol. If the * expectation is not met, issue a warning at the provided node's source code position. */ void expectNumberOrSymbol(Node n, JSType type, String msg) { if (!type.matchesNumberContext() && !type.matchesSymbolContext()) { mismatch(n, msg, type, NUMBER_SYMBOL); } } /** * Expect the type to be a string or symbol, or a type convertible to a string. If the expectation * is not met, issue a warning at the provided node's source code position. */ void expectStringOrSymbol(Node n, JSType type, String msg) { if (!type.matchesStringContext() && !type.matchesSymbolContext()) { mismatch(n, msg, type, STRING_SYMBOL); } } /** Expect the type to be unknown or a comparable type (bigint, number, or string) */ void expectUnknownOrComparable(Node n, JSType type, String msg) { if (!type.isSubtypeOf(getNativeType(BIGINT_NUMBER_STRING)) && !type.isSubtypeOf(getNativeType(BIGINT_NUMBER_STRING_OBJECT))) { if (type.matchesNumberContext() || type.matchesStringContext()) { // INVALID_OPERAND_TYPE is suppressed unless strict type checking is enabled registerMismatchAndReport( n, INVALID_OPERAND_TYPE, msg, type, getNativeType(BIGINT_NUMBER_STRING), null, null); } else { mismatch(n, msg, type, BIGINT_NUMBER_STRING); } } } /** * Expect the type to be a number or string or symbol, or a type convertible to a number or * string. If the expectation is not met, issue a warning at the provided node's source code * position. */ void expectStringOrNumberOrSymbol(Node n, JSType type, String msg) { if (!type.matchesNumberContext() && !type.matchesStringContext() && !type.matchesSymbolContext()) { mismatch(n, msg, type, NUMBER_STRING_SYMBOL); } else { expectStringOrNumberOrSymbolStrict(n, type, msg); } } void expectStringOrNumberOrSymbolStrict(Node n, JSType type, String msg) { if (!type.isSubtypeOf(getNativeType(NUMBER_STRING_SYMBOL))) { registerMismatchAndReport( n, INVALID_OPERAND_TYPE, msg, type, getNativeType(NUMBER_STRING_SYMBOL), null, null); } } /** * Expect the type to be a bigint or number, or a type convertible to number. If the expectation * is not met, issue a warning at the provided node's source code position. */ void expectBigIntOrNumber(Node n, JSType type, String msg) { if (!type.isSubtypeOf(getNativeType(BIGINT_NUMBER)) && !type.isSubtypeOf(getNativeType(BIGINT_NUMBER_OBJECT))) { if (type.matchesNumberContext()) { // INVALID_OPERAND_TYPE is suppressed unless strict type checking is enabled registerMismatchAndReport( n, INVALID_OPERAND_TYPE, msg, type, getNativeType(BIGINT_NUMBER), null, null); } else { mismatch(n, msg, type, BIGINT_NUMBER); } } } /** * Expect the type to be anything but the null or void type. If the expectation is not met, issue * a warning at the provided node's source code position. Note that a union type that includes the * void type and at least one other type meets the expectation. * * @return Whether the expectation was met. */ boolean expectNotNullOrUndefined( NodeTraversal t, Node n, JSType type, String msg, JSType expectedType) { if (!type.isNoType() && !type.isUnknownType() && type.isSubtypeOf(nullOrUndefined) && !containsForwardDeclaredUnresolvedName(type)) { // There's one edge case right now that we don't handle well, and // that we don't want to warn about. // if (this.x == null) { // this.initializeX(); // this.x.foo(); // } // In this case, we incorrectly type x because of how we // infer properties locally. See issue 109. // http://blickly.github.io/closure-compiler-issues/#109 // // We do not do this inference globally. if (n.isGetProp() && !t.inGlobalScope() && type.isNullType()) { return true; } mismatch(n, msg, type, expectedType); return false; } return true; } private static boolean containsForwardDeclaredUnresolvedName(JSType type) { if (type.isUnionType()) { for (JSType alt : type.toMaybeUnionType().getAlternates()) { if (containsForwardDeclaredUnresolvedName(alt)) { return true; } } } return type.isNoResolvedType(); } /** Expect that the type of a switch condition matches the type of its case condition. */ void expectSwitchMatchesCase(Node n, JSType switchType, JSType caseType) { // ECMA-262, page 68, step 3 of evaluation of CaseBlock if (!switchType.canTestForShallowEqualityWith(caseType)) { mismatch(n.getFirstChild(), "case expression doesn't match switch", caseType, switchType); } } /** * Expect that the first type can be addressed with GETELEM syntax and that the second type is the * right type for an index into the first type. * * @param n The GETELEM or COMPUTED_PROP node to issue warnings on. * @param objType The type we're indexing into (the left side of the GETELEM). * @param indexType The type inside the brackets of the GETELEM/COMPUTED_PROP. */ void expectIndexMatch(Node n, JSType objType, JSType indexType) { checkState( n.isGetElem() || n.isOptChainGetElem() || n.isComputedProp() || n.isComputedFieldDef(), n); Node indexNode = n.isGetElem() ? n.getLastChild() : n.getFirstChild(); if (indexType.isSymbolValueType()) { // For now, allow symbols definitions/access on any type. In the future only allow them // on the subtypes for which they are defined. return; } if (objType.isUnknownType()) { expectStringOrNumberOrSymbol(indexNode, indexType, "property access"); return; } ObjectType dereferenced = objType.dereference(); if (dereferenced != null && dereferenced.getTemplateTypeMap().hasTemplateKey(typeRegistry.getObjectIndexKey())) { expectCanAssignTo( indexNode, indexType, dereferenced .getTemplateTypeMap() .getResolvedTemplateType(typeRegistry.getObjectIndexKey()), "restricted index type"); } else if (dereferenced != null && dereferenced.isArrayType()) { expectNumberOrSymbol(indexNode, indexType, "array access"); } else if (objType.isStruct()) { this.compiler.report(JSError.make(indexNode, ILLEGAL_PROPERTY_ACCESS, "'[]'", "struct")); } else if (objType.matchesObjectContext()) { expectStringOrSymbol(indexNode, indexType, "property access"); } else { mismatch( n, "only arrays or objects can be accessed", objType, typeRegistry.createUnionType(ARRAY_TYPE, OBJECT_TYPE)); } } /** * Expect that the first type can be assigned to a symbol of the second type. * * @param n The node to issue warnings on. * @param rightType The type on the RHS of the assign. * @param leftType The type of the symbol on the LHS of the assign. * @param owner The owner of the property being assigned to. * @param propName The name of the property being assigned to. * @return True if the types matched, false otherwise. */ boolean expectCanAssignToPropertyOf( Node n, JSType rightType, JSType leftType, Node owner, String propName) { Supplier typeNameSupplier; if (n.isMemberFieldDef()) { FunctionType classType = n.getGrandparent().getJSType().assertFunctionType(); typeNameSupplier = () -> classType.getInstanceType().toString(); } else { typeNameSupplier = () -> typeRegistry.getReadableTypeName(owner); } return expectCanAssignToPropertyOf( n, rightType, leftType, getJSType(owner), typeNameSupplier, propName); } /** * Expect that the first type can be assigned to a symbol of the second type. * * @param n The node to issue warnings on. * @param rightType The type on the RHS of the assign. * @param leftType The type of the symbol on the LHS of the assign. * @param ownerType The owner of the property being assigned to. * @param propName The name of the property being assigned to. * @return True if the types matched, false otherwise. */ boolean expectCanAssignToPropertyOf( Node n, JSType rightType, JSType leftType, JSType ownerType, Supplier typeNameSupplier, String propName) { if (leftType.isTemplateType()) { TemplateType left = leftType.toMaybeTemplateType(); if (rightType.containsReferenceAncestor(left) || rightType.isUnknownType() || left.isUnknownType()) { // The only time we can assign to a variable with a template type is if the value assigned // has a type that explicitly has it as a supertype. // Otherwise, the template type is existential and it is unknown whether or not it is a // proper super type. return true; } else { registerMismatchAndReport( n, TYPE_MISMATCH_WARNING, "assignment to property " + propName + " of " + typeNameSupplier.get(), rightType, leftType, new LinkedHashSet<>(), new LinkedHashSet<>()); return false; } } // The NoType check is a hack to make typedefs work OK. if (!leftType.isNoType() && !rightType.isSubtypeOf(leftType)) { // Do not type-check interface methods, because we expect that // they will have dummy implementations that do not match the type // annotations. if (ownerType.isFunctionPrototypeType()) { FunctionType ownerFn = ownerType.toObjectType().getOwnerFunction(); if (ownerFn.isInterface() && rightType.isFunctionType() && leftType.isFunctionType()) { return true; } } mismatch( n, "assignment to property " + propName + " of " + typeNameSupplier.get(), rightType, leftType); return false; } return true; } /** * Expect that the first type can be assigned to a symbol of the second type. * * @param n The node to issue warnings on. * @param rightType The type on the RHS of the assign. * @param leftType The type of the symbol on the LHS of the assign. * @param msg An extra message for the mismatch warning, if necessary. * @return True if the types matched, false otherwise. */ boolean expectCanAssignTo(Node n, JSType rightType, JSType leftType, String msg) { if (leftType.isTemplateType()) { TemplateType left = leftType.toMaybeTemplateType(); if (rightType.containsReferenceAncestor(left) || rightType.isUnknownType() || left.isUnknownType()) { // The only time we can assign to a variable with a template type is if the value assigned // has a type that explicitly has it as a supertype. // Otherwise, the template type is existential and it is unknown whether or not it is a // proper super type. return true; } else { registerMismatchAndReport( n, TYPE_MISMATCH_WARNING, msg, rightType, leftType, new LinkedHashSet<>(), new LinkedHashSet<>()); return false; } } if (!rightType.isSubtypeOf(leftType)) { mismatch(n, msg, rightType, leftType); return false; } return true; } /** * Expect that the type of an argument matches the type of the parameter that it's fulfilling. * * @param n The node to issue warnings on. * @param argType The type of the argument. * @param paramType The type of the parameter. * @param callNode The call node, to help with the warning message. * @param ordinal The argument ordinal, to help with the warning message. */ void expectArgumentMatchesParameter( Node n, JSType argType, JSType paramType, Node callNode, int ordinal) { if (!argType.isSubtypeOf(paramType)) { mismatch( n, "actual parameter " + ordinal + " of " + typeRegistry.getReadableTypeNameNoDeref(callNode.getFirstChild()) + " does not match formal parameter", argType, paramType); } } /** * Expect that the first type is the direct superclass of the second type. * * @param n The node where warnings should point to. * @param superObject The expected super instance type. * @param subObject The sub instance type. */ void expectSuperType(Node n, ObjectType superObject, ObjectType subObject) { FunctionType subCtor = subObject.getConstructor(); ObjectType implicitProto = subObject.getImplicitPrototype(); ObjectType declaredSuper = implicitProto == null ? null : implicitProto.getImplicitPrototype(); if (declaredSuper != null && declaredSuper.isTemplatizedType()) { declaredSuper = declaredSuper.toMaybeTemplatizedType().getReferencedType(); } if (declaredSuper != null && !(superObject instanceof UnknownType) && !declaredSuper.equals(superObject)) { if (declaredSuper.equals(getNativeType(OBJECT_TYPE))) { registerMismatchAndReport( superObject, declaredSuper, JSError.make(n, MISSING_EXTENDS_TAG_WARNING, subObject.toString())); } else { mismatch(n, "mismatch in declaration of superclass type", superObject, declaredSuper); } // Correct the super type. if (!subCtor.hasCachedValues()) { subCtor.setPrototypeBasedOn(superObject); } } } /** * Expect that an ES6 class's extends clause is actually a supertype of the given class. Compares * the registered supertype, which is taken from the JSDoc if present, otherwise from the AST, * with the type in the extends node of the AST. * * @param n The node where warnings should point to. * @param subCtor The sub constructor type. * @param astSuperCtor The expected super constructor from the extends node in the AST. */ void expectExtends(Node n, FunctionType subCtor, FunctionType astSuperCtor) { if (astSuperCtor == null || (!astSuperCtor.isConstructor() && !astSuperCtor.isInterface())) { // toMaybeFunctionType failed, or we've got a loose type. Let it go for now. return; } if (astSuperCtor.isConstructor() != subCtor.isConstructor()) { // Don't bother looking if one is a constructor and the other is an interface. // We'll report an error elsewhere. return; } ObjectType astSuperInstance = astSuperCtor.getInstanceType(); if (subCtor.isConstructor()) { // There should be exactly one superclass, and it needs to have this constructor. // Note: if the registered supertype (from the @extends jsdoc) was unresolved, // then getSuperClassConstructor will be null - make sure not to crash. FunctionType registeredSuperCtor = subCtor.getSuperClassConstructor(); if (registeredSuperCtor != null) { ObjectType registeredSuperInstance = registeredSuperCtor.getInstanceType(); if (!astSuperInstance.equals(registeredSuperInstance)) { mismatch( n, "mismatch in declaration of superclass type", astSuperInstance, registeredSuperInstance); } } } else if (subCtor.isInterface()) { // We intentionally skip this check for interfaces because they can extend multiple other // interfaces. } } /** * Expect that it's valid to assign something to a given type's prototype. * *

Most of these checks occur during TypedScopeCreator, so we just handle very basic cases here * *

For example, assuming `Foo` is a constructor, `Foo.prototype = 3;` will warn because `3` is * not an object. * * @param ownerType The type of the object whose prototype is being changed. (e.g. `Foo` above) * @param node Node to issue warnings on (e.g. `3` above) * @param rightType the rvalue type being assigned to the prototype (e.g. `number` above) */ void expectCanAssignToPrototype(JSType ownerType, Node node, JSType rightType) { if (ownerType.isFunctionType()) { FunctionType functionType = ownerType.toMaybeFunctionType(); if (functionType.isConstructor()) { expectObject(node, rightType, "cannot override prototype with non-object"); } } } /** * Expect that the first type can be cast to the second type. The first type must have some * relationship with the second. * * @param n The node where warnings should point. * @param targetType The type being cast to. * @param sourceType The type being cast from. */ void expectCanCast(Node n, JSType targetType, JSType sourceType) { if (!sourceType.canCastTo(targetType)) { registerMismatchAndReport( sourceType, targetType, JSError.make(n, INVALID_CAST, sourceType.toString(), targetType.toString())); // The "canCastTo" check is intentionally looser than the subtyping check, but we still want // to record potential mismatches for disambiguation safety. } else if (!sourceType.isSubtypeOf(targetType)) { this.mismatches.registerMismatch(n, sourceType, targetType); } } /** * Expect that the given variable has not been declared with a type. * * @param sourceName The name of the source file we're in. * @param n The node where warnings should point to. * @param parent The parent of {@code n}. * @param var The variable that we're checking. * @param variableName The name of the variable. * @param newType The type being applied to the variable. Mostly just here for the benefit of the * warning. * @return The variable we end up with. Most of the time, this will just be {@code var}, but in * some rare cases we will need to declare a new var with new source info. */ TypedVar expectUndeclaredVariable( String sourceName, CompilerInput input, Node n, Node parent, TypedVar var, String variableName, JSType newType) { TypedVar newVar = var; JSType varType = var.getType(); // Only report duplicate declarations that have types. Other duplicates // will be reported by the syntactic scope creator later in the // compilation process. if (varType != null && varType != typeRegistry.getNativeType(UNKNOWN_TYPE) && newType != null && newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) { // If there are two typed declarations of the same variable, that // is an error and the second declaration is ignored, except in the // case of native types. A null input type means that the declaration // was made in TypedScopeCreator#createInitialScope and is a // native type. We should redeclare it at the new input site. if (var.getInput() == null) { TypedScope s = var.getScope(); s.undeclare(var); newVar = s.declare(variableName, n, varType, input, false); n.setJSType(varType); if (parent.isVar()) { if (n.hasChildren()) { n.getFirstChild().setJSType(varType); } } else { checkState(parent.isFunction() || parent.isClass()); parent.setJSType(varType); } } else { // Check for @suppress duplicate or similar warnings guard on the previous variable // declaration location. boolean allowDupe = hasDuplicateDeclarationSuppression(compiler, var.getNameNode()); // If the previous definition doesn't suppress the warning, emit it here (i.e. always emit // on the second of the duplicate definitions). The warning might still be suppressed by an // @suppress tag on this declaration. if (!allowDupe) { // Report specifically if it is not just a duplicate, but types also don't mismatch. if (!newType.equals(varType)) { this.compiler.report( JSError.make( n, DUP_VAR_DECLARATION_TYPE_MISMATCH, variableName, newType.toString(), var.getInputName(), String.valueOf(var.getNameNode().getLineno()), varType.toString())); } else if (!var.getParentNode().isExprResult()) { // If the type matches and the previous declaration was a stub declaration // (isExprResult), then ignore the duplicate, otherwise emit an error. this.compiler.report( JSError.make( n, DUP_VAR_DECLARATION, variableName, var.getInputName(), String.valueOf(var.getNameNode().getLineno()))); } } } } return newVar; } /** * Expect that all properties on interfaces that this type implements are implemented and * correctly typed. */ void expectAllInterfaceProperties(Node n, FunctionType type) { ObjectType instance = type.getInstanceType(); for (ObjectType implemented : type.getAllImplementedInterfaces()) { expectInterfaceProperties(n, instance, implemented); } for (ObjectType extended : type.getExtendedInterfaces()) { expectInterfaceProperties(n, instance, extended); } } private void expectInterfaceProperties( Node n, ObjectType instance, ObjectType ancestorInterface) { // Case: `/** @interface */ class Foo { constructor() { this.prop; } }` for (String prop : ancestorInterface.getOwnPropertyNames()) { expectInterfaceProperty(n, instance, ancestorInterface, prop); } if (ancestorInterface.getImplicitPrototype() != null) { // Case: `/** @interface */ class Foo { prop() { } }` for (String prop : ancestorInterface.getImplicitPrototype().getOwnPropertyNames()) { expectInterfaceProperty(n, instance, ancestorInterface, prop); } } } /** * Expect that the property in an interface that this type implements is implemented and correctly * typed. */ private void expectInterfaceProperty( Node n, ObjectType instance, ObjectType implementedInterface, String propName) { OwnedProperty propSlot = instance.findClosestDefinition(propName); if (propSlot == null || (!instance.getConstructor().isInterface() && propSlot.isOwnedByInterface())) { if (instance.getConstructor().isAbstract() || instance.getConstructor().isInterface()) { // Abstract classes and interfaces are not required to implement interface properties. return; } if (implementedInterface.getPropertyType(propName).isVoidable()) { // Voidable properties don't require explicit initializaition in type constructors. return; } registerMismatchAndReport( instance, implementedInterface, JSError.make( n, INTERFACE_METHOD_NOT_IMPLEMENTED, propName, implementedInterface.getReferenceName(), instance.toString())); } else { boolean local = propSlot.getOwnerInstanceType().equals(instance); if (!local && instance.getConstructor().isInterface()) { // non-local interface mismatches already handled via interface property conflict checks. return; } Property prop = propSlot.getValue(); Node propNode = prop.getDeclaration() == null ? null : prop.getDeclaration().getNode(); // Fall back on the constructor node if we can't find a node for the property. propNode = propNode == null ? n : propNode; checkPropertyType(propNode, instance, implementedInterface, propName, prop.getType()); } } /** * Check the property is correctly typed (i.e. subtype of the parent property's type declaration). */ void checkPropertyType( Node n, JSType instance, ObjectType parent, String propertyName, JSType found) { JSType required = parent.getPropertyType(propertyName); TemplateTypeMap typeMap = instance.getTemplateTypeMap(); if (!typeMap.isEmpty() && required.hasAnyTemplateTypes()) { required = required.visit(TemplateTypeReplacer.forPartialReplacement(typeRegistry, typeMap)); } if (found.isSubtypeOf(required, this.subtypingMode)) { return; } // Implemented, but not correctly typed JSError err = JSError.make( n, parent.getConstructor().isInterface() ? HIDDEN_INTERFACE_PROPERTY_MISMATCH : HIDDEN_SUPERCLASS_PROPERTY_MISMATCH, propertyName, parent.getReferenceName(), required.toString(), found.toString(), instance.toString()); registerMismatchAndReport(found, required, err); } /** * For a concrete class, expect that all abstract methods that haven't been implemented by any of * the super classes on the inheritance chain are implemented. */ void expectAbstractMethodsImplemented(Node n, FunctionType ctorType) { checkArgument(ctorType.isConstructor()); Map abstractMethodSuperTypeMap = new LinkedHashMap<>(); FunctionType currSuperCtor = ctorType.getSuperClassConstructor(); if (currSuperCtor == null || !currSuperCtor.isAbstract()) { return; } while (currSuperCtor != null && currSuperCtor.isAbstract()) { ObjectType superType = currSuperCtor.getInstanceType(); for (String prop : currSuperCtor.getPrototype().getOwnPropertyNames()) { FunctionType maybeAbstractMethod = superType.findPropertyType(prop).toMaybeFunctionType(); if (maybeAbstractMethod != null && maybeAbstractMethod.isAbstract() && !abstractMethodSuperTypeMap.containsKey(prop)) { abstractMethodSuperTypeMap.put(prop, superType); } } currSuperCtor = currSuperCtor.getSuperClassConstructor(); } ObjectType instance = ctorType.getInstanceType(); for (var entry : abstractMethodSuperTypeMap.entrySet()) { String method = entry.getKey(); ObjectType superType = entry.getValue(); FunctionType abstractMethod = instance.findPropertyType(method).toMaybeFunctionType(); if (abstractMethod == null || abstractMethod.isAbstract()) { registerMismatchAndReport( instance, superType, JSError.make( n, ABSTRACT_METHOD_NOT_IMPLEMENTED, method, superType.toString(), instance.toString())); } } } private void mismatch(Node n, String msg, JSType found, JSTypeNative required) { mismatch(n, msg, found, getNativeType(required)); } private void mismatch(Node n, String msg, JSType found, JSType required) { if (!found.isSubtypeOf(required, this.subtypingMode)) { Set missing = null; Set mismatch = null; JSType structuralType = null; if (required.isStructuralType()) { structuralType = required; } else if (required.isUnionType()) { JSType restrictedType = required.restrictByNotNullOrUndefined(); if (restrictedType.isStructuralType()) { // restrict to unions created by undefined `{!Type=}` type or by nullable `{?Type}` type. structuralType = restrictedType; } } if (structuralType != null) { missing = new TreeSet<>(); mismatch = new TreeSet<>(); ObjectType requiredObject = structuralType.toMaybeObjectType(); ObjectType foundObject = found.toMaybeObjectType(); if (requiredObject != null && foundObject != null) { for (String property : requiredObject.getPropertyNames()) { JSType propRequired = requiredObject.getPropertyType(property); boolean hasProperty = foundObject.hasProperty(property); if (!propRequired.isExplicitlyVoidable() || hasProperty) { if (hasProperty) { if (!foundObject .getPropertyType(property) .isSubtypeOf(propRequired, subtypingMode)) { mismatch.add(property); } } else { missing.add(property); } } } } } registerMismatchAndReport(n, TYPE_MISMATCH_WARNING, msg, found, required, missing, mismatch); } } /** Used both for TYPE_MISMATCH_WARNING and INVALID_OPERAND_TYPE. */ private void registerMismatchAndReport( Node n, DiagnosticType diagnostic, String msg, JSType found, JSType required, @Nullable Set missing, @Nullable Set mismatch) { String foundRequiredFormatted = formatFoundRequired(msg, found, required, missing, mismatch); JSError err = JSError.make(n, diagnostic, foundRequiredFormatted); registerMismatchAndReport(found, required, err); } /** Registers a type mismatch into the universe of mismatches owned by this pass. */ private void registerMismatchAndReport(JSType found, JSType required, JSError error) { this.compiler.report(error); this.mismatches.registerMismatch(error.getNode(), found, required); } /** Formats a found/required error message. */ private static String formatFoundRequired( String description, JSType found, JSType required, Set missing, Set mismatch) { String foundStr = found.toString(); String requiredStr = required.toString(); if (foundStr.equals(requiredStr)) { foundStr = found.toAnnotationString(Nullability.IMPLICIT); requiredStr = required.toAnnotationString(Nullability.IMPLICIT); } String missingStr = ""; String mismatchStr = ""; if (missing != null && !missing.isEmpty()) { missingStr = Joiner.on(",").join(missing); } if (mismatch != null && !mismatch.isEmpty()) { mismatchStr = Joiner.on(",").join(mismatch); } if (missingStr.length() > 0 || mismatchStr.length() > 0) { return Platform.formatMessage( FOUND_REQUIRED_MISSING, description, foundStr, requiredStr, missingStr, mismatchStr); } else { return Platform.formatMessage(FOUND_REQUIRED, description, foundStr, requiredStr); } } /** This method gets the JSType from the Node argument and verifies that it is present. */ private JSType getJSType(Node n) { return checkNotNull(n.getJSType(), "%s has no JSType attached", n); } private JSType getNativeType(JSTypeNative typeId) { return typeRegistry.getNativeType(typeId); } /** * @param decl The declaration to check. * @return Whether duplicated declarations warnings should be suppressed for the given node. */ static boolean hasDuplicateDeclarationSuppression(AbstractCompiler compiler, Node decl) { // NB: DUP_VAR_DECLARATION is somewhat arbitrary here, but it must be one of the errors // suppressed by the "duplicate" group. CheckLevel originalDeclLevel = compiler.getErrorLevel(JSError.make(decl, DUP_VAR_DECLARATION, "dummy", "dummy")); return originalDeclLevel == CheckLevel.OFF; } void expectWellFormedTemplatizedType(Node n) { WellFormedTemplatizedTypeVerifier verifier = new WellFormedTemplatizedTypeVerifier(n); if (n.getJSType() != null) { n.getJSType().visit(verifier); } } final class WellFormedTemplatizedTypeVerifier extends Visitor.WithDefaultCase { Node node; WellFormedTemplatizedTypeVerifier(Node node) { this.node = node; } @Override protected Boolean caseDefault(@Nullable JSType type) { return true; } @Override public Boolean caseEnumElementType(EnumElementType type) { return type.getPrimitiveType() == null || type.getPrimitiveType().visit(this); } @Override public Boolean caseFunctionType(FunctionType type) { for (FunctionType.Parameter param : type.getParameters()) { if (!param.getJSType().visit(this)) { return false; } } return type.getReturnType().visit(this); } @Override public Boolean caseNamedType(NamedType type) { return type.getReferencedType().visit(this); } @Override public Boolean caseUnionType(UnionType type) { // Avoid iterators in very hot code. ImmutableList alternates = type.getAlternates(); int alternateCount = alternates.size(); for (int i = 0; i < alternateCount; i++) { var alternative = alternates.get(i); if (!alternative.visit(this)) { return false; } } return true; } @Override public Boolean caseTemplatizedType(TemplatizedType type) { ImmutableList referencedTemplates = type.getReferencedType().getTemplateTypeMap().getTemplateKeys(); for (int i = 0; i < type.getTemplateTypes().size(); i++) { JSType assignedType = type.getTemplateTypes().get(i); if (i < referencedTemplates.size()) { TemplateType templateType = referencedTemplates.get(i); if (!assignedType.isSubtype(templateType.getBound())) { registerMismatchAndReport( assignedType, templateType.getBound(), JSError.make( node, RhinoErrorReporter.BOUNDED_GENERIC_TYPE_ERROR, assignedType.toString(), templateType.getReferenceName(), templateType.getBound().toString())); return false; } } } return true; } @Override public Boolean caseTemplateType(TemplateType templateType) { return !templateType.containsCycle() && templateType.getBound().visit(this); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy