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

com.google.javascript.jscomp.InvalidatingTypes 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 2017 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.checkState;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.LinkedHashSet;
import javax.annotation.Nullable;

/**
 * Keeps track of "invalidating types" that force type-based optimizations to back off, specifically
 * for {@link InlineProperties}, {@link
 * com.google.javascript.jscomp.disambiguate.AmbiguateProperties}, and {@link
 * com.google.javascript.jscomp.disambiguate.DisambiguateProperties}. Note that disambiguation has
 * slightly different behavior from the other two, as pointed out in implementation comments.
 */
public final class InvalidatingTypes {
  private final ImmutableSet types;
  /** Whether to allow disambiguating enum properties */
  private final boolean allowEnums;
  /** Whether to allow types like 'str'.toString() */
  private final boolean allowScalars;

  private InvalidatingTypes(Builder builder, ImmutableSet types) {
    this.types = types;
    this.allowEnums = builder.allowEnums;
    this.allowScalars = builder.allowScalars;
  }

  public boolean isInvalidating(JSType type) {
    if (type == null || type.isUnknownType() || type.isEmptyType()) {
      return true;
    }

    // A union type is invalidating if any one of its members is invalidating
    if (type.isUnionType()) {
      type = type.restrictByNotNullOrUndefined();
      if (type.isUnionType()) {
        for (JSType alt : type.getUnionMembers()) {
          if (isInvalidating(alt)) {
            return true;
          }
        }
        return false;
      }
    }

    ObjectType objType = type.toMaybeObjectType();

    if (objType == null) {
      return !allowScalars;
    }

    return types.contains(objType)
        // Don't disambiguate properties on object literals, e.g. var obj = {a: 'a', b: 'b'};
        || this.isInvalidatingDueToAmbiguity(objType)
        || (!allowEnums && objType.isEnumType())
        || (!allowScalars && objType.isBoxableScalar());
  }

  private boolean isInvalidatingDueToAmbiguity(ObjectType type) {
    return type.isAmbiguousObject();
  }

  /** Builder */
  public static final class Builder {
    private final JSTypeRegistry registry;

    @Nullable private Multimap invalidationMap;
    private final LinkedHashSet mismatches = new LinkedHashSet<>();
    private boolean allowEnums = false;
    private boolean allowScalars = false;

    // TODO(b/160269908): Investigate making this always false, instead of always true.
    private final boolean alsoInvalidateRelatedTypes = true;

    private ImmutableSet.Builder types;

    public Builder(JSTypeRegistry registry) {
      this.registry = registry;
    }

    public InvalidatingTypes build() {
      checkState(this.types == null);
      this.types = ImmutableSet.builder();
      types.add(
          registry.getNativeType(JSTypeNative.FUNCTION_FUNCTION_TYPE),
          registry.getNativeType(JSTypeNative.FUNCTION_TYPE),
          registry.getNativeType(JSTypeNative.FUNCTION_PROTOTYPE),
          registry.getNativeType(JSTypeNative.OBJECT_TYPE),
          registry.getNativeType(JSTypeNative.OBJECT_PROTOTYPE),
          registry.getNativeType(JSTypeNative.OBJECT_FUNCTION_TYPE));

      for (TypeMismatch mismatch : this.mismatches) {
        this.addTypeWithReason(mismatch.getFound(), mismatch.getLocation());
        this.addTypeWithReason(mismatch.getRequired(), mismatch.getLocation());
      }

      ImmutableSet types = this.types.build();
      this.types = null;
      return new InvalidatingTypes(this, types);
    }

    public Builder writeInvalidationsInto(@Nullable Multimap invalidationMap) {
      this.invalidationMap = invalidationMap;
      return this;
    }

    // TODO(sdh): Investigate whether this can be consolidated between all three passes.
    // In particular, mutation testing suggests allowEnums=true should work everywhere.
    // We should revisit what breaks when we disallow scalars everywhere.
    public Builder allowEnums() {
      // Ambiguate and Inline do not allow enums
      this.allowEnums = true;
      return this;
    }

    public Builder allowScalars() {
      // Ambiguate and Inline do not allow scalars.
      this.allowScalars = true;
      return this;
    }

    public Builder addAllTypeMismatches(Iterable mismatches) {
      mismatches.forEach(this.mismatches::add);
      return this;
    }

    /** Invalidates the given type, so that no properties on it will be inlined or renamed. */
    private void addTypeWithReason(JSType type, Node location) {
      type = type.restrictByNotNullOrUndefined();

      if (type.isUnionType()) {
        for (JSType alt : type.getUnionMembers()) {
          this.addTypeWithReason(alt, location);
        }
        return;
      }
      checkState(!type.isUnionType(), type);

      if (!this.alsoInvalidateRelatedTypes) {
        this.recordTypeWithReason(type, location);
      } else if (type.isEnumElementType()) {
        // Only in disambigation.
        this.recordTypeWithReason(type.getEnumeratedTypeOfEnumElement(), location);
        return;
      } else {
        // Ambiguation and InlineProperties both do this.
        this.recordTypeWithReason(type, location);

        ObjectType objType = type.toMaybeObjectType();
        if (objType == null) {
          return;
        }

        this.recordTypeWithReason(objType.getImplicitPrototype(), location);

        if (objType.isConstructor()) {
          // TODO(b/142431852): This should never be null but it is possible.
          // Case: `function(new:T)`, `T = number`.
          this.recordTypeWithReason(objType.toMaybeFunctionType().getInstanceType(), location);
        } else if (objType.isInstanceType()) {
          this.recordTypeWithReason(objType.getConstructor(), location);
        }
      }
    }

    private void recordTypeWithReason(JSType type, Node location) {
      if (type == null || !type.isObjectType()) {
        return;
      }

      this.types.add(type);
      if (invalidationMap != null) {
        this.invalidationMap.put(type, location);
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy