This is the intended behavior. See here and here for the implementation and explanations. (It is also alluded to here by @lorentey in explaining the implementation of Hashable
enhancements. See also this earlier discussion which lays out the problem in more detail.)
We have had several Swift Evolution proposals to do with NSNumber
behavior in Swift; this is not some temporary hack but part of the final design, and is now consistent across all platforms. A previous bridging behavior (SE-0139) was ultimately deemed suboptimal and abandoned with SE-0170; notably, this bridging design was adopted well after AnyHashable
was added in SE-0131. In fact, in motivating SE-0170, the authors write:
No matter if you are using Objective-C in your app/framework in addition to Swift or not the behavior should be easily understood and consistent.
I'm not aware of any guarantee that AnyHashable
preserves the hashValue
of its wrapped instance. In fact, it seems that the point of the _HasCustomAnyHashableRepresentation
protocol is to permit otherwise.
The example given in the documentation for AnyHashable
(written for SE-0131) is now outdated; it was actually corrected for the next version of Swift in PR #21550. In that conversation, @Joe_Groff explains:
AnyHashable should behave this way for any types that are transitively
as?
-castable, so you can get aString
out as anNSString
and v.v., anArray<T>
as anNSArray
, and so on. The standard library integer and floating-point types as well asCGFloat
andDecimal
all bridge toNSNumber
, and NSNumber bridges back to any of those types if the value is exactly representable.