-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Compatibility for Optional -> AnyHashable casts #35650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Compatibility for Optional -> AnyHashable casts #35650
Conversation
As part of making casting more consistent, the behavior of Optional -> AnyHashable casts was changed in some cases. This PR provides a hook for re-enabling the old behavior in certain contexts. Background: Most of the time, casts from String? to AnyHashable get optimized to just injects the String? into the AnyHashable, so the following has long been true and remains true in Swift 5.4: ``` let s = "abc" let o: String? = s // Next test is true because s is promoted to Optional<String> print(s == o) // Next test is false: Optional<String> and String are different types print(s as AnyHashable == o as AnyHashable) ``` But when casts ended up going through the runtime, Swift 5.3 behaved differently, as you could see by casting a dictionary with `String?` keys (in the generic array code, key and value casts always use the runtime logic). In the following code, both print statements behave differently in Swift 5.4 than before: ``` let a: [String?:String] = ["Foo":"Bar"] let b = a as [AnyHashable:Any] print(b["Foo"] == "Bar") // Works before Swift 5.4 print(b["Foo" as String?] == "Bar") // Works in Swift 5.4 and later ``` Old behavior: The `String?` keys would get unwrapped to `String` before being injected into AnyHashable. This allows the first to work but strangely breaks the second. New behavior: The `String?` keys do not get unwrapped. This breaks the first but makes the second work. TODO: The long-term goal, of course, is for `AnyHashable("Foo" as String?)` to test equal to `AnyHashable("Foo")` (and hash the same, of course). In that case, all of the tests above will succeed. Resolves rdar://73301155
@swift-ci Please test |
Build failed |
@swift-ci Please test |
Build failed |
Build failed |
@swift-ci Please test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! I think we should preserve the old behavior unconditionally for now though; details below.
@swift-ci Please test Windows |
This restores the earlier behavior of Optionals cast to AnyHashable, so that [String?:Any] dictionaries cast to [AnyHashable:Any] can be indexed by plain String keys. This is a little problematic because it's not consistent with the compiler-optimized casts. But the ability to index such dictionaries by plain String keys seems important to preserve. SR-9047 will expand Optional/AnyHashable interoperability so that such indexing works without this special case. An earlier proposal would link-check this, changing the behavior depending on the caller. But it's not really workable to change the behavior seen by intermediate frameworks depending on the app they're being called by.
After thinking on it through the weekend, I think Karoy's right. I've changed it to unconditionally preserve the previous behavior. (And updated the tests to match.) |
@swift-ci Please test and merge |
@swift-ci Please test Windows platform |
(This is a cherry-pick of PR swiftlang#35650 to the release/5.4 branch.) The recent overhaul of the runtime dynamic casting made this particular cast consistent with the compiler optimizer and with how other similar casts behave. In particular, an `Optional` is cast to `AnyHashable` by simple injection, whereas previously the `Optional` was unwrapped (if it was non-nil) and the contents were injected. The previous behavior allowed code like the following to work: ``` let a: [String?:String] = ["Foo":"Bar"] let b = a as [AnyHashable:Any] print(b["Foo"] == "Bar") // Used to work ``` This is broken by the new cast behavior because `AnyHashable("Foo")` is not considered equal to `AnyHashable("Foo" as String?)`. Once SR-9047 is implemented, these two will in fact be considered equal. At that time, we should go back to a simple injection to make this cast behave identically to the compiler-optimized version of this case. Resolves rdar://73301155
Build failed |
Build failed |
As part of making casting more consistent, the behavior of Optional -> AnyHashable casts
was changed in some cases. This PR restores the old behavior.
Background: Most of the time, casts from String? to AnyHashable get optimized
to just injects the String? into the AnyHashable, so the following
has long been true and remains true in Swift 5.4:
But when casts ended up going through the runtime, Swift 5.3 behaved
differently, as you could see by casting a dictionary with
String?
keys (inthe generic array code, key and value casts always use the runtime logic). In
the following code, both print statements behave differently in current builds than
before:
Swift 5.3 and before: The
String?
keys would get unwrapped toString
before being injected into AnyHashable. This allows the first to work but strangely breaks the second.Behavior after casting overhaul: The
String?
keys do not get unwrapped. This breaks the first but makes the second work.This change restores the behavior for this specific case to the Swift 5.3 behavior.
TODO: The long-term goal, of course, is for
AnyHashable("Foo" as String?)
to test equal toAnyHashable("Foo")
(and hash the same, of course). See SR-9047 for details. Once that's done, we won't need to unwrap optionals in this case and can revert this change so the runtime behavior will precisely match the behavior of compiler-optimized casts.Resolves rdar://73301155