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