Hi all,
As we travel down the road to Swift 6, we are accumulating a number of improvements to the language that have enough source-compatibility impact that we cannot enable them by default in Swift 5.x. So, they remain implemented in the Swift compiler behind the "Swift 6" flag that's only available to regression testing.
In a small number of cases, we've explicitly provided a compiler flag to enable these features in Swift 5.x: -warn-concurrency
came from SE-0337, and -enable-bare-slash-regex
is under discussion in the review for SE-0354. The desire for a flag to require any
on all existentials came up in the review of SE-0335. One-off compiler flags for experimental features are commonplace in the development of the Swift compiler, as a staging mechanism.
I think we should explicitly embrace the piecemeal, intentional adoption of these Swift 6 features in Swift 5.x modes by providing a generalized compiler flag to indicate which features to enable. This will allow developers to incrementally move their code closer to the Swift 6 model feature-by-feature, minimizing disruption along the road to Swift 6 while getting more of its benefits with each step.
What features are we talking about, really?
There are a number of proposals we have accepted that delayed source-incompatible changes until Swift 6, going back more than two years. These are the ones I know about:
-
SE-0274 "Concise magic file names" is still marked "accepted" because the change that shortens
#file
changes the meaning of existing source code, so we've held off on enabling it until Swift 6. - SE-0286 "Forward-scan matching for trailing closures" delays the removal of the "backward-scan matching" rule of trailing closures until Swift 6.
-
SE-0335 "Introduce existential
any
" delays the requirement to useany
for all existentials until Swift 6. - SE-0337 "Incremental migration to concurrency checking" delays some checking of the concurrency model to Swift 6 (with a flag to opt in to warnings about it in Swift 5.x).
- SE-0352 "Implicitly Opened Existentials" expands implicit opening to more cases in Swift 6, because we didn't want to change the semantics of well-formed code in Swift 5.x.
-
SE-0354 "Regex Literals" delays the introduction of the
/.../
regex literal syntax and prohibition of prefix/
operators until Swift 6 (with a flag to opt in to the syntax in Swift 5.x).
As we continue refining Swift 6, I expect we'll have more of these.
A general compiler flag
We should introduce a flag -enable-feature X
, where X
is a name for the feature to enable. Each proposal will document what X
is, so it's clear how to enable that feature. For example, SE-0274 could use ConciseMagicFile
, so that -enable-feature ConciseMagicFile
will enable that change in semantics. One can of course pass multiple -enable-feature
flags to the compiler to enable multiple features. Unrecognized compiler features will be ignored, and the flag will become unnecessary in Swift 6 mode because these features will automatically be enabled.
We should also extend the SwiftPM manifest format to have a general "language features" target-level option, e.g.,
.target(name: "myPackage",
languageFeatures: ["ConciseMagicFile", "ExistentialAny"])
Detecting features in source
During the development and rollout of Swift Concurrency, we found that it was important for source code to be able to detect the availability of a particular feature, so we hacked in some support using $
identifiers for language features. For example, one can detect async/await with, e.g.,
#if compiler(>=5.5.) && $AsyncAwait
func f() async -> String {
/* implement in terms of f(completionHandler:) */
}
#endif
Let's codify this: when a given feature X
is enabled, whether through -enable-feature X
or because it's implied by the language version, the compiler should define $X
so that one can check for this feature with #if $X
. That way, one can more easily adapt code to new features without losing support for older features and compilation modes.
Streamlining experimental features
We can apply the same approach for the whole lifecycle of features, from their experimental state until they are enabled by default in a language mode. In addition to -enable-feature X
, we should add -enable-experimental-feature X
. This flag should not be available in shipping compilers, but unifies the way we handle features. A feature can be developed and reviewed under -enable-experimental-feature X
and then graduate to either -enable-feature X
(if it has source-compatibility impacts that need to be delayed until Swift 6) or just become always-enabled. If we do this consistently, for all proposals, then #if $X
will still work even if there was never an -enable-feature X
.
But... aren't dialects bad?
Swift has, for the most part, avoided adding one-off feature flags because doing so creates additional dialects. Source-incompatible changes with enough impact that we cannot make them within a release are delayed until the next "major" Swift release, e.g., Swift 6, so we limit the number of dialects.
What we are trying to do here is limit permanent forks in the language. By limiting -enable-feature X
to features that are going to be enabled in Swift 6, it means that folks can move along a path from Swift 5.x to Swift 6 incrementally. So long as we reject the idea of a feature that can be enabled but remains off-by-default in the next major Swift version, we have a smooth path forward that doesn't introduce the usual downsides of having disjoint dialects.
Doug