-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
[SR-8704] Cast from Any?
to as? Value
where Value == Any produces unexpected result in Xcode 10
#51216
Comments
cc @rudkx |
The behavior you're seeing is a result of the change for https://bugs.swift.org/browse/SR-4248. The behavior now matches what you would get if you conditionally cast to the same type in a non-generic context. It would have probably been best to put this under |
Ah yup, this one's my bad. I didn't realise the (now obvious with hindsight) compatibility implications. Would it be worth restoring the old behaviour for Swift 5 under Swift 4 compat mode? Under the new behaviour, the compiler will assume that a generic placeholder could be a type with an arbitrary level of optionality, and so therefore won't eagerly unwrap the value before performing the cast – it will instead leave any unwrapping to the runtime. And because you're casting an To get the desired behaviour for your function, you can either cast to the optional type and then coalesce nil: func foo<Value>(_ x: Any?, as type: Value.Type) -> Value? {
return (x as? Value?) ?? nil
} Or unwrap func foo<Value>(_ x: Any?, as type: Value.Type) -> Value? {
return x.flatMap { $0 as? Value }
} |
Given that this affects Xcode 10 beta, which is Swift 4.2, not Swift 5, I would say the question should be whether old behavior should be restored in Swift 4 mode and new behavior used in Swift 4.2 mode (and I think the answer should be "yes" because this is a rather surprising behavior change to get just by upgrading Xcode 10 while keeping Swift 4 mode). |
Unfortunately, I believe the ship has sailed for making such changes to the 4.2 compiler, so the most we can do is restore the behaviour for the Swift 5 compiler under Swift 4 compat mode. |
It's a compatibility regression, so I'm not sure how Ted and co would feel about taking it for a 4.2.1 release. |
It depends how risky you and your reviewers think the changes are. If you think it's worth doing, you can follow the same process as before to get changes into the 4.2 branch, and Ted will either approve or deny the change. |
Opened a PR to limit the new casting behaviour to Swift 5 mode: #19217 |
Comment by Evgeny Kaz (JIRA) @hamishknight But what the philosophy of Optionals in swift says about it? Is this new behaviour right or not? Shall we rely on it? |
I think the new behavior is perfectly reasonable, it's just the fact that it's a potentially-breaking change that wasn't good. |
Comment by Evgeny Kaz (JIRA) Eridius (JIRA User) I feel like it should not be "reasonable" - it is kinda crucial for the language concept of optional and nil coalescing. It should be described very clearly in the documentation. Look an the next code: import UIKit
protocol Factory {
associatedtype ViewController: UIViewController
associatedtype Context
func build(with context: Context) -> ViewController
}
struct StringFactory: Factory {
func build(with context: String?) -> UINavigationController {
return UINavigationController(nibName: nil, bundle: nil)
}
}
struct FactoryBox<F: Factory> {
let factory: F
init(_ factory: F) {
self.factory = factory
}
func build(with context: Any?) -> UIViewController? {
guard let typedContext = context as? F.Context else {
print("Context is not supported")
return nil
}
return factory.build(with: typedContext)
}
}
StringFactory().build(with: nil) // Returns UINavigationController in both XCode 9 and 10
FactoryBox(StringFactory()).build(with: nil) // Returns nil in XCode 9 printing "Context is not supported", returns UINavigationController in XCode 10 As for me - the new behaviour is reasonable, but on other hand a construction like `guard let b = a as? Whatever else ...` becomes a bit confusing. What exactly is b? |
I confirmed that the behavior described in this issue has been changed on Swift 5 with $ pbpaste
func foo<Value>(_ x: Any?, as type: Value.Type) -> Value? {
return x as? Value
}
$ pbpaste|xcrun --toolchain org.swift.42320190228a swift -
▿ Optional(nil)
- some: nil
$ pbpaste|xcrun --toolchain org.swift.5020190306a swift -
▿ Optional(nil)
- some: nil
$ pbpaste|xcrun --toolchain org.swift.5020190306a swift -swift-version 4.2 -
- nil But is it correct behavior that using `-swift-version 4.2` makes Swift 5 to incompatible with Swift 4.2? |
@norio-nomura That is correct, albeit unfortunate. It's worth noting that there would be still be incompatibility between Swift 4.2 and Swift 5 with `-swift-version 4` even if we preserved the behavioural change in `-swift-version 4.2`. |
Environment
Xcode 10.0 (10L221o)
Apple Swift version 4.2 (swiftlang-1000.0.32.1 clang-1000.10.39)
Target: x86_64-apple-darwin17.7.0
Additional Detail from JIRA
md5: 695e9648f10cd45d05e11cf6b2acc3b6
is duplicated by:
relates to:
Issue Description:
In a generic context, when casting from an
Any?
value to a generically-bound type usingas? Value
, if the generic type is equal toAny
and the source value isnil
, in Xcode 9 the result isnil
but in Xcode 10 the result is.some(nil)
. This is true even when running with-swift-version 4
.Example:
In Xcode 9.4.1 this prints
But in Xcode 10 beta 5 this prints
The text was updated successfully, but these errors were encountered: