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.
Details:
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 AnyHashable
, a
and 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.