Piecemeal adoption of Swift 6 improvements in Swift 5.x

What happens when the Swift version is greater than 5.8 but hasFeature(VariadicGenerics) is false? In this case you don't have any code at all.

I think you perhaps missed something. The reason an #if compiler conditional is required (in circumstances when it is) in conjunction with hasFeature (or $Feature, however spelt) is because the contents of #if blocks—other than #if compiler—are still parsed even if not taken. Therefore, code using a feature like variadic generics would cause an older compiler to stop parsing even if correctly gated with a feature conditional—that is, unless it is used within an additional #if compiler conditional requiring the minimum compiler version to be at least as high as the version in which the feature is introduced. So the answer to your question is, in the case when the Swift version is greater than 5.8 but hasFeature(VariadicGenerics) is false, you're holding it wrong and the file won't compile at all; this would be the case regardless of how you spell it and with any permutation of #ifs, #elseifs, and #elses.

Now, I'm advocating for some #if hasFeature blocks not taken also not to be parsed in future versions of Swift, perhaps on a feature-by-feature basis, but on reflection that would be unworkable as by construction any older compiler version would not have feature-by-feature knowledge of future features. Thus, unless we default to never parsing #if hasFeature blocks not taken (which may not be desirable, for the reasons that @DougGregor discusses above), the #if compiler conditional will be necessary.

Ah, I understand the disconnect now! I was thinking of the "compiler(<5.8)" check as only being there to make sure hasFeature was available, but it's communicating more than that in your example: 5.8 is also the place where the feature in our example was introduced.

However, this doesn't account for features that will be enabled in Swift 6 but are opt-in in prior language modes. There, we have to distinguish between "the compiler is 5.8 and the feature is disabled" and "the compiler is 5.8 and the feature is enabled". For such cases, the need to nest hasFeature forces you to have 3 branches (two of which will often be the same) vs. the $ formulation only needing 2 (is the feature on or off?).

Ah, that's a good point.

As much as I'm loathe to introduce two features for the same thing at the same time... this does give the nicer syntax over time. We could say that the $ form is only allowed in Swift 5.x mode, and hasFeature is in all language modes.

Thank you, everyone, I think we have some fairly solid direction here, so I'm going to turn this pitch + the suggestions here into a full proposal.

Doug

21 Likes

I think this is a great idea.

• I prefer one of the hasFeature() spellings over $
• Please provide a way to list all available feature flags, e.g. swiftc -list-features, that ideally lists the feature flag name as well as a short description that points to the SE-XXXX.

3 Likes

Thank you everyone for the great feedback. I turned this into a proper proposal over at Initial version by DougGregor · Pull Request #1660 · apple/swift-evolution · GitHub.

Doug

9 Likes

Looks really good. Quick read through — big +1. Nice integration with SPM extra bonus points! Thanks.

The compiler can define names with a leading $, but developers cannot, so it's effectively a reserved space for "magic" names.

I'm able to define $ names in Swift 5.6 (via Package.swift, I didn't try the command-line).

swiftSettings: [
  .define("$BareSlashRegexLiterals"),
]
#if $BareSlashRegexLiterals
#warning("$BareSlashRegexLiterals is defined") // ⚠️
#else
#error("$BareSlashRegexLiterals is undefined")
#endif

Could feature flags be added for the RequirementMachine?
This is currently inaccessible, except for unsafe flags.

swiftSettings: [
  .unsafeFlags([
    "-Xfrontend", "-requirement-machine-abstract-signatures=on",
    "-Xfrontend", "-requirement-machine-inferred-signatures=on",
    "-Xfrontend", "-requirement-machine-protocol-signatures=on",
  ]),
]

I'm a little hesitant about being so aggressive on removing feature checks. A warning would be better than an error when you try to enable a feature that is enabled by default, which will give users of lower versions time to migrate.

In the evolution of Swift we may encounter some feature that is not implementable or cannot be enabled on some platform as being blocked by platform-specific issues. This is especially a case for restricted or low-resource platforms (e.g. embedded / WASM), which may unavoidably miss some functionalities. It’s reasonable to provide a way to check for these partially-enabled features even they’re enabled by default.

I have a couple clarifying questions and the proposed behavior.

The compiler currently supports multiple language compatibility modes, such as "Swift 5", "Swift 4.2", "Swift 4". Would the feature flags apply only to "Swift 5" mode, or to all language compatibility modes? Would the compiler reject these flags in Swift 6 mode, or warn about them (since they become vestigial at that point)?

Oh, oops. I think we meant to prohibit that. I'll fix the wording!

Doug

No, the requirement machine isn't meant to be a language dialect and shouldn't have flags. The flags are there so we could stage in the requirement machine as a new implementation of the generics system, leaving the old implementation as a workaround for a release or two while we iron out all of the wrinkles. In 5.7, the requirement machine is on by default. On main, the requirement machine is the only implementation and the flags are no-ops.

I don't see why one would need that time. Moving to a new language version (e.g., adopting -swift-version 6) is an explicit action the developer takes. At that point, the compiler errors will also have them remove now-default future feature flags.

If at some point we decide that there is a limited set of features available to restricted environments, it would be possible to add hasFeature(X) checks for features that might not be available. Whether such a subset exists, or what features should be in or out of that subset, should be a separate discussion.

The proposal currently specifies that the feature flags are respected for all language modes up until the language mode where the feature is enabled by default. And in the language modes where the feature is enabled by default, it is an error to pass -enable-future-feature for that feature. We could be more picky, i.e., only allow -enable-future-feature X to be used in (say) Swift 5 when the feature is on-by-default in Swift 6, but I'm not sure there is much benefit to it.

Doug

5 Likes

And for those interested in the implementation details, I have a pull request up at Piecemeal adoption of future language improvements by DougGregor · Pull Request #59055 · apple/swift · GitHub for the compiler pieces.

Doug

5 Likes

I realize you have not put the proposal through the pitch phase yet but I had one suggestion to consider before you do.

All compiler conditions except canImport() omit the word is or has to define the condition.

I think #if feature(FeatureX) is as clear as #if targetEnvironment(simulator), #if compiler(swift >= 5.1) or any of the other existing conditions without a verb.

The condition canImport() seems to be an outlier because there isn't a single noun that adequately explains the condition.

Possibly consider #if feature() instead of #if hasFeature() since it follows the existing naming convention of all but one compilation condition and seems it would be just as clear and understandable to read.

5 Likes

That sounds like a really concise way to phrase it. It would also be an alternative to using fragile build flags in package manifests. Import availabilities and (maybe) platforms are extremely dynamic; so are the plethora of Swift 6 features we want to test out in Swift 5. By dynamic, I mean it would take unreasonably long to make each one an official part of the Swift compiler and then support its specific build flag in the far future. We need something where support for the opt-in feature can be quickly added or dropped and not promised to be maintained indefinitely.

1 Like

I don't know if you read through the full proposal Douglas Gregor has written up:

I am guessing the proposal will go into pitch phase sometime after WWDC, since it is a pretty busy week for the core team, but I didn't want to forget my naming suggestion.

1 Like

The only omitted verb is "is": there is only one target environment, compiler version, etc. at build time, so the conditional asks what that target environment is, or what that compiler version is, etc. It would be inapt to write #if feature, by contrast, as one is not asking what "the" feature is at compile time, since there's more than one feature that may or may not be enabled just like there's more than one library that can or cannot be imported.

1 Like

The core team generally avoids scheduling proposal reviews over the week of WWDC, since a large chunk of the community is focused on that conference. I expect this proposal to go into review shortly afterward based on the responses in this pitch.

Doug

5 Likes

A bikeshed on --enable-experimental-feature: Would --enable-experimental-future-feature be better? I think it communicates an extra layer of caution needed, and the longer flag name adds a little bit more friction so people are (slightly) less inclined to use it.

Would it be a good idea to also add --enable-all-(experimental-)future-features to enable all, along with --disable-(experimental-)future-feature flags to opt out specific features when the former flag is set.

I don't think so. Experimental features are not necessarily future features---they might turn out to be a bad idea, or get changed or renamed.

I don't think we should do that. These "future" features are source-breaking, so adding a new one into your build needs to be a deliberate choice... not something that comes from installing a new toolchain.

Doug

11 Likes

I've kicked this off for review here. Thanks all for the discussion so far, and please take any further feedback over to the review thread!

1 Like