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