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

com.google.javascript.jscomp.TypeValidator Maven / Gradle / Ivy

/*
 * 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.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;
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.javascript.jscomp.JsIterables.MaybeBoxedIterableOrAsyncIterable;
import com.google.javascript.jscomp.parsing.parser.util.format.SimpleFormat;
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.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import javax.annotation.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;

  // TODO(nicksantos): Provide accessors to better filter the list of type
  // mismatches. For example, if we pass (Cake|null) where only Cake is
  // allowed, that doesn't mean we should invalidate all Cakes.
  private final List mismatches = new ArrayList<>();
  // the detection logic of this one is similar to this.mismatches
  private final List implicitInterfaceUses = new ArrayList<>();

  // 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 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 mismatches;
  }

  void setSubtypingMode(SubtypingMode mode) {
    this.subtypingMode = mode;
  }

  /**
   * all uses of implicitly implemented interfaces,
   * captured during type validation and type checking
   * (uses of explicitly @implemented structural interfaces are excluded)
   */
  public Iterable getImplicitInterfaceUses() {
    return implicitInterfaceUses;
  }

  // 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) {
    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) {
    MaybeBoxedIterableOrAsyncIterable 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()); registerMismatch(type, promiseOfUnknownType, err); report(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 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 expectStringOrNumber(Node n, JSType type, String msg) { if (!type.matchesNumberContext() && !type.matchesStringContext() && !type.matchesStringContext()) { mismatch(n, msg, type, NUMBER_STRING); } else { expectStringOrNumberOrSymbolStrict(n, type, msg); } } void expectStringOrNumberStrict(Node n, JSType type, String msg) { if (!type.isSubtypeOf(getNativeType(NUMBER_STRING))) { registerMismatchAndReport( n, INVALID_OPERAND_TYPE, msg, type, getNativeType(NUMBER_STRING), null, null); } } /** * 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 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 t The node traversal. * @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.isComputedProp(), 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()) { 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 t The node traversal. * @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) { 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 " + typeRegistry.getReadableTypeName(owner), rightType, leftType, new HashSet<>(), new HashSet<>()); 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. JSType ownerType = getJSType(owner); if (ownerType.isFunctionPrototypeType()) { FunctionType ownerFn = ownerType.toObjectType().getOwnerFunction(); if (ownerFn.isInterface() && rightType.isFunctionType() && leftType.isFunctionType()) { return true; } } mismatch( n, "assignment to property " + propName + " of " + typeRegistry.getReadableTypeName(owner), rightType, leftType); return false; } else if (!leftType.isNoType() && !rightType.isSubtypeWithoutStructuralTyping(leftType)){ TypeMismatch.recordImplicitInterfaceUses(this.implicitInterfaceUses, n, rightType, leftType); TypeMismatch.recordImplicitUseOfNativeObject(this.mismatches, n, rightType, leftType); } return true; } /** * Expect that the first type can be assigned to a symbol of the second type. * * @param t The node traversal. * @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 HashSet<>(), new HashSet<>()); return false; } } if (!rightType.isSubtypeOf(leftType)) { mismatch(n, msg, rightType, leftType); return false; } else if (!rightType.isSubtypeWithoutStructuralTyping(leftType)) { TypeMismatch.recordImplicitInterfaceUses(this.implicitInterfaceUses, n, rightType, leftType); TypeMismatch.recordImplicitUseOfNativeObject(this.mismatches, n, rightType, leftType); } return true; } /** * Expect that the type of an argument matches the type of the parameter that it's fulfilling. * * @param t The node traversal. * @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, SimpleFormat.format( "actual parameter %d of %s does not match formal parameter", ordinal, typeRegistry.getReadableTypeNameNoDeref(callNode.getFirstChild())), argType, paramType); } else if (!argType.isSubtypeWithoutStructuralTyping(paramType)){ TypeMismatch.recordImplicitInterfaceUses(this.implicitInterfaceUses, n, argType, paramType); TypeMismatch.recordImplicitUseOfNativeObject(this.mismatches, n, argType, paramType); } } /** * Expect that the first type is the direct superclass of the second type. * * @param t The node traversal. * @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.isEquivalentTo(superObject)) { if (declaredSuper.isEquivalentTo(getNativeType(OBJECT_TYPE))) { registerMismatch( superObject, declaredSuper, report(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.isEquivalentTo(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 t The node traversal. * @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)) { registerMismatch( sourceType, targetType, report(JSError.make(n, INVALID_CAST, sourceType.toString(), targetType.toString()))); } else if (!sourceType.isSubtypeWithoutStructuralTyping(targetType)){ TypeMismatch.recordImplicitInterfaceUses( this.implicitInterfaceUses, 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.input == 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. // NOTE: structural matches are explicitly allowed here. if (!newType.isEquivalentTo(varType, true)) { report( JSError.make( n, DUP_VAR_DECLARATION_TYPE_MISMATCH, variableName, newType.toString(), var.getInputName(), String.valueOf(var.nameNode.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. report( JSError.make( n, DUP_VAR_DECLARATION, variableName, var.getInputName(), String.valueOf(var.nameNode.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; } registerMismatch( instance, implementedInterface, report( 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.isSubtype(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()); registerMismatch(found, required, err); report(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 (Map.Entry entry : abstractMethodSuperTypeMap.entrySet()) { String method = entry.getKey(); ObjectType superType = entry.getValue(); FunctionType abstractMethod = instance.findPropertyType(method).toMaybeFunctionType(); if (abstractMethod == null || abstractMethod.isAbstract()) { registerMismatch( instance, superType, report( 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.isSubtype(required, this.subtypingMode)) { Set missing = null; Set mismatch = null; if (required.isStructuralType()) { missing = new TreeSet<>(); mismatch = new TreeSet<>(); ObjectType requiredObject = required.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) .isSubtype(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, Set missing, Set mismatch) { String foundRequiredFormatted = formatFoundRequired(msg, found, required, missing, mismatch); JSError err = JSError.make(n, diagnostic, foundRequiredFormatted); registerMismatch(found, required, err); report(err); } /** Registers a type mismatch into the universe of mismatches owned by this pass. */ private void registerMismatch(JSType found, JSType required, JSError error) { TypeMismatch.registerMismatch( this.mismatches, this.implicitInterfaceUses, found, required, error); } /** 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 MessageFormat.format( FOUND_REQUIRED_MISSING, description, foundStr, requiredStr, missingStr, mismatchStr); } else { return MessageFormat.format(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); } private JSError report(JSError error) { compiler.report(error); return error; } /** * @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) : true; } @Override public Boolean caseFunctionType(FunctionType type) { for (JSType param : type.getParameterTypes()) { if (!param.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) { for (JSType alt : type.getAlternates()) { if (!alt.visit(this)) { return false; } } return true; } @Override public Boolean caseTemplatizedType(TemplatizedType type) { List 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())) { registerMismatch( assignedType, templateType.getBound(), report( 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