SE-0362: Piecemeal adoption of future language improvements

+1. This solves an important problem in a way that fits well with how Swift already handles similar things, and appears to cover everything we need as a library developer who tries to adopt new Swift features promptly while still maintaining compatibility with old versions.

One thought I had while looking at the current set of Swift 6 things which will use this is that we might not actually use hasFeature(). Valid code with StrictConcurrency and ExistentialAny enabled should be a strict subset of valid code without them, and other than maybe ImplicitOpenExistentials the rest are things that don't make sense to enable until we can rely on them always being available. This is probably a good thing; I was worried about a proliferation of #ifs but at least for these features we'll probably avoid them. hasFeature() absolutely should exist, though, and I'm happy to see the changes to #if parsing to make similar future additions work better.

Duplicating our Package.swift to use this is going to be painful (it's a few hundred lines of code...) but that's a totally separate problem to solve. I really wish SPM let us declare a supported range of versions rather than a single one and use #if rather than duplicating the file.

1 Like

Mind elaborating? The two sound (roughly) synonymous to me so just wondering if I’m missing something.

1 Like

Overall, a great idea. How about also adding a flag like "-list-optional-features" that I could use on the command line to help me discover what's available?

2 Likes

For people with some types of reading and speech disorders, and for some people whose native tongue is not English, two consecutive words like this that look and sound very similar are not comfortable to read and utter.

4 Likes

I am +1 on the proposal overall.

I read the pitch and the proposal and feel this is a very nice addition.

However, after reading the suggestion from @benrimmington, I believe exposing this to SPM as methods on SwiftSetting would be a better choice.

To me it feels fairly heavyweight to add these parameters to the initializer of Target, even if they are optional.

First, the flags only apply to Swift targets, so the parameters never have use or meaning for C or Cxx targets. Something scoped to Swift targets, such as SwiftSetting seems more appropriate.

Second, enabling future and experimental features is undoubtedly useful, but both seem less fundamental and more esoteric than the other init parameters for a Target.

Since SwiftSetting is the existing SPM mechanism in place for specifying flags to be passed to the Swift compiler, adding the functionality there seems to be the better choice.

Although more verbose, I also think having the method name in SwiftSetting match the setting passed on the command line (with differences only in case style) also makes the mapping extremely clear. Being able to use the existing BuildSettingCondition for SwiftSettings is also a benefit.

6 Likes

Maybe “adoptFeature” is better?

I think it reads better and it clearly conveys intent. It’s even in the proposal title.

1 Like

+1. The gist of the proposa for me: previously, Swift has had ad-hoc command line arguments to enable some features. This proposal replaces these with a general command line argument approach for all future features, as well as SPM support and compile-time checking in the source. All good.

One thing I take issue with: hasFeature construct. I’d argue it can be clearer. Let’s look at the example proposed here:

#if hasFeature(ImplicitOpenExistentials)

It reads "if has feature…” but what has the feature? Swift language? Or the code that is being developed? It is ambiguous.

Let’s compare it to some prior art of similar widely used directives:

#if compiler(>=5.7) - “if compiler is greater than or equal to version…” pretty clear

#if os(macOS) - “if operating system is macOS” - reads smoothly. You could argue that spelling out #if operatingSystem(macOS) would also be quite Swifty.

if #available(iOS 13, *) { - does not read as smoothly, but the argument provides the context that this is talking about available OS versions.

In the spirit of clarity, all these would read unambiguously to me:

#if swiftHasFeature(ImplicitOpenExistentials)
#if languageHasFeature(ImplicitOpenExistentials)
#if hasSwiftFeature(ImplicitOpenExistentials)
#if hasLanguageFeature(ImplicitOpenExistentials)

I realize this is more verbose, but perhaps also more clear. I also note that hasFeature already reads much better than the originally proposed $X. hasFeature feels ambiguous to me because it is not immediately obvious to someone glancing at it that it is talking about Swift features, not the features of the code/app being developed.

1 Like

Great proposal! It will help a lot with preparing our codebases for upcoming changes to the language and make the transition smooth.

I hope my following comment is not too off topic, but on the Swift by Sundell podcast (~ 50:00 mark), Ben Cohen mentions that:

.. [In Swift 6] there will be a language mode that would mean that you will be able to continue building your code in Swift 5 mode even with the new compiler ..

I was looking for some mention of this in the proposal, but it's still unclear if this is really something planned and how it would work.

This is important, because delaying the adoption of some of these new language features may be essential for a large codebase where such migration might take more time. (e.g. fixing all concurrency-related issues is not as "trivial" task as adding any to your existentials). You wouldn't want your entire app to be stuck in Swift 5, but halting product feature development in order to migrate the whole codebase is also not desirable.

In the light of that, the proposal mentions: "[...] future feature flags only accumulate up to the next major Swift language version and are then cleared away, so we don't fork the language into incompatible dialects". I absolutely agree with this, however would be nice to understand if there are any plans for such language mode "downgrades" and how this would play with what is being proposed here.

For me anything is better than the proposed name.

hasFeature(ImplicitOpenExistentials) was proposed because of its symmetry with canImport(Darwin), the one directive you ignored ;)

2 Likes

There are two related kinds of "Swift version" at work here that are distinct, but we often conflate them for convenience. They matter a lot to understanding this proposal, so let me call them out here:

  • Swift tools version: the version number of the compiler itself. The most recent release is Swift 5.6, the one in beta is Swift 5.7.
  • Swift language version: the language version with which we are providing source compatibility.

The Swift tools support multiple Swift language versions. All recent versions (since Swift tools version 5.0) have supported multiple Swift language versions, of which there are currently only three: 4, 4.2, and 5. As the tools evolve, we try to avoid making source-incompatible changes within a Swift language version, although we do introduce extensions to them---for example, you can use async/await in any of those Swift language versions and it's fine.

We have been queuing up source-incompatible changes for Swift language version 6. Swift tools version 6.0 will be the first tools to officially allow the use of Swift language version 6. Those tools will continue to support Swift language versions 4, 4.2, and 5. Your code does not need to move to Swift language version 6 to use Swift tools version 6.0, or 6.1, and so on.

The Swift tools permit interoperability between modules compiled with different Swift language versions. A module compiled with Swift language version 6 can use (and be used by) modules compiled with Swift language version 5 (or 4, or 4.2).

This specific proposal eases the migration path for future features by allowing you to adopt individual future, source-incompatible changes one-by-one, within a given module, each step getting closer to the next Swift language version. At some point, you can switch over to using the new Swift language version (say, 6).

Doug

13 Likes

The toolchain version and language version both being called the Swift version and both using numbers in the same range that are sometimes connected to each other but often aren't is something that I've found confusing quite often, and confusing to talk about very often. I've often wished they were just totally decoupled from each other.

As I mentioned previously, I am very much in favor of the proposal overall, but with some feedback and suggestions on the exact details.

I don't think that the 'has' prefix in #if hasFeature() makes the condition more clear and if additional conditions are added with the has prefix as pitched, the prefix makes these conditions more difficult to read.

All but one of the existing compilation conditions essentially omit the verb ‘is’:
#if compiler(>=5.7)
#if os(macOS)
#if arch(arm64)
#if swift(>=4.2)
#if targetEnvironment(simulator)

The one remaining condition is difficult to express without the verb ‘can’ because it is difficult to come up with a single word to indicate importability:
#if canImport(UIKit)

The use of ‘can’ avoids having to use an awkward and less clear construction such as #if importable(UIKit).

For me, this proposed new condition is much closer to the first set of existing conditions above. I think that just as an ‘is’ prefix hasn't been required to clarify the existing conditions, a ‘has’ prefix doesn't add clarity to the proposed condition,

Since the existing conditions have not proved to be confusing:
#if os(macOS)

I don't believe omitting the prefix makes the code less clear or that including it would make the code more clear:

#if feature(ImplicitOpenExistentials)
  f(aCollectionOfInts)
#else
  f(AnyCollection<Int>(aCollectionOfInts))
#endif

Although not part of this proposal, the proposed naming convention is also used in a current pitch to conditionally compile based on attribute availability. For me, the following code is very clear without the prefix:

#if attribute(preconcurrency)
@preconcurrency
#endif
protocol P: Sendable { 
    func f()
    func g()
}

In addition to not seeming to add much additional clarity, if multiple conditions begin with 'has', the prefix becomes visual noise that must always be scanned through to get to the actual thing being conditionalized.

1 Like

How can we extend this to OS version checking? There is a real gap when adopting new capabilities between WWDC and the Fall OS releases.

I understand the desire for such a feature, but it is not in scope for this proposal, which is focused on language features.

Doug

Thanks everyone for the insightful review discussion! The Language Workgroup talked about this proposal and the review feedback, and arrived at the following conclusions:

  • The SwiftPM manifest API based on SwiftSetting rather than the target is a better design. This offers more control over when to enable the features, and SwiftSetting already represents build settings specific to Swift.
  • The term "upcoming feature" is less confusing than "future feature". During our verbal discussion, it became abundantly clear that "future feature" is hard to pronounce! We agree that it's best to avoid two consecutive words that are so similar, and "upcoming" is just as clear as "future".
  • The short-circuiting of parsing call syntax should apply to both || and &&.

The author has updated the proposal to incorporate these changes, and the review has been extended through July 11th to gather more discussion on the above modifications. The proposal has also incorporated explanations of Swift language version and Swift tools version, and how these two concepts interact.

These points have received some discussion already, so we'll continue the extended review in this thread.

Thank you,

Holly Borla
Review Manager

4 Likes

I'm still +1 and am glad to see the changes in the proposal.

One small piece of feedback about the proposal itself:

For SwiftPM, the addition of the enableUpcomingFeature function to SwiftSetting represents a one-time break in the manifest file format.

should also mention the enableExperimentalFeature method since it will also be added by this proposal.

Good point, thank you. I've added it via [SE-0362] Clarify that `enableExperimentalFeature` is part of SwiftPM… by DougGregor · Pull Request #1715 · apple/swift-evolution · GitHub.

Doug

1 Like

Thanks everyone for participating in this review! The review has concluded and SE-0362 has been accepted.

2 Likes