It's actually a bug fixed on the main branch. Conditionally-available property wrappers are not allowed for the same reason as conditionally-available stored properties.
Yes, since FocusState is itself only available on iOS 15+, this code never should've compiled unless your app's deployment target, or the type the stored property appears in, is iOS 15+.
A correct implementation of this requires doing some form of dynamic layout where the stored property is omitted if the type metadata for it is not available. This would also require language changes to definite initialization to ensure that the compiler could handle version checks in initializers. It's possible it will be implemented in the future, but it is not implemented today.
I hadn't gotten far enough to test the code I was writing on a pre-iOS 15 device, but based on this discussion thread it sounds like it would have crashed at runtime. So even though the compiler allowed it previously there shouldn't be any apps using that "feature"(bug)
I'd be interested to understand why we can't use @available on stored properties without a property wrapper. I often find myself writing code like this:
struct Thing {
// What I wanted to write:
// @available(iOS 10, *)
// var feedbackGenerator: UISelectionFeedbackGenerator? = nil
// What I had to do instead:
private var _feedbackGenerator: Any?
@available(iOS 10, *)
var feedbackGenerator: UISelectionFeedbackGenerator? {
get { return _feedbackGenerator as? UISelectionFeedbackGenerator }
set { _feedbackGenerator = newValue }
}
init() {
if #available(iOS 10, *) {
feedbackGenerator = UISelectionFeedbackGenerator()
}
}
}
This moves the @available declaration from the stored property to a computed property, and then everything is happy. But it feels like a bunch of unnecessary.
I (being naive) would assume that when running on older OS versions, the compiler could just fill the memory of that property with 0s, and then the compiler would disallow all access to that property. Would something like that feasible?
The size of a type isn't necessarily known at compile time in Swift. That's why in the fully general case you'd need a fallback like Slava described, handling a fully or partially invalid type (consider Result<OnlyOnNewOS, Error>) without crashing. In theory the compiler could special-case class references, or optional class references, or any type with fully-known layout (recursively frozen on all structs and enums, basically), and in those cases "fill with zeros and disallow all access" would work. But the rule might be hard to explain. Either way there needs to be some work done on the compiler and possibly the runtime as well, but it's not impossible.
You've replaced the storage of the type with 'Any', which adds a layer of dynamic boxing around the actual value (UISelectionFeedbackGenerator). Older runtimes know how to lay out an 'Any', but not necessarily 'UISelectionFeedbackGenerator'.
This is a slightly different issue, but previous to Swift 5.7, you could use @available on lazy properties but now it gives the same error about using @available on stored properties. Was this an intentional change or a bug?