Note that the attached sample project doesn't compile in Swift 4.1 or below --
Optional<String> has only became
Hashable in Swift 4.2, so it couldn't be used as a
Dictionary key in older versions.
However, the behavior you found is definitely a bug!
Car.init does not specify Objective-C generic type parameters for its
NSDictionary argument, so it gets imported as taking
Dictionary<AnyHashable, Any>. Unfortunately in Swift 4.2,
String keys in such dictionaries do not compare the same as
String? keys holding the same text, which is why you can't use NSString instances to look up values in the bridged dictionary instance.
Swift 4.2 introduces conditional
Hashable conformance for
Optional. In the implementation,
Optional<T> does not hash the same way as
T -- to ensure that its two cases generate unique hash encodings,
Optional.some feeds a constant value to the hasher in addition to its wrapped value. This is somewhat pedantic, but it's the correct behavior:
nil should not hash the same as any non-nil value.
This has some interesting implications, though. Consider this experiment:
let a: String = "Hello"
let b: String? = "Hello"
a == b // ⟹ true
a.hashValue == b.hashValue // ⟹ (usually) false
This may look like a violation of Hashable requirements, but it actually isn't! In
a == b,
a suffers implicit promotion to
String? in order to make it Equatable to b. This promotion does not happen when we compare hash values, and this is why hashes differ -- there is no requirement for two values of distinct types to produce the same hash value.
However, when converted to
b can be reasonably expected to compare and hash the same. Unfortunately, they don't:
// Swift 4.2
(a as AnyHashable) == (b as AnyHashable) // ⟹ false
(a as AnyHashable).hashValue == (b as AnyHashable).hashValue // ⟹ (usually) false
This behavior is consistent with
Hashable requirements, but it isn't semantically correct.
Optional should have a custom
AnyHashable representation that makes non-nil optionals interchangeable with their wrapped value.