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