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 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);
 }
  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));
  }
 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);
 }