ObjectType specialize(ObjectType other) {
   Preconditions.checkState(areRelatedClasses(this.nominalType, other.nominalType));
   if (this == TOP_OBJECT && other.objectKind.isUnrestricted()) {
     return other;
   }
   NominalType resultNomType = NominalType.pickSubclass(this.nominalType, other.nominalType);
   ObjectKind ok = ObjectKind.meet(this.objectKind, other.objectKind);
   if (resultNomType != null && resultNomType.isClassy()) {
     Preconditions.checkState(this.fn == null && other.fn == null);
     PersistentMap<String, Property> newProps =
         meetPropsHelper(true, resultNomType, this.props, other.props);
     if (newProps == BOTTOM_MAP) {
       return BOTTOM_OBJECT;
     }
     return new ObjectType(resultNomType, newProps, null, false, ok);
   }
   FunctionType thisFn = this.fn;
   boolean isLoose = this.isLoose;
   if (resultNomType != null && resultNomType.isFunction() && this.fn == null) {
     thisFn = other.fn;
     isLoose = other.fn.isLoose();
   }
   PersistentMap<String, Property> newProps =
       meetPropsHelper(true, resultNomType, this.props, other.props);
   if (newProps == BOTTOM_MAP) {
     return BOTTOM_OBJECT;
   }
   FunctionType newFn = thisFn == null ? null : thisFn.specialize(other.fn);
   if (!FunctionType.isInhabitable(newFn)) {
     return BOTTOM_OBJECT;
   }
   return new ObjectType(resultNomType, newProps, newFn, isLoose, ok);
 }
 static ObjectType meet(ObjectType obj1, ObjectType obj2) {
   Preconditions.checkState(areRelatedClasses(obj1.nominalType, obj2.nominalType));
   if (obj1 == TOP_OBJECT) {
     return obj2;
   } else if (obj2 == TOP_OBJECT) {
     return obj1;
   }
   NominalType resultNomType = NominalType.pickSubclass(obj1.nominalType, obj2.nominalType);
   FunctionType fn = FunctionType.meet(obj1.fn, obj2.fn);
   if (!FunctionType.isInhabitable(fn)) {
     return BOTTOM_OBJECT;
   }
   boolean isLoose = obj1.isLoose && obj2.isLoose || fn != null && fn.isLoose();
   if (resultNomType != null && resultNomType.isFunction() && fn == null) {
     fn = obj1.fn == null ? obj2.fn : obj1.fn;
     isLoose = fn.isLoose();
   }
   PersistentMap<String, Property> props;
   if (isLoose) {
     props = joinPropsLoosely(obj1.props, obj2.props);
   } else {
     props = meetPropsHelper(false, resultNomType, obj1.props, obj2.props);
   }
   if (props == BOTTOM_MAP) {
     return BOTTOM_OBJECT;
   }
   ObjectKind ok = ObjectKind.meet(obj1.objectKind, obj2.objectKind);
   return new ObjectType(resultNomType, props, fn, isLoose, ok);
 }
 private ObjectType(
     NominalType nominalType,
     PersistentMap<String, Property> props,
     FunctionType fn,
     boolean isLoose,
     ObjectKind objectKind) {
   Preconditions.checkArgument(
       fn == null || fn.isQmarkFunction() || fn.isLoose() == isLoose,
       "isLoose: %s, fn: %s",
       isLoose,
       fn);
   Preconditions.checkArgument(FunctionType.isInhabitable(fn));
   Preconditions.checkArgument(
       fn == null || nominalType != null, "Cannot create function %s without nominal type", fn);
   if (nominalType != null) {
     Preconditions.checkArgument(
         !nominalType.isClassy() || !isLoose,
         "Cannot create loose objectType with nominal type %s",
         nominalType);
     Preconditions.checkArgument(
         fn == null || nominalType.isFunction(),
         "Cannot create objectType of nominal type %s with function (%s)",
         nominalType,
         fn);
     Preconditions.checkArgument(
         !nominalType.isFunction() || fn != null,
         "Cannot create Function instance without a FunctionType");
   }
   this.nominalType = nominalType;
   this.props = props;
   this.fn = fn;
   this.isLoose = isLoose;
   this.objectKind = objectKind;
 }
  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));
  }
 private NominalType getMaybeParentClass(
     JSDocInfo jsdoc,
     String functionName,
     Node funNode,
     ImmutableList<String> typeParameters,
     DeclaredTypeRegistry registry) {
   if (!jsdoc.hasBaseType()) {
     return null;
   }
   if (!jsdoc.isConstructor()) {
     warnings.add(JSError.make(funNode, EXTENDS_NOT_ON_CTOR_OR_INTERF, functionName));
     return null;
   }
   Node docNode = jsdoc.getBaseType().getRoot();
   JSType extendedType = getMaybeTypeFromComment(docNode, registry, typeParameters);
   if (extendedType == null) {
     return null;
   }
   NominalType parentClass = extendedType.getNominalTypeIfSingletonObj();
   if (parentClass != null && parentClass.isClass()) {
     return parentClass;
   }
   if (parentClass == null) {
     warnings.add(
         JSError.make(funNode, EXTENDS_NON_OBJECT, functionName, extendedType.toString()));
   } else {
     Preconditions.checkState(parentClass.isInterface());
     warnings.add(JSError.make(funNode, CONFLICTING_EXTENDED_TYPE, "constructor", functionName));
   }
   return null;
 }
 private void handleConstructorAnnotation(
     String functionName,
     Node funNode,
     RawNominalType constructorType,
     NominalType parentClass,
     ImmutableSet<NominalType> implementedIntfs,
     DeclaredTypeRegistry registry,
     FunctionTypeBuilder builder) {
   String className = constructorType.toString();
   NominalType builtinObject = registry.getCommonTypes().getObjectType();
   if (parentClass == null && !functionName.equals("Object")) {
     parentClass = builtinObject;
   }
   if (parentClass != null) {
     if (!constructorType.addSuperClass(parentClass)) {
       warnings.add(JSError.make(funNode, INHERITANCE_CYCLE, className));
     } else if (parentClass != builtinObject) {
       if (constructorType.isStruct() && !parentClass.isStruct()) {
         warnings.add(JSError.make(funNode, CONFLICTING_SHAPE_TYPE, "struct", className));
       } else if (constructorType.isDict() && !parentClass.isDict()) {
         warnings.add(JSError.make(funNode, CONFLICTING_SHAPE_TYPE, "dict", className));
       }
     }
   }
   if (constructorType.isDict() && !implementedIntfs.isEmpty()) {
     warnings.add(JSError.make(funNode, DICT_IMPLEMENTS_INTERF, className));
   }
   boolean noCycles = constructorType.addInterfaces(implementedIntfs);
   Preconditions.checkState(noCycles);
   builder.addNominalType(constructorType.getInstanceAsJSType());
 }
 // We require finalization for the interfaces here because the inheritance
 // chain of each type may not be correct until after the type is finalized.
 public ImmutableSet<NominalType> getInstantiatedInterfaces() {
   Preconditions.checkState(this.rawType.isFinalized());
   ImmutableSet.Builder<NominalType> result = ImmutableSet.builder();
   for (NominalType interf : this.rawType.getInterfaces()) {
     result.add(interf.instantiateGenerics(typeMap));
   }
   return result.build();
 }
 // The main difference from getInstantiatedInterfaces is that this method
 // can be used on non-finalized types.
 private ImmutableSet<NominalType> getInstantiatedIObjectInterfaces() {
   ImmutableSet.Builder<NominalType> result = ImmutableSet.builder();
   for (NominalType interf : this.rawType.getInterfaces()) {
     if (interf.inheritsFromIObjectReflexive()) {
       result.add(interf.instantiateGenerics(typeMap));
     }
   }
   return result.build();
 }
 private Property getLeftmostProp(QualifiedName qname) {
   String objName = qname.getLeftmostName();
   Property p = props.get(objName);
   if (p != null) {
     return p;
   }
   if (nominalType != null) {
     return nominalType.getProp(objName);
   }
   return builtinObject == null ? null : builtinObject.getProp(objName);
 }
 // A special-case of meet
 static NominalType pickSubclass(NominalType c1, NominalType c2) {
   if (c1 == null) {
     return c2;
   }
   if (c2 == null) {
     return c1;
   }
   if (c1.isNominalSubtypeOf(c2)) {
     return c1;
   }
   return c2.isNominalSubtypeOf(c1) ? c2 : null;
 }
  /**
   * Required properties are acceptable where an optional is required, but not vice versa. Optional
   * properties create cycles in the type lattice, eg, { } \le { p: num= } and also { p: num= } \le
   * { }.
   */
  boolean isSubtypeOf(boolean keepLoosenessOfThis, ObjectType other) {
    if (other == TOP_OBJECT) {
      return true;
    }

    if ((keepLoosenessOfThis && this.isLoose) || other.isLoose) {
      return this.isLooseSubtypeOf(other);
    }

    NominalType thisNt = this.nominalType;
    NominalType otherNt = other.nominalType;
    if (thisNt == null && otherNt != null
        || thisNt != null && otherNt != null && !thisNt.isSubtypeOf(otherNt)) {
      return false;
    }

    if (otherNt == null
        && !this.objectKind.isSubtypeOf(other.objectKind)
        // Interfaces are structs but we allow them to be used in a context that
        // expects a record type, even though it is unsound.
        // TODO(dimvar): Remove this when we switch to structural interfaces.
        && !(this.isInterfaceInstance() && other.objectKind.isUnrestricted())) {
      return false;
    }

    // If nominalType1 < nominalType2, we only need to check that the
    // properties of other are in (obj1 or nominalType1)
    for (Map.Entry<String, Property> entry : other.props.entrySet()) {
      String pname = entry.getKey();
      Property prop2 = entry.getValue();
      Property prop1 = this.getLeftmostProp(new QualifiedName(pname));

      if (prop2.isOptional()) {
        if (prop1 != null && !prop1.getType().isSubtypeOf(prop2.getType())) {
          return false;
        }
      } else {
        if (prop1 == null || prop1.isOptional() || !prop1.getType().isSubtypeOf(prop2.getType())) {
          return false;
        }
      }
    }

    if (other.fn == null) {
      return true;
    } else if (this.fn == null) {
      // Can only be executed if we have declared types for callable objects.
      return false;
    }
    return this.fn.isSubtypeOf(other.fn);
  }
 public boolean isTypeVariableInScope(String tvar) {
   if (typeParameters != null && typeParameters.contains(tvar)) {
     return true;
   }
   // We don't look at this.nominalType, b/c if this function is a generic
   // constructor, then typeParameters contains the relevant type variables.
   if (receiverType != null && receiverType.isUninstantiatedGenericType()) {
     RawNominalType rawType = receiverType.getRawNominalType();
     if (rawType.getTypeParameters().contains(tvar)) {
       return true;
     }
   }
   return false;
 }
 JSType getIndexType() {
   if (isIObject()) {
     return this.typeMap.get(this.rawType.getTypeParameters().get(0));
   }
   // This type is a subtype of all indexed types it inherits from,
   // and we use contravariance for the key of the index operation,
   // so we join here.
   JSType result = getCommonTypes().BOTTOM;
   for (NominalType interf : getInstantiatedIObjectInterfaces()) {
     JSType tmp = interf.getIndexType();
     if (tmp != null) {
       result = JSType.join(result, tmp);
     }
   }
   return result.isBottom() ? null : result;
 }
 boolean unifyWithSubtype(
     NominalType other,
     List<String> typeParameters,
     Multimap<String, JSType> typeMultimap,
     SubtypeCache subSuperMap) {
   other = other.findMatchingAncestorWith(this);
   if (other == null) {
     return false;
   }
   if (!isGeneric()) {
     // Non-generic nominal types don't contribute to the unification.
     return true;
   }
   // Most of the time, both nominal types are already instantiated when
   // unifyWith is called. Rarely, when we call a polymorphic function from the
   // body of a method of a polymorphic class, then other.typeMap is
   // empty. For now, don't do anything fancy in that case.
   Preconditions.checkState(!typeMap.isEmpty());
   if (other.typeMap.isEmpty()) {
     return true;
   }
   boolean hasUnified = true;
   for (String typeParam : this.rawType.getTypeParameters()) {
     JSType fromOtherMap = other.typeMap.get(typeParam);
     Preconditions.checkNotNull(
         fromOtherMap, "Type variable %s not found in map %s", typeParam, other.typeMap);
     hasUnified =
         hasUnified
             && this.typeMap
                 .get(typeParam)
                 .unifyWithSubtype(fromOtherMap, typeParameters, typeMultimap, subSuperMap);
   }
   return hasUnified;
 }
 private static PersistentMap<String, Property> meetPropsHelper(
     boolean specializeProps1,
     NominalType resultNominalType,
     PersistentMap<String, Property> props1,
     PersistentMap<String, Property> props2) {
   PersistentMap<String, Property> newProps = props1;
   if (resultNominalType != null) {
     for (Map.Entry<String, Property> propsEntry : props1.entrySet()) {
       String pname = propsEntry.getKey();
       Property nomProp = resultNominalType.getProp(pname);
       if (nomProp != null) {
         newProps =
             addOrRemoveProp(specializeProps1, newProps, pname, nomProp, propsEntry.getValue());
         if (newProps == BOTTOM_MAP) {
           return BOTTOM_MAP;
         }
       }
     }
   }
   for (Map.Entry<String, Property> propsEntry : props2.entrySet()) {
     String pname = propsEntry.getKey();
     Property prop2 = propsEntry.getValue();
     Property newProp;
     if (!props1.containsKey(pname)) {
       newProp = prop2;
     } else {
       Property prop1 = props1.get(pname);
       if (prop1.equals(prop2)) {
         continue;
       }
       newProp = specializeProps1 ? prop1.specialize(prop2) : Property.meet(prop1, prop2);
     }
     if (resultNominalType != null && resultNominalType.getProp(pname) != null) {
       Property nomProp = resultNominalType.getProp(pname);
       newProps = addOrRemoveProp(specializeProps1, newProps, pname, nomProp, newProp);
       if (newProps == BOTTOM_MAP) {
         return BOTTOM_MAP;
       }
     } else {
       if (newProp.getType().isBottom()) {
         return BOTTOM_MAP;
       }
       newProps = newProps.with(pname, newProp);
     }
   }
   return newProps;
 }
 private static Property getProp(Map<String, Property> props, NominalType nom, String pname) {
   if (props.containsKey(pname)) {
     return props.get(pname);
   } else if (nom != null) {
     return nom.getProp(pname);
   }
   return null;
 }
 private boolean isStructuralSubtypeOf(NominalType other, SubtypeCache subSuperMap) {
   Preconditions.checkArgument(other.isStructuralInterface());
   for (String pname : other.getAllPropsOfInterface()) {
     Property prop2 = other.getProp(pname);
     Property prop1 = this.getProp(pname);
     if (prop2.isOptional()) {
       if (prop1 != null && !prop1.getType().isSubtypeOf(prop2.getType(), subSuperMap)) {
         return false;
       }
     } else if (prop1 == null
         || prop1.isOptional()
         || !prop1.getType().isSubtypeOf(prop2.getType(), subSuperMap)) {
       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))));
 }
 JSType getIndexedType() {
   if (isIObject()) {
     return this.typeMap.get(this.rawType.getTypeParameters().get(1));
   }
   // This type is a subtype of all indexed types it inherits from,
   // and we use covariance for the value of the index operation,
   // so we meet here.
   JSType result = getCommonTypes().TOP;
   // We need this because the index type may explicitly be TOP.
   boolean foundIObject = false;
   for (NominalType interf : getInstantiatedIObjectInterfaces()) {
     JSType tmp = interf.getIndexedType();
     if (tmp != null) {
       foundIObject = true;
       result = JSType.meet(result, tmp);
     }
   }
   return foundIObject ? result : null;
 }
 boolean isNominalSubtypeOf(NominalType other) {
   RawNominalType thisRaw = this.rawType;
   if (thisRaw == other.rawType) {
     return areTypeMapsCompatible(other);
   }
   if (other.isBuiltinObject()) {
     return true;
   }
   if (other.isInterface()) {
     // If thisRaw is not finalized, thisRaw.interfaces may be null.
     for (NominalType i : thisRaw.getInterfaces()) {
       if (i.instantiateGenerics(this.typeMap).isNominalSubtypeOf(other)) {
         return true;
       }
     }
   }
   // Note that other can still be an interface here (implemented by a superclass)
   return isClass()
       && thisRaw.getSuperClass() != null
       && thisRaw.getSuperClass().instantiateGenerics(this.typeMap).isNominalSubtypeOf(other);
 }
 StringBuilder appendTo(StringBuilder builder) {
   if (!hasNonPrototypeProperties()) {
     if (fn != null) {
       return fn.appendTo(builder);
     } else if (getNominalType() != null) {
       return getNominalType().appendTo(builder);
     }
   }
   if (nominalType != null && !nominalType.getName().equals("Function")) {
     nominalType.appendTo(builder);
   } else if (isStruct()) {
     builder.append("struct");
   } else if (isDict()) {
     builder.append("dict");
   }
   if (fn != null) {
     builder.append("<|");
     fn.appendTo(builder);
     builder.append("|>");
   }
   if (nominalType == null || !props.isEmpty()) {
     builder.append('{');
     boolean firstIteration = true;
     for (String pname : new TreeSet<>(props.keySet())) {
       if (firstIteration) {
         firstIteration = false;
       } else {
         builder.append(", ");
       }
       builder.append(pname);
       builder.append(':');
       props.get(pname).appendTo(builder);
     }
     builder.append('}');
   }
   if (isLoose) {
     builder.append(" (loose)");
   }
   return builder;
 }
 // Returns a type with the same raw type as other, but possibly different type maps.
 private NominalType findMatchingAncestorWith(NominalType other) {
   RawNominalType thisRaw = this.rawType;
   if (thisRaw == other.rawType) {
     return this;
   }
   if (other.isInterface()) {
     for (NominalType i : thisRaw.getInterfaces()) {
       NominalType nt = i.instantiateGenerics(this.typeMap).findMatchingAncestorWith(other);
       if (nt != null) {
         return nt;
       }
     }
   }
   // Note that other can still be an interface here (implemented by a superclass)
   if (isClass() && thisRaw.getSuperClass() != null) {
     return thisRaw
         .getSuperClass()
         .instantiateGenerics(this.typeMap)
         .findMatchingAncestorWith(other);
   }
   return null;
 }
 private ImmutableSet<NominalType> getInterfacesHelper(
     JSDocInfo jsdoc,
     DeclaredTypeRegistry registry,
     ImmutableList<String> typeParameters,
     boolean implementedIntfs) {
   ImmutableSet.Builder<NominalType> builder = ImmutableSet.builder();
   for (JSTypeExpression texp :
       (implementedIntfs ? jsdoc.getImplementedInterfaces() : jsdoc.getExtendedInterfaces())) {
     Node expRoot = texp.getRoot();
     JSType interfaceType = getMaybeTypeFromComment(expRoot, registry, typeParameters);
     if (interfaceType != null) {
       NominalType nt = interfaceType.getNominalTypeIfSingletonObj();
       if (nt != null && nt.isInterface()) {
         builder.add(nt);
       } else if (implementedIntfs) {
         warnings.add(JSError.make(expRoot, IMPLEMENTS_NON_INTERFACE, interfaceType.toString()));
       } else {
         warnings.add(JSError.make(expRoot, EXTENDS_NON_INTERFACE, interfaceType.toString()));
       }
     }
   }
   return builder.build();
 }
 ObjectType substituteGenerics(Map<String, JSType> concreteTypes) {
   if (concreteTypes.isEmpty()) {
     return this;
   }
   PersistentMap<String, Property> newProps = PersistentMap.create();
   for (Map.Entry<String, Property> propsEntry : this.props.entrySet()) {
     String pname = propsEntry.getKey();
     Property newProp = propsEntry.getValue().substituteGenerics(concreteTypes);
     newProps = newProps.with(pname, newProp);
   }
   FunctionType newFn = fn == null ? null : fn.substituteGenerics(concreteTypes);
   return makeObjectType(
       nominalType == null ? null : nominalType.instantiateGenerics(concreteTypes),
       newProps,
       newFn,
       newFn != null && newFn.isQmarkFunction() || isLoose,
       objectKind);
 }
 /**
  * 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 ObjectType unifyUnknowns(ObjectType t1, ObjectType t2) {
   NominalType nt1 = t1.nominalType;
   NominalType nt2 = t2.nominalType;
   NominalType nt;
   if (nt1 == null && nt2 == null) {
     nt = null;
   } else if (nt1 == null || nt2 == null) {
     return null;
   } else {
     nt = NominalType.unifyUnknowns(nt1, nt2);
     if (nt == null) {
       return null;
     }
   }
   FunctionType newFn = null;
   if (t1.fn != null || t2.fn != null) {
     newFn = FunctionType.unifyUnknowns(t1.fn, t2.fn);
     if (newFn == null) {
       return null;
     }
   }
   PersistentMap<String, Property> newProps = PersistentMap.create();
   for (String propName : t1.props.keySet()) {
     Property prop1 = t1.props.get(propName);
     Property prop2 = t2.props.get(propName);
     if (prop2 == null) {
       return null;
     }
     Property p = Property.unifyUnknowns(prop1, prop2);
     if (p == null) {
       return null;
     }
     newProps = newProps.with(propName, p);
   }
   return makeObjectType(
       nt,
       newProps,
       newFn,
       t1.isLoose || t2.isLoose,
       ObjectKind.join(t1.objectKind, t2.objectKind));
 }
 /**
  * Unify {@code this}, which may contain free type variables, with {@code other}, a concrete type,
  * modifying the supplied {@code typeMultimap} to add any new template varaible type bindings.
  *
  * @return Whether unification succeeded
  */
 boolean unifyWithSubtype(
     ObjectType other, List<String> typeParameters, Multimap<String, JSType> typeMultimap) {
   if (fn != null) {
     if (other.fn == null || !fn.unifyWithSubtype(other.fn, typeParameters, typeMultimap)) {
       return false;
     }
   }
   if (nominalType != null && other.nominalType != null) {
     return nominalType.unifyWithSubtype(other.nominalType, typeParameters, typeMultimap);
   }
   if (nominalType != null || other.nominalType != null) {
     return false;
   }
   for (String propName : this.props.keySet()) {
     Property thisProp = props.get(propName);
     Property otherProp = other.props.get(propName);
     if (otherProp == null
         || !thisProp.unifyWithSubtype(otherProp, typeParameters, typeMultimap)) {
       return false;
     }
   }
   return true;
 }
 // A special-case of join
 static NominalType pickSuperclass(NominalType c1, NominalType c2) {
   if (c1 == null || c2 == null) {
     return null;
   }
   if (c1.isNominalSubtypeOf(c2)) {
     return c2;
   }
   if (c1.isRawSubtypeOf(c2)) {
     return c2.instantiateGenericsWithUnknown();
   }
   if (c2.isNominalSubtypeOf(c1)) {
     return c1;
   }
   if (c2.isRawSubtypeOf(c1)) {
     return c1.instantiateGenericsWithUnknown();
   }
   return null;
 }
 private boolean areTypeMapsCompatible(NominalType other) {
   Preconditions.checkState(this.rawType.equals(other.rawType));
   if (this.typeMap.isEmpty()) {
     return other.instantiationIsUnknownOrIdentity();
   }
   if (other.typeMap.isEmpty()) {
     return instantiationIsUnknownOrIdentity();
   }
   for (String typeVar : this.rawType.getTypeParameters()) {
     Preconditions.checkState(
         this.typeMap.containsKey(typeVar),
         "Type variable %s not in the domain: %s",
         typeVar,
         this.typeMap.keySet());
     Preconditions.checkState(
         other.typeMap.containsKey(typeVar),
         "Other (%s) doesn't contain mapping (%s->%s) from this (%s)",
         other,
         typeVar,
         this.typeMap.get(typeVar),
         this);
     JSType thisType = this.typeMap.get(typeVar);
     JSType otherType = other.typeMap.get(typeVar);
     JSTypes commonTypes = getCommonTypes();
     if (commonTypes.bivariantArrayGenerics && this.rawType.isBuiltinWithName("Array")) {
       thisType = thisType.removeType(commonTypes.NULL_OR_UNDEFINED);
       otherType = otherType.removeType(commonTypes.NULL_OR_UNDEFINED);
       if (!thisType.isSubtypeOf(otherType) && !otherType.isSubtypeOf(thisType)) {
         return false;
       }
     } else if (!thisType.isSubtypeOf(otherType)) {
       return false;
     }
   }
   return true;
 }
 static ObjectType fromNominalType(NominalType cl) {
   return ObjectType.makeObjectType(cl, null, null, false, cl.getObjectKind());
 }
 private static boolean areRelatedClasses(NominalType c1, NominalType c2) {
   if (c1 == null || c2 == null) {
     return true;
   }
   return c1.isSubtypeOf(c2) || c2.isSubtypeOf(c1);
 }