com.google.javascript.jscomp.InvalidatingTypes Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-compiler-unshaded Show documentation
Show all versions of closure-compiler-unshaded Show documentation
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.
/*
* 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.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
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 javax.annotation.Nullable;
/**
* Keeps track of "invalidating types" that force type-based optimizations to back off, specifically
* for {@link InlineProperties}, {@link AmbiguateProperties}, and {@link DisambiguateProperties}.
* Note that disambiguation has slightly different behavior from the other two, as pointed out in
* implementation comments.
*/
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) {
this.types = builder.types.build();
this.allowEnums = builder.allowEnums;
this.allowScalars = builder.allowScalars;
}
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'};
|| objType.isAmbiguousObject()
|| (!allowEnums && objType.isEnumType())
|| (!allowScalars && objType.isBoxableScalar());
}
static final class Builder {
private final ImmutableSet.Builder types = ImmutableSet.builder();
private final JSTypeRegistry registry;
private boolean allowEnums = false;
private boolean allowScalars = false;
@Nullable private Multimap> invalidationMap;
Builder(JSTypeRegistry registry) {
this.registry = registry;
}
InvalidatingTypes build() {
return new InvalidatingTypes(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.
Builder writeInvalidationsInto(@Nullable Multimap> invalidationMap) {
this.invalidationMap = invalidationMap;
return this;
}
Builder allowEnumsAndScalars() {
// Ambiguate and Inline do not allow enums or scalars.
this.allowEnums = this.allowScalars = true;
return this;
}
Builder disallowGlobalThis() {
// Disambiguate does not invalidate global this because it
// sets skipping explicitly for extern properties only on
// the extern types.
types.add(registry.getNativeType(JSTypeNative.GLOBAL_THIS));
return this;
}
Builder addAllTypeMismatches(Iterable mismatches) {
for (TypeMismatch mis : mismatches) {
addType(mis.typeA, mis);
addType(mis.typeB, mis);
}
return this;
}
Builder addTypesInvalidForPropertyRenaming() {
types.addAll(
ImmutableList.of(
registry.getNativeType(JSTypeNative.FUNCTION_FUNCTION_TYPE),
registry.getNativeType(JSTypeNative.U2U_CONSTRUCTOR_TYPE),
registry.getNativeType(JSTypeNative.FUNCTION_PROTOTYPE),
registry.getNativeType(JSTypeNative.OBJECT_TYPE),
registry.getNativeType(JSTypeNative.OBJECT_PROTOTYPE),
registry.getNativeType(JSTypeNative.OBJECT_FUNCTION_TYPE)));
return this;
}
/** Invalidates the given type, so that no properties on it will be inlined or renamed. */
private Builder addType(JSType type, TypeMismatch mismatch) {
type = type.restrictByNotNullOrUndefined();
if (type.isUnionType()) {
for (JSType alt : type.getUnionMembers()) {
addType(alt, mismatch);
}
} else if (type.isEnumElementType()) { // only in disamb
addType(type.getEnumeratedTypeOfEnumElement(), mismatch);
} else { // amb and inl both do this without the else
checkState(!type.isUnionType());
types.add(type);
recordInvalidation(type, mismatch);
ObjectType objType = type.toMaybeObjectType();
if (objType != null) {
ObjectType proto = objType.getImplicitPrototype();
if (proto != null) {
types.add(proto);
recordInvalidation(proto, mismatch);
}
if (objType.isConstructor()) {
ObjectType instanceType = objType.toMaybeFunctionType().getInstanceType();
if (instanceType != null) {
// TODO(b/142431852): This shouldn't be possible.
// Case: `function(new:T)`, `T = number`.
types.add(instanceType);
}
} else if (objType.isInstanceType()) {
types.add(objType.getConstructor());
}
}
}
return this;
}
private void recordInvalidation(JSType type, TypeMismatch mis) {
if (!type.isObjectType()) {
return;
}
if (invalidationMap != null) {
invalidationMap.put(type, mis.error);
}
}
}
}