The need for an official solution to this problem makes sense to me. The main design question I have is about the granularity of the query. I think that a design like the one you proposed where the query matches the OS version aligned with the SDK is the most practical solution but it somewhat narrow in its utility.
As a starting point for exploring some of the alternatives, there's actually already an underscored variant of #if canImport()
that takes a module version, and it was designed to be used in situations precisely like the one you detailed in the post:
// Suppose WidgetKit-1.2.3 is user-module-version of WidgetKit in one
// of the betas that introduced these new APIs.
#if canImport(WidgetKit, _version: "1.2.3")
// ... handle other cases introduced in iOS 16 aligned SDKs
#if os(macOS)
// handle macOS specific cases introduced in iOS 16 aligned SDKs
case .someMacOnlyVariant: // [case 4]
#endif
#endif // canImport(WidgetKit, _version: "1.2.3")
This is not especially ergonomic to use because you need to scrape the the user-module-version
of the relevant framework out of the SDK in order to determine the version to put in the query (the version can be found at the top of one of the framework's .swiftinterface
files). The design does have a few nice properties, though:
- If the framework and the APIs in question are cross platform, then you can write a single query to determine the build time availability of the API across multiple platforms because module versions tend to be aligned across the aligned platform specific SDKs.
- It allows you to gracefully handle things like APIs introduced midway through the betas even though the overall system/SDK version number hasn't changed.
- It works for any Swift module that has an embedded user-module-version, regardless of whether the module is distributed with an SDK that is legible to the compiler.
We've also discussed an even finer grained version of this in another evolution thread. There the idea was to query directly for the presence of an individual declaration, and it might look something like this for the example in the OP:
#if has(WidgetKit.WidgetFamily.someMacOnlyVariant)
case .someMacOnlyVariant: // [case 4]
#endif
This is a potentially more verbose, but also more powerful query. Unfortunately I think we came to the conclusion that this would be prohibitively difficult to implement since it needs to be evaluated during parsing, before name lookups can be performed.
Of these two alternatives, I think that an official version #if canImport(Foo, _version: "1.2.3")
has the most promise because it is flexible enough to work in a broader ecosystem. We might be able to overcome the current awkwardness of relying on -user-module-version
being difficult to find by enhancing tooling so that it's easier to inspect.