static ImmutableSet<ObjectType> withLooseObjects(Set<ObjectType> objs) { ImmutableSet.Builder<ObjectType> newObjs = ImmutableSet.builder(); for (ObjectType obj : objs) { newObjs.add(obj.withLoose()); } return newObjs.build(); }
static ImmutableSet<ObjectType> withoutProperty(Set<ObjectType> objs, QualifiedName qname) { ImmutableSet.Builder<ObjectType> newObjs = ImmutableSet.builder(); for (ObjectType obj : objs) { newObjs.add(obj.withProperty(qname, null)); } return newObjs.build(); }
static ImmutableSet<ObjectType> withPropertyRequired(Set<ObjectType> objs, String pname) { ImmutableSet.Builder<ObjectType> newObjs = ImmutableSet.builder(); for (ObjectType obj : objs) { newObjs.add(obj.withPropertyRequired(pname)); } return newObjs.build(); }
// We never infer properties as optional on loose objects, // and we don't warn about possibly inexistent properties. boolean isLooseSubtypeOf(ObjectType other) { Preconditions.checkState(isLoose || other.isLoose); if (other == TOP_OBJECT) { return true; } if (!isLoose) { if (!objectKind.isSubtypeOf(other.objectKind)) { return false; } for (String pname : other.props.keySet()) { QualifiedName qname = new QualifiedName(pname); if (!mayHaveProp(qname) || !getProp(qname).isSubtypeOf(other.getProp(qname))) { return false; } } } else { // this is loose, other may be loose for (String pname : props.keySet()) { QualifiedName qname = new QualifiedName(pname); if (other.mayHaveProp(qname) && !getProp(qname).isSubtypeOf(other.getProp(qname))) { return false; } } } if (other.fn == null) { return this.fn == null || other.isLoose(); } else if (this.fn == null) { return isLoose; } return fn.isLooseSubtypeOf(other.fn); }
static ObjectType join(ObjectType obj1, ObjectType obj2) { if (obj1 == TOP_OBJECT || obj2 == TOP_OBJECT) { return TOP_OBJECT; } NominalType nom1 = obj1.nominalType; NominalType nom2 = obj2.nominalType; Preconditions.checkState(areRelatedClasses(nom1, nom2)); if (obj1.equals(obj2)) { return obj1; } boolean isLoose = obj1.isLoose || obj2.isLoose; FunctionType fn = FunctionType.join(obj1.fn, obj2.fn); PersistentMap<String, Property> props; if (isLoose) { fn = fn == null ? null : fn.withLoose(); props = joinPropsLoosely(obj1.props, obj2.props); } else { props = joinProps(obj1.props, obj2.props, nom1, nom2); } NominalType nominal = NominalType.pickSuperclass(nom1, nom2); // TODO(blickly): Split TOP_OBJECT from empty object and remove this case if (nominal == null || !nominal.isFunction()) { fn = null; } return ObjectType.makeObjectType( nominal, props, fn, isLoose, ObjectKind.join(obj1.objectKind, obj2.objectKind)); }
/** * Takes a type tag with a single bit set (including the non-scalar bit), and prints the string * representation of that single type. */ private static String tagToString(int tag, Set<ObjectType> objs, String T) { switch (tag) { case TRUE_MASK: case FALSE_MASK: return "boolean"; case BOTTOM_MASK: return "bottom"; case STRING_MASK: return "string"; case NON_SCALAR_MASK: Set<String> strReps = Sets.newHashSet(); for (ObjectType obj : objs) { strReps.add(obj.toString()); } return Joiner.on("|").join(strReps); case NULL_MASK: return "null"; case NUMBER_MASK: return "number"; case TOP_MASK: return "top"; case UNDEFINED_MASK: return "undefined"; case UNKNOWN_MASK: return "?"; case TYPEVAR_MASK: return T; default: // Must be a union type. return null; } }
static ImmutableSet<ObjectType> withDeclaredProperty( Set<ObjectType> objs, QualifiedName qname, JSType type, boolean isConstant) { ImmutableSet.Builder<ObjectType> newObjs = ImmutableSet.builder(); for (ObjectType obj : objs) { newObjs.add(obj.withPropertyHelper(qname, type, true, isConstant)); } return newObjs.build(); }
public boolean hasProp(QualifiedName qname) { if (objs == null) { return false; } for (ObjectType o : objs) { if (!o.hasProp(qname)) { return false; } } return true; }
public boolean isDict() { if (objs == null) { return false; } for (ObjectType objType : objs) { if (objType.isDict()) { return true; } } return false; }
public boolean hasConstantProp(QualifiedName pname) { Preconditions.checkArgument(pname.isIdentifier()); if (objs == null) { return false; } for (ObjectType obj : objs) { if (obj.hasConstantProp(pname)) { return true; } } return false; }
public FunctionType getFunType() { if (objs == null) { return null; } if (objs.size() == 1) { // The common case is fast return Iterables.getOnlyElement(objs).getFunType(); } FunctionType result = FunctionType.TOP_FUNCTION; for (ObjectType obj : objs) { result = FunctionType.meet(result, obj.getFunType()); } return result; }
// True iff there exists a value that can have this type public boolean isInhabitable() { if (isBottom()) { return false; } else if (objs == null) { return true; } for (ObjectType obj : objs) { if (!obj.isInhabitable()) { return false; } } return true; }
public JSType getDeclaredProp(QualifiedName qname) { if (isUnknown()) { return UNKNOWN; } Preconditions.checkState(objs != null, "Cannot get declared prop %s of type %s", qname, this); JSType ptype = BOTTOM; for (ObjectType o : objs) { JSType declType = o.getDeclaredProp(qname); if (declType != null) { ptype = join(ptype, declType); } } return ptype.isBottom() ? null : ptype; }
public boolean isLooseStruct() { if (objs == null) { return false; } boolean foundLooseStruct = false; boolean foundNonLooseStruct = false; for (ObjectType objType : objs) { if (objType.isLooseStruct()) { foundLooseStruct = true; } else if (objType.isStruct()) { foundNonLooseStruct = true; } } return foundLooseStruct && !foundNonLooseStruct; }
public JSType getProp(QualifiedName qname) { if (isBottom() || isUnknown()) { return UNKNOWN; } Preconditions.checkState(objs != null); JSType ptype = BOTTOM; for (ObjectType o : objs) { if (o.mayHaveProp(qname)) { ptype = join(ptype, o.getProp(qname)); } } if (ptype.isBottom()) { return null; } return ptype; }
private JSType getRecordTypeHelper( Node n, DeclaredTypeRegistry registry, ImmutableList<String> typeParameters) throws UnknownTypeException { Map<String, Property> props = new LinkedHashMap<>(); for (Node propNode = n.getFirstFirstChild(); propNode != null; propNode = propNode.getNext()) { boolean isPropDeclared = propNode.getType() == Token.COLON; Node propNameNode = isPropDeclared ? propNode.getFirstChild() : propNode; String propName = propNameNode.getString(); if (propName.startsWith("'") || propName.startsWith("\"")) { propName = propName.substring(1, propName.length() - 1); } JSType propType = !isPropDeclared ? JSType.UNKNOWN : getTypeFromCommentHelper(propNode.getLastChild(), registry, typeParameters); Property prop; if (propType.equals(JSType.UNDEFINED) || isUnionWithUndefined(propNode.getLastChild())) { prop = Property.makeOptional(null, propType, propType); } else { prop = Property.make(propType, propType); } props.put(propName, prop); } return JSType.fromObjectType(ObjectType.fromProperties(props)); }
static boolean isUnionSubtype( boolean keepLoosenessOfThis, Set<ObjectType> objs1, Set<ObjectType> objs2) { for (ObjectType obj1 : objs1) { boolean foundSupertype = false; for (ObjectType obj2 : objs2) { if (obj1.isSubtypeOf(keepLoosenessOfThis, obj2)) { foundSupertype = true; break; } } if (!foundSupertype) { return false; } } return true; }
private JSType getNominalTypeHelper( RawNominalType rawType, Node n, DeclaredTypeRegistry registry, ImmutableList<String> outerTypeParameters) throws UnknownTypeException { NominalType uninstantiated = rawType.getAsNominalType(); if (!rawType.isGeneric() && !n.hasChildren()) { return rawType.getInstanceWithNullability(NULLABLE_TYPES_BY_DEFAULT); } ImmutableList.Builder<JSType> typeList = ImmutableList.builder(); if (n.hasChildren()) { // Compute instantiation of polymorphic class/interface. Preconditions.checkState(n.getFirstChild().isBlock(), n); for (Node child : n.getFirstChild().children()) { typeList.add(getTypeFromCommentHelper(child, registry, outerTypeParameters)); } } ImmutableList<JSType> typeArguments = typeList.build(); ImmutableList<String> typeParameters = rawType.getTypeParameters(); int typeArgsSize = typeArguments.size(); int typeParamsSize = typeParameters.size(); if (typeArgsSize != typeParamsSize) { // We used to also warn when (typeArgsSize < typeParamsSize), but it // happens so often that we stopped. Array, Object and goog.Promise are // common culprits, but many other types as well. if (typeArgsSize > typeParamsSize) { warnings.add( JSError.make( n, INVALID_GENERICS_INSTANTIATION, uninstantiated.getName(), String.valueOf(typeParamsSize), String.valueOf(typeArgsSize))); } return maybeMakeNullable( JSType.fromObjectType( ObjectType.fromNominalType( uninstantiated.instantiateGenerics( fixLengthOfTypeList(typeParameters.size(), typeArguments))))); } return maybeMakeNullable( JSType.fromObjectType( ObjectType.fromNominalType(uninstantiated.instantiateGenerics(typeArguments)))); }
private ObjectType withPropertyRequired(String pname) { Property oldProp = this.props.get(pname); Property newProp = oldProp == null ? UNKNOWN_PROP : Property.make(oldProp.getType(), oldProp.getDeclaredType()); return ObjectType.makeObjectType( nominalType, this.props.with(pname, newProp), fn, isLoose, this.objectKind); }
public JSType withProperty(QualifiedName qname, JSType type) { Preconditions.checkArgument(type != null); if (isUnknown()) { return this; } Preconditions.checkState(this.objs != null); return new JSType( this.mask, this.location, ObjectType.withProperty(this.objs, qname, type), typeVar); }
static ImmutableSet<ObjectType> joinSets( ImmutableSet<ObjectType> objs1, ImmutableSet<ObjectType> objs2) { if (objs1.isEmpty()) { return objs2; } else if (objs2.isEmpty()) { return objs1; } ObjectType[] objs1Arr = objs1.toArray(new ObjectType[0]); ObjectType[] keptFrom1 = Arrays.copyOf(objs1Arr, objs1Arr.length); ImmutableSet.Builder<ObjectType> newObjs = ImmutableSet.builder(); for (ObjectType obj2 : objs2) { boolean addedObj2 = false; for (int i = 0; i < objs1Arr.length; i++) { ObjectType obj1 = objs1Arr[i]; NominalType nominalType1 = obj1.nominalType; NominalType nominalType2 = obj2.nominalType; if (areRelatedClasses(nominalType1, nominalType2)) { if (nominalType2 == null && nominalType1 != null && !obj1.isSubtypeOf(obj2) || nominalType1 == null && nominalType2 != null && !obj2.isSubtypeOf(obj1)) { // Don't merge other classes with record types break; } keptFrom1[i] = null; addedObj2 = true; // obj1 and obj2 may be in a subtype relation. // Even then, we want to join them because we don't want to forget // any extra properties in the subtype object. newObjs.add(join(obj1, obj2)); break; } } if (!addedObj2) { newObjs.add(obj2); } } for (ObjectType o : keptFrom1) { if (o != null) { newObjs.add(o); } } return newObjs.build(); }
public JSType withDeclaredProperty(QualifiedName qname, JSType type, boolean isConstant) { Preconditions.checkState(this.objs != null && this.location == null); if (type == null && isConstant) { type = JSType.UNKNOWN; } return new JSType( this.mask, null, ObjectType.withDeclaredProperty(this.objs, qname, type, isConstant), typeVar); }
// TODO(dimvar): handle greatest lower bound of interface types. // If we do that, we need to normalize the output, otherwise it could contain // two object types that are in a subtype relation, eg, see // NewTypeInferenceES5OrLowerTest#testDifficultObjectSpecialization. static ImmutableSet<ObjectType> meetSetsHelper( boolean specializeObjs1, Set<ObjectType> objs1, Set<ObjectType> objs2) { ImmutableSet.Builder<ObjectType> newObjs = ImmutableSet.builder(); for (ObjectType obj2 : objs2) { for (ObjectType obj1 : objs1) { if (areRelatedClasses(obj1.nominalType, obj2.nominalType)) { ObjectType newObj; if (specializeObjs1) { newObj = obj1.specialize(obj2); if (newObj == null) { continue; } } else { newObj = meet(obj1, obj2); } newObjs.add(newObj); } } } return newObjs.build(); }
public JSType removeType(JSType other) { if ((isTop() || isUnknown()) && other.equals(NULL)) { return TOP_MINUS_NULL; } if ((isTop() || isUnknown()) && other.equals(UNDEFINED)) { return TOP_MINUS_UNDEF; } if (other.equals(NULL) || other.equals(UNDEFINED)) { return new JSType(mask & ~other.mask, location, objs, typeVar); } if (objs == null) { return this; } Preconditions.checkState((other.mask & ~NON_SCALAR_MASK) == 0 && other.objs.size() == 1); NominalType otherKlass = Iterables.getOnlyElement(other.objs).getNominalType(); ImmutableSet.Builder<ObjectType> newObjs = ImmutableSet.builder(); for (ObjectType obj : objs) { if (!Objects.equal(obj.getNominalType(), otherKlass)) { newObjs.add(obj); } } return new JSType(mask, location, newObjs.build(), typeVar); }
private boolean isSubtypeOfHelper(boolean keepLoosenessOfThis, JSType other) { if (isUnknown() || other.isUnknown() || other.isTop()) { return true; } else if ((mask | other.mask) != other.mask) { return false; } else if (!Objects.equal(this.typeVar, other.typeVar)) { return false; } else if (this.objs == null) { return true; } // Because of optional properties, // x \le y \iff x \join y = y does not hold. return ObjectType.isUnionSubtype(keepLoosenessOfThis, this.objs, other.objs); }
public JSType substituteGenerics(Map<String, JSType> concreteTypes) { if (isTop() || isUnknown()) { return this; } ImmutableSet<ObjectType> newObjs = null; if (objs != null) { ImmutableSet.Builder<ObjectType> builder = ImmutableSet.builder(); for (ObjectType obj : objs) { builder.add(obj.substituteGenerics(concreteTypes)); } newObjs = builder.build(); } JSType current = new JSType(mask & ~TYPEVAR_MASK, location, newObjs, null); if ((mask & TYPEVAR_MASK) != 0) { current = JSType.join( current, concreteTypes.containsKey(typeVar) ? concreteTypes.get(typeVar) : fromTypeVar(typeVar)); } return current; }
public void setObjectType(RawNominalType builtinObject) { NominalType builtinObjectNT = builtinObject.getAsNominalType(); this.builtinObject = builtinObject; this.topObjectType = builtinObject.getInstanceAsJSType().getObjTypeIfSingletonObj(); this.looseTopObject = ObjectType.makeObjectType( this, builtinObjectNT, PersistentMap.<String, Property>create(), null, null, true, ObjectKind.UNRESTRICTED); this.topObject = JSType.fromObjectType(this.topObjectType); this.topStruct = JSType.fromObjectType( ObjectType.makeObjectType( this, builtinObjectNT, PersistentMap.<String, Property>create(), null, null, false, ObjectKind.STRUCT)); this.topDict = JSType.fromObjectType( ObjectType.makeObjectType( this, builtinObjectNT, PersistentMap.<String, Property>create(), null, null, false, ObjectKind.DICT)); this.bottomObject = ObjectType.createBottomObject(this); }
/** * Unify the two types symmetrically, given that we have already instantiated the type variables * of interest in {@code t1} and {@code t2}, treating JSType.UNKNOWN as a "hole" to be filled. * * @return The unified type, or null if unification fails */ static JSType unifyUnknowns(JSType t1, JSType t2) { if (t1.isUnknown()) { return t2; } else if (t2.isUnknown()) { return t1; } else if (t1.isTop() && t2.isTop()) { return TOP; } else if (t1.isTop() || t2.isTop()) { return null; } int t1Mask = promoteBoolean(t1.mask); int t2Mask = promoteBoolean(t2.mask); if (t1Mask != t2Mask || !Objects.equal(t1.typeVar, t2.typeVar)) { return null; } // All scalar types are equal if ((t1Mask & NON_SCALAR_MASK) == 0) { return t1; } if (t1.objs.size() != t2.objs.size()) { return null; } Set<ObjectType> ununified = Sets.newHashSet(t2.objs); Set<ObjectType> unifiedObjs = Sets.newHashSet(); for (ObjectType objType1 : t1.objs) { ObjectType unified = objType1; boolean hasUnified = false; for (ObjectType objType2 : t2.objs) { ObjectType tmp = ObjectType.unifyUnknowns(unified, objType2); if (tmp != null) { hasUnified = true; ununified.remove(objType2); unified = tmp; } } if (!hasUnified) { return null; } unifiedObjs.add(unified); } if (!ununified.isEmpty()) { return null; } return new JSType(t1Mask, null, ImmutableSet.copyOf(unifiedObjs), t1.typeVar); }
/** * When defining an enum such as /** @enum {number} * / var X = { ONE: 1, TWO: 2 }; the properties * of the object literal are constant. */ @Override protected JSType computeJSType() { Preconditions.checkNotNull(enumPropType); Preconditions.checkState(this.namespaceType == null); PersistentMap<String, Property> propMap = PersistentMap.create(); for (String s : this.props) { propMap = propMap.with(s, Property.makeConstant(null, enumPropType, enumPropType)); } return JSType.fromObjectType( ObjectType.makeObjectType( this.commonTypes, this.commonTypes.getLiteralObjNominalType(), propMap, null, this, false, ObjectKind.UNRESTRICTED)); }
// Meet two types, location agnostic public static JSType meet(JSType lhs, JSType rhs) { if (lhs.isTop()) { return rhs; } else if (rhs.isTop()) { return lhs; } else if (lhs.isUnknown()) { return rhs; } else if (rhs.isUnknown()) { return lhs; } int newMask = lhs.mask & rhs.mask; String newTypevar; if (Objects.equal(lhs.typeVar, rhs.typeVar)) { newTypevar = lhs.typeVar; } else { newTypevar = null; newMask = newMask & ~TYPEVAR_MASK; } return new JSType(newMask, null, ObjectType.meetSets(lhs.objs, rhs.objs), newTypevar); }