SE-0362: Piecemeal adoption of future language improvements

Hello, Swift community!

The review of SE-0362: Piecemeal adoption of future language improvements begins now and runs through July 5th, 2022.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager by email or DM. When contacting the review manager directly, please keep the proposal link at the top of the message and put "SE-0362" in the subject line.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at:

https://github.com/apple/swift-evolution/blob/main/process.md

Thank you,

Holly Borla
Review Manager

19 Likes

Quick question about this from the proposal:

The compiler will produce an error if -enable-future-feature X is provided and the language version enables the feature X by default.

Why an error instead of a warning?

It seems like libraries would want to make themselves “Swift 6 ready” with future feature flags, and this might put a library in a situation cannot build for both Swift 5 and Swift 6 clients.

I suppose a module needs to specify a language version of 6 for those feature flags to become errors, and presumably the language version is typically specified in the same package manifest or makefile as the future feature flags, so adopting Swift 6 and discarding the future flags would normally happen in synchrony. Still, I'm a little leery that this might paint some shared code into a corner.

7 Likes

Not attempting a review of the full proposal, but just want to jot down this one (minor) point so that I don't forget:

To prevent this issue for any future extensions to the #if syntax, the compiler should not attempt to interpret any "call" syntax on the right-hand side of a && whose left-hand side disables parsing of the #if body, such as compiler(>=5.7) or swift(>=6.0). For example, if we invent something like #if hasAttribute(Y) in the future, one can use this formulation:

#if compiler(>=5.8) && hasAttribute(Sendable)
...
#endif

On Swift 5.8 or newer compilers (which we assume will support hasAttribute), the full condition will be evaluated. On prior Swift compilers (i.e., ones that support this proposal but not something newer like hasAttribute), the code after the && will be parsed as an expression, but will not be evaluated, so such compilers will not reject this #if condition.

So that we're not unduly restricting future compatibility to one formulation only, it'd be good to have the same allowance for short-circuiting where the condition is expressed as #if compiler(<99) || ....

This may not be the primary way in which users are likely to use feature gating with hasAttribute, but other future extensions of #if may be most naturally expressed in such a way, and it is similar to #if compiler(>=99) && ... in that the right-hand side can be appropriately ignored in prior versions of Swift.

5 Likes

Excellent proposal, based on a quick reading. +1 A way to get incremental access to new features without creating dialects is welcome.

I have a question about the SPM impact. The text indicates that futureFeatures causes -enable-future-feature to be passed to the compiler. Does this means the package manifest cannot support both Swift 5 with the feature enabled, and Swift 6 (since on Swift 6, it would be an error to pass this flag)? What is the expected way for a package to support both versions?

4 Likes

What is your evaluation of the proposal?

Big +1 from me.

Is the problem being addressed significant enough to warrant a change to Swift?

Yes. Especially the option to enable new Swift language features on a per target basis in SwiftPM is a significant move forward. This allows experimentation with new and experimental features in complex projects, which can lead to better feedback here on the forums.

Does this proposal fit well with the feel and direction of Swift?

Yes.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

A quick complete read.

Just a minor comment towards the aesthetics:

SwiftPM targets should be able to specify the future language features they require. Extend the target part of the manifest to take a set of future features as strings, e.g.:

.target(name: "myPackage",
       futureFeatures: ["ConciseMagicFile", "ExistentialAny"])

— this starts to look a bit odd with time as the language matures and "future features" become not so "future" anymore, yet they will still stay so in the manifest unless manually removed. Could there be a more neutral, non-temporal term for this?

The proposal makes it an error to use a future feature flag when the feature is enabled by default and therefore no longer in the “future,” and the explicitly temporal name is meant to emphasize the point that these are not arbitrary language dialects.

2 Likes

This doesn't answer the question — in fact, it introduces more.

A package does not necessarily list a language version; if all SwiftPM does is to automatically pass the listed feature flags (at least that's the extent to which it's specified in the proposal), what's the mechanism for disabling SwiftPM from passing these flags when they are started to be considered an error?

Anyway, the question remains — if it's an error to leave a future feature in a manifest as soon as a particular language version goes "widespread" (whatever that means) — the way I interpret your message is that the futureFeatures parameter is meant to be temporary and

  1. has to be tracked for relevance and be removed in a timely manned by a maintainer (which is IMO and undue burden),
  2. limits a package's viability to a very particular list of compiler versions, as the package would only be valid for compiler versions that
    a. already know of the flag (and thus can compile the feature), but
    b. still not "old" enough that the feature has become mainstream and thus would not error out on the feature being marked as a "future" one.

SwiftPM targets should be able to specify the future language features they require. Extend the target part of the manifest to take a set of future features as strings, e.g.:

.target(name: "myPackage",
        futureFeatures: ["ConciseMagicFile", "ExistentialAny"])

Alternatively, should there be methods on SwiftSetting, with optional condition parameters?

extension SwiftSetting {

  public static func enableFutureFeature(
    _ name: String,
    _ condition: BuildSettingCondition? = nil
  ) -> SwiftSetting

  public static func enableExperimentalFeature(
    _ name: String,
    _ condition: BuildSettingCondition? = nil
  ) -> SwiftSetting
}

Contrived example:

.target(
  name: "MyTarget",
  swiftSettings: [
    .define("FLAG_ONE"),
    .define("FLAG_TWO"),
    .enableFutureFeature("BareSlashRegexLiterals"),
    .enableFutureFeature("ForwardTrailingClosures"),
    .enableFutureFeature("ConciseMagicFile", .when(platforms: [.macOS])),
    .enableFutureFeature("ExistentialAny", .when(configuration: .debug)),
  ]
)
3 Likes

This is exactly correct. For the specific module itself, a change to the manifest that that adopts "Swift version 6" would also remove all of the future feature flags, in one step. That impacts the modules in the package, but not the clients of that package.

It means that the package manifest will either specify "Swift 5 with some future features" or "Swift 6".

Package manifests always have a swift-tools-version, which informs the default version passed to the compiler. So an existing package with a swift-tools-version of 5.x will pass -swift-version 5 (unless it's overridden at the target level), one with swift-tools-version of 4.x will pass that along to -swift-version, and so on. We don't get to process an arbitrary package with just any Swift language version, because the point of language versions (4.0, 4.2, 5, eventually 6) is that they introduce small source-compatibility issues.

It's not about "widespread". A future feature is one that is enabled in the next major language version (e.g., Swift 6) but is not enabled by default in prior language versions (e.g., Swift 5) due to source-compatibility impact. The mechanism in this proposal lets folks opt in to these features on a per-module basis in prior language versions.

This is not correct. You remove the future feature once you move to the major language version. Future features will not be implicitly turned on in older language modes.

There is no notion of "becoming mainstream" for future features. There is work to do for package maintainers that want to retain compatibility with older compilers:

  • If you adopt a future feature in your module and want to still compile with older compilers, use #if hasFeature(X) from this proposal to conditionally compile code.

  • If you adopt any future feature and you still want to build with compilers that predate the addition of "future features" in the tools, you'll need to use a versioned package manifest file (e.g., Package@5.7.swift).

    Doug

8 Likes

I don't author many package manifests, so I'd love to hear more feedback on this approach. It's more flexible than the one I proposed, but more verbose. Perhaps that's a good tradeoff here?

EDIT: You're totally right, this is a better API. I put up a pull request to adopt this API in the proposal, with an implementation here. Toolchains are forthcoming.

Doug

3 Likes

Good to have that confirmed. My concern was about whether there might be some build processes out there off the SwiftPM map where the two settings (language version and future feature flags) aren’t managed in the same place…though I guess it’s fair to say those who got themselves in that pickle are on their own.

They'll get errors and need to fix their build, I guess. I don't think letting the confusion go on longer (via a warning) would improve on this situation.

Doug

+1

Read the pitch and now the proposal fairly quickly, looks very nice, it's great this gets a structured treatment. Only note is as mentioned in the proposal that it'd be great to have all of the options for the next future release collated somewhere - could we dream of a future swift.org "roadmap" page - that'd be great for many reasons and could then include this as part of the page.

1 Like

+1 overall.

This makes total sense, and other people already mentioned the important corners.

But: I am very uncomfortable with "Future Feature" phrase, and I suspect I am not the only one. Is there any chance we might find a better alternative? How about "Upcoming Feature"?

2 Likes

I think the existing Swift Evolution page at Swift Evolution is already the roadmap page for Swift. It summarizes what is being reviewed, what is accepted, and what implemented in which release. You can filter by status and by release for implemented items.

Since the identifier for a future feature will be included in its Swift Evolution proposal, adding a "Feature identifier" field to the summary fields of a proposal should be relatively straightforward.

Since the page already has the ability to filter, it would hopefully not be too complex to add an additional filter to show only proposals with valid future feature identifiers.

And since each entry is already linked to its proposal, a user could find a proposal with a feature identifier on the page and click through to read about that proposal and decide if they wanted to enable that future feature.

This would allow the Swift Evolution page to continue to be a central for a summary of proposal information, while increasing its usefulness, instead of introducing a separate page that would need to be kept in sync by maintainers and remembered/bookmarked by users.

7 Likes

Only issue is the things that there are no placeholders for - e.g. Ted:s post 'on the road to swift 6' - that is more what I view as a long-term roadmap, while the evolution proposals are more short- to mid-term.

1 Like

Yes, the Swift Evolution page does not capture a longer-term vision for a release the way an "On The Road To Swift 6" post does.

But since future feature flags are tied to specific concrete proposals, I think it makes sense for them to be part of the existing summary page of those proposals.

5 Likes

+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