com.google.javascript.jscomp.newtypes.Property Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-compiler Show documentation
Show all versions of closure-compiler 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 2013 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.newtypes;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.Multimap;
import com.google.javascript.rhino.Node;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
*
* @author [email protected] (Ben Lickly)
* @author [email protected] (Dimitris Vardoulakis)
*/
class Property implements Serializable {
// AST node where the property is defined; most commonly null.
// TODO(dimvar): if having this field on every property wastes memory,
// consider alternatives:
// 1) Subclass Property for the few ones that have jsdoc.
// 2) Store the jsdocs scattered in NominalType and Namespace.
// This is hard b/c for objlit namespaces, we don't keep around the
// Namespace class in NTI; they're plain-old object literals.
private final Node defSite;
private final JSType inferredType;
private final JSType declaredType;
// Attributes are ordered: constant <= required <= optional
private enum Attribute {
CONSTANT, // For required props only
OPTIONAL,
REQUIRED;
}
private final Attribute attribute;
private Property(Node defSite, JSType inferredType, JSType declaredType, Attribute attribute) {
checkArgument(inferredType != null);
this.defSite = defSite;
this.inferredType = inferredType;
this.declaredType = declaredType;
this.attribute = attribute;
}
static Property make(JSType inferredType, JSType declaredType) {
return new Property(null, inferredType, declaredType, Attribute.REQUIRED);
}
static Property makeWithDefsite(Node defSite, JSType inferredType, JSType declaredType) {
return new Property(defSite, inferredType, declaredType, Attribute.REQUIRED);
}
static Property makeConstant(Node defSite, JSType inferredType, JSType declaredType) {
return new Property(defSite, inferredType, declaredType, Attribute.CONSTANT);
}
static Property makeOptional(Node defSite, JSType inferredType, JSType declaredType) {
return new Property(defSite, inferredType, declaredType, Attribute.OPTIONAL);
}
boolean isRequired() {
return attribute == Attribute.REQUIRED;
}
boolean isOptional() {
return attribute == Attribute.OPTIONAL;
}
boolean isConstant() {
return attribute == Attribute.CONSTANT;
}
boolean isDeclared() {
return declaredType != null;
}
JSType getType() {
return inferredType;
}
Node getDefSite() {
return this.defSite;
}
JSType getDeclaredType() {
return declaredType;
}
Property withOptional() {
return isOptional() ? this
: new Property(this.defSite, this.inferredType, this.declaredType, Attribute.OPTIONAL);
}
Property withRequired() {
return isRequired() ? this
: new Property(this.defSite, this.inferredType, this.declaredType, Attribute.REQUIRED);
}
Property withNewType(JSType newType) {
return new Property(
this.defSite, newType, this.declaredType == null ? null : newType, this.attribute);
}
private static Attribute meetAttributes(Attribute a1, Attribute a2) {
if (a1 == Attribute.CONSTANT || a2 == Attribute.CONSTANT) {
return Attribute.CONSTANT;
}
if (a1 == Attribute.REQUIRED || a2 == Attribute.REQUIRED) {
return Attribute.REQUIRED;
}
return Attribute.OPTIONAL;
}
private static Attribute joinAttributes(Attribute a1, Attribute a2) {
if (a1 == Attribute.OPTIONAL || a2 == Attribute.OPTIONAL) {
return Attribute.OPTIONAL;
}
if (a1 == Attribute.REQUIRED || a2 == Attribute.REQUIRED) {
return Attribute.REQUIRED;
}
return Attribute.CONSTANT;
}
Property specialize(Property other) {
// TODO(dimvar): consider not always creating a new property here; only
// when you can actually specialize.
return new Property(
this.defSite,
this.inferredType.specialize(other.inferredType),
this.declaredType,
meetAttributes(this.attribute, other.attribute));
}
static Property meet(Property p1, Property p2) {
return new Property(
p1.defSite == p2.defSite ? p1.defSite : null,
JSType.meet(p1.inferredType, p2.inferredType),
null,
meetAttributes(p1.attribute, p2.attribute));
}
static Property join(Property p1, Property p2) {
JSType declType, p1decl = p1.declaredType, p2decl = p2.declaredType;
if (p1decl == null || p2decl == null) {
declType = null;
} else if (p1decl.equals(p2decl)) {
declType = p1decl;
} else {
declType = null;
}
return new Property(
p1.defSite == p2.defSite ? p1.defSite : null,
JSType.join(p1.inferredType, p2.inferredType),
declType,
joinAttributes(p1.attribute, p2.attribute));
}
/**
* Unify the two types bidirectionally, ignoring type variables, but
* treating JSType.UNKNOWN as a "hole" to be filled.
* @return The unified type, or null if unification fails
*/
static Property unifyUnknowns(Property p1, Property p2) {
JSType unifiedDeclaredType = null;
if (p1.declaredType != null && p2.declaredType != null) {
unifiedDeclaredType =
JSType.unifyUnknowns(p1.declaredType, p2.declaredType);
if (unifiedDeclaredType == null) {
return null;
}
}
JSType unifiedInferredType =
JSType.unifyUnknowns(p1.inferredType, p2.inferredType);
if (unifiedInferredType == null) {
return null;
}
return new Property(
p1.defSite == p2.defSite ? p1.defSite : null,
unifiedInferredType, unifiedDeclaredType,
meetAttributes(p1.attribute, p2.attribute));
}
/** Returns whether unification succeeded */
boolean unifyWithSubtype(Property other, List typeParameters,
Multimap typeMultimap, SubtypeCache subSuperMap) {
if (!inferredType.unifyWithSubtype(
other.inferredType, typeParameters, typeMultimap, subSuperMap)) {
return false;
} else if (declaredType != null && other.declaredType != null &&
!declaredType.unifyWithSubtype(
other.declaredType, typeParameters, typeMultimap, subSuperMap)) {
return false;
}
return true;
}
Property substituteGenerics(Map concreteTypes) {
if (concreteTypes.isEmpty()) {
return this;
}
return new Property(
this.defSite,
inferredType.substituteGenerics(concreteTypes),
declaredType == null ?
null : declaredType.substituteGenerics(concreteTypes),
attribute);
}
@Override
public String toString() {
return appendTo(new StringBuilder(), ToStringContext.TO_STRING).toString();
}
public StringBuilder appendTo(StringBuilder builder, ToStringContext ctx) {
switch (attribute) {
case CONSTANT:
return inferredType.appendTo(builder, ctx).append('^');
case REQUIRED:
return inferredType.appendTo(builder, ctx);
case OPTIONAL:
return inferredType.appendTo(builder, ctx).append('=');
default:
throw new RuntimeException("Unknown Attribute value " + attribute);
}
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Property)) {
return false;
}
if (this == o) {
return true;
}
Property p2 = (Property) o;
return inferredType.equals(p2.inferredType) &&
attribute == p2.attribute;
}
@Override
public int hashCode() {
return Objects.hash(inferredType, attribute);
}
}