Yes! If we do one, ideally we should do the other too. Alas, I don't think we can do that quite yet. The reasons have to do with frustratingly technical minutia, but I'll try to convey them.
The bit gotcha right now is that the existing definitions heavily rely on type existentials:
// Current definitions as of Swift 6:
extension ObjectIdentifier {
public init(_ x: Any.Type) { ... }
}
public func == (t0: Any.Type?, t1: Any.Type?) -> Bool { ... }
public func != (t0: Any.Type?, t1: Any.Type?) -> Bool { ... }
Ideally we'd generalize these using generalized existential types:
// Desired generalizations:
extension ObjectIdentifier {
public init(_ x: any (~Copyable & ~Escapable).Type) { ... }
}
public func == (
left: (any (~Copyable & ~Escapable).Type)?,
right: (any (~Copyable & ~Escapable).Type)?
) -> Bool { ... }
public func != (
left: (any (~Copyable & ~Escapable).Type)?,
right: (any (~Copyable & ~Escapable).Type)?
) -> Bool { ... }
Unfortunately, these any (~Copyable & ~Escapable).Type
existentials aren't fully operational yet. (The syntax seems to work, but the overloads never trigger. The correct syntax results in an ill-formed .swiftinterface due to a small issue.) The pitch works around this by adding a generic initializer for ObjectIdentifier
as a temporary measure:
extension ObjectIdentifier {
public init(_ x: Any.Type) { ... }
@_disfavoredOverload // Unstable hack to avoid ambiguities
public init<T: ~Copyable & ~Escapable>(_ x: T.Type) { ... }
}
The generic isn't a perfect substitute for the original Any
-based version, though, as it isn't dynamic enough:
func test1(_ t: Any.Type) {}
func test2<T>(_ t: T.Type) {}
let t: Any.Type = Bool.self
test1(t) // OK
test2(t) // error: generic parameter 'T' could not be inferred
So unfortunately we need to have both variants as a workaround, with extra hacks to avoid ambiguous expressions in cases where both variants would apply. (My expectation is that we would be able to merge the two variants once generalized existentials become a thing, without breaking newly written code.)
Unfortunately, applying the same workaround on the ==
/!=
functions would result in the addition of new generic overloads for infix operators:
public func == (t0: Any.Type?, t1: Any.Type?) -> Bool { ... }
public func != (t0: Any.Type?, t1: Any.Type?) -> Bool { ... }
@_disfavoredOverload
public func ==<
T1: ~Copyable & ~Escapable,
T2: ~Copyable & ~Escapable
> (left: T1.Type?, right: T2.Type?) -> Bool { ... }
@_disfavoredOverload
public func !=<
T1: ~Copyable & ~Escapable,
T2: ~Copyable & ~Escapable
> (left: T1.Type?, right: T2.Type?) -> Bool { ... }
This would be extremely messy! Adding new generic overloads to infix operators increases the complexity of overload resolution, and that inevitably leads to previously working code running into "expression too complex" errors -- an unacceptable outcome.
The pitch therefore draws a line at the ObjectIdentifier
initializer, deferring to generalize the metatype ==
operators until generalized existentials become usable.
We do have the option to delay generalizing ObjectIdentifier
too, for consistency. (Or we could fix type existentials, of course -- but that would unnecessarily delay the rest of the proposal.)
(Edit: I corrected the syntax of generalized type existentials. (any ~Copyable & ~Escapable).Type
is obviously very different from any ((~Copyable & ~Escapable).Type)
, and naturally we need the latter. any (~Copyable & ~Escapable).Type
parses to this second interpretation.
)