|
| 1 | +# Migration tooling for Swift upcoming features |
| 2 | + |
| 3 | +* Proposal: [SE-NNNN](NNNN-filename.md) |
| 4 | +* Authors: [Anthony Latsis](https://github.com/AnthonyLatsis) |
| 5 | +* Review Manager: TBD |
| 6 | +* Status: **Awaiting implementation** |
| 7 | +* Implementation: TBD |
| 8 | +* Review: TBD |
| 9 | + |
| 10 | +## Introduction |
| 11 | + |
| 12 | +As Swift evolves, source-breaking changes to the language are staged behind new |
| 13 | +language modes and, more recently, [upcoming features][SE-0362] that may be |
| 14 | +enabled piecemeal without adopting an entire new language mode. |
| 15 | + |
| 16 | +This proposal centers on the experience of adopting individual features. |
| 17 | +The premise is that Swift needs a built-in mechanism for supporting quality |
| 18 | +assistance with code migration and adoption. |
| 19 | +And that, in principle, comprehensive, code-aware assistance can be delivered |
| 20 | +without breaking source and followed incrementally. |
| 21 | + |
| 22 | +[SE-0362]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0362-piecemeal-future-features.md |
| 23 | + |
| 24 | +## Motivation |
| 25 | + |
| 26 | +For many projects, source-breaking language features are expected to generate |
| 27 | +hundreds of targeted errors. |
| 28 | +It is also likely for developers to encounter knock-on collateral errors that |
| 29 | +are not indicative of the exact cause or source of the problem. |
| 30 | +We should strive for a better migration experience than requiring programmers |
| 31 | +to slog through such a volume of errors and blocking development on atomic |
| 32 | +adoption. |
| 33 | + |
| 34 | +### Automation |
| 35 | + |
| 36 | +Many existing and prospective upcoming features provide reliable solutions for |
| 37 | +preserving behavior: |
| 38 | + |
| 39 | +* [SE-0192] — `NonfrozenEnumExhaustivity`: Restore exhaustivity with |
| 40 | + `@unknown default:`. |
| 41 | +* [SE-0274] — `ConciseMagicFile`: `#file` → `#filePath`. |
| 42 | +* [SE-0286] — `ForwardTrailingClosures`: Disambiguate argument matching by |
| 43 | + de-trailing closures or/and inlining default arguments. |
| 44 | +* [SE-0335] — `ExistentialAny`: `P` → `any P`. |
| 45 | +* [SE-0352] — `ImplicitOpenExistentials`: Suppress opening with `as any P` coercions. |
| 46 | +* [SE-0354] — `BareSlashRegexLiterals`: `foo(/a, b/)` → `foo((/a), b/)` |
| 47 | + (disambiguate using parentheses). |
| 48 | +* [SE-0383] — `DeprecateApplicationMain`: (`@UIApplicationMain`|`@NSApplicationMain`) → `@main`. |
| 49 | +* [SE-0401] — `DisableOutwardActorInference`: Specify global actor isolation explicitly. |
| 50 | +* [SE-0409] — `InternalImportsByDefault`: `import X` → `public import X`. |
| 51 | +* [SE-0412] — `GlobalConcurrency`: Mark nonisolated global state with |
| 52 | + `nonisolated(unsafe)`. |
| 53 | +* [SE-0444] — `MemberImportVisibility`: Add explicit imports appropriately. |
| 54 | +* [SE-0418] — `InferSendableFromCaptures`: Suppress inference with coercions |
| 55 | + and type annotations. |
| 56 | +* [Inherit isolation by default for async functions][async-inherit-isolation-pitch] |
| 57 | + : Mark nonisolated functions with the proposed attribute. |
| 58 | + |
| 59 | +UPCOMING_FEATURE(RegionBasedIsolation, 414, 6) |
| 60 | + |
| 61 | +[SE-0192]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0192-non-exhaustive-enums.md |
| 62 | +[SE-0274]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0274-magic-file.md |
| 63 | +[SE-0286]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0286-forward-scan-trailing-closures.md |
| 64 | +[SE-0335]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0335-existential-any.md |
| 65 | +[SE-0352]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0352-implicit-open-existentials.md |
| 66 | +[SE-0354]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0354-regex-literals.md |
| 67 | +[SE-0383]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0383-deprecate-uiapplicationmain-and-nsapplicationmain.md |
| 68 | +[SE-0401]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0401-remove-property-wrapper-isolation.md |
| 69 | +[SE-0409]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0409-access-level-on-imports.md |
| 70 | +[SE-0411]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0411-isolated-default-values.md |
| 71 | +[SE-0412]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0412-strict-concurrency-for-global-variables.md |
| 72 | +[SE-0418]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0418-inferring-sendable-for-methods.md |
| 73 | +[SE-0444]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0444-member-import-visibility.md |
| 74 | +[async-inherit-isolation-pitch]: https://forums.swift.org/t/pitch-inherit-isolation-by-default-for-async-functions/74862 |
| 75 | + |
| 76 | +### Flexibility |
| 77 | + |
| 78 | +Currently best solution is to warn under the feature + error in language mode |
| 79 | +where feature is enabled. This approach cannot be applied to all features (why?) |
| 80 | + |
| 81 | +UPCOMING_FEATURE(DynamicActorIsolation, 423, 6) |
| 82 | +UPCOMING_FEATURE(GlobalActorIsolatedTypesUsability, 0434, 6) |
| 83 | +UPCOMING_FEATURE(ImportObjcForwardDeclarations, 384, 6) |
| 84 | +UPCOMING_FEATURE(StrictConcurrency, 0337, 6) |
| 85 | +UPCOMING_FEATURE(IsolatedDefaultValues, 411, 6) |
| 86 | + |
| 87 | +Adjusting to new behaviors or language requirements can demand research, |
| 88 | +careful consideration, coordinated efforts, and manual code refactorings, |
| 89 | +sometimes on a case-by-case basis. |
| 90 | + |
| 91 | +Something about the following being good illustrations of the problems |
| 92 | +described above: |
| 93 | + |
| 94 | +* [SE-0409]: Introduces the concept of access levels on import declarations to |
| 95 | + give library developers control over which module dependencies are exposed to |
| 96 | + clients and an upcoming feature for defaulting to internal imports to bias |
| 97 | + toward hiding dependencies rather than exposing them. This feature can break |
| 98 | + source across modules and may require a coordinated adoption effort in large |
| 99 | + projects. |
| 100 | + |
| 101 | +* [SE-0444] Fixing member import visibility: The goal of this proposal is to reduce the likelihood of ambiguities arising from the use of extension members. Swift’s existing rules about when imports are required when referencing external declarations in a source file are inconsistent and they lead to extension members being visible even when the module they are declared in has not been imported. This proposal amends the rules for member lookup to make them consistent with top-level name lookup, giving developers more direct control over which extension members are visible. |
| 102 | + |
| 103 | +* [SE-0335] introduced syntax for existential or boxed types to visually |
| 104 | + distinguish them from conformance constraints and deliver contrast between |
| 105 | + dynamic existential types and fixed `some` types. The associated |
| 106 | + `ExistentialAny` feature is a vivid example where automatic migration is |
| 107 | + possible, but not desirable. The niche that exitential types occupy combined |
| 108 | + with their lighweight syntax make for an enticing and viable, yet often |
| 109 | + misused, abstraction construct. In places where type dynamism may be |
| 110 | + superfluous, we want to guide developers toward an informed choice of |
| 111 | + abstraction. |
| 112 | + |
| 113 | +[SE-0335]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0335-existential-any.md |
| 114 | +[SE-0337]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0337-support-incremental-migration-to-concurrency-checking.md |
| 115 | +[SE-0409]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0409-access-level-on-imports.md |
| 116 | +[SE-0444]: https://github.com/swiftlang/swift-evolution/blob/main/proposals/0444-member-import-visibility.md |
| 117 | + |
| 118 | +### A standard for staging upcoming features |
| 119 | + |
| 120 | + |
| 121 | + |
| 122 | +## Proposed Solution |
| 123 | + |
| 124 | +Introduce the notion of a "preview" mode for features. |
| 125 | +Preview mode can be used to provide adoption tips and specific source changes |
| 126 | +that can be applied to preserve or enhance the behavior of existing code when |
| 127 | +the feature is effectively enabled. |
| 128 | + |
| 129 | +## Detailed Design |
| 130 | + |
| 131 | +### Behavior |
| 132 | + |
| 133 | +Enabling an additive feature or previously disabled source-breaking feature in |
| 134 | +preview mode is necessarily a source-compatible action and must never result in |
| 135 | +behavioral changes or new errors. |
| 136 | + |
| 137 | +The effect of preview mode on the state of a feature will depend on whether it |
| 138 | +can break source: |
| 139 | +* Additive features will be enabled in preview mode. |
| 140 | +* Source-breaking features will be disabled in preview mode. |
| 141 | + If a source-breaking feature does not implement preview mode, a corresponding |
| 142 | + warning will also be emitted to avoid the false impression that the impacted |
| 143 | + code is source-compatible with the feature. |
| 144 | + |
| 145 | +Experimental features can be both additive and source-breaking. Upcoming |
| 146 | +features are necessarily source-breaking. Baseline features, such as |
| 147 | +`async`/`await`, are necessarily additive and cannot be disabled. They are |
| 148 | +currently defined for the sole purpose of supporting feature availability |
| 149 | +checks in conditional compilation blocks: `#if hasFeature`. |
| 150 | + |
| 151 | +Preview mode will deliver guidance in the shape of warnings, notes, remarks, |
| 152 | +and fix-its, as and when appropriate. |
| 153 | + |
| 154 | +When implemented, preview mode for upcoming features is expected to anticipate |
| 155 | +and call out any hard and soft source breaks — compiler errors and silent |
| 156 | +behavioral changes — that would result from actually enabling the feature, |
| 157 | +coupling diagnostic messages with counteracting source-compatible changes |
| 158 | +whenever possible. |
| 159 | +However, as previously highlighted with `ExistentialAny`, preview mode cannot |
| 160 | +promise to offer exclusively source-compatible changes because the source |
| 161 | +impact of a change is generally nondeterministic. |
| 162 | +Neither can it promise to always offer source changes in the first place for the |
| 163 | +same reason in regards to user intention. |
| 164 | +Although upcoming features should strive to facilitate automatic code |
| 165 | +migration, every source-breaking feature poses a unique trade-off between |
| 166 | +estimated impact and code migration solutions. |
| 167 | + |
| 168 | +### Interface |
| 169 | + |
| 170 | +#### Compiler |
| 171 | + |
| 172 | +The `-enable-*-feature` frontend and driver command line options will start |
| 173 | +supporting an optional mode specifier with `preview` as the only valid mode: |
| 174 | + |
| 175 | +``` |
| 176 | +-enable-upcoming-feature <feature>[:<mode>] |
| 177 | +-enable-experimental-feature <feature>[:<mode>] |
| 178 | +
|
| 179 | +<mode> := preview |
| 180 | +``` |
| 181 | + |
| 182 | +For example: |
| 183 | + |
| 184 | +``` |
| 185 | +-enable-upcoming-feature InternalImportsByDefault:preview |
| 186 | +``` |
| 187 | + |
| 188 | +In a series of either of these options applied to a given feature, only the |
| 189 | +last option will be honored. |
| 190 | +If a feature is both implied by the effective language mode and enabled in |
| 191 | +preview mode using either of the aforementioned options, the latter will be |
| 192 | +disregarded, analogous to how the language mode overrides |
| 193 | +`-disable-upcoming-feature` today. |
| 194 | + |
| 195 | +#### Swift Package Manager |
| 196 | + |
| 197 | +The [`SwiftSetting.enableUpcomingFeature`] and |
| 198 | +[`SwiftSetting.enableExperimentalFeature`] methods from the |
| 199 | +[`PackageDescription`](https://developer.apple.com/documentation/packagedescription) |
| 200 | +library will be augmented with a `mode` parameter defaulted to match the |
| 201 | +current behavior: |
| 202 | + |
| 203 | +```swift |
| 204 | +extension SwiftSetting { |
| 205 | + @available(_PackageDescription, introduced: 6.2) |
| 206 | + public enum SwiftFeatureMode { |
| 207 | + case preview |
| 208 | + case on |
| 209 | + } |
| 210 | +} |
| 211 | +``` |
| 212 | +```diff |
| 213 | + public static func enableUpcomingFeature( |
| 214 | + _ name: String, |
| 215 | ++ mode: SwiftFeatureMode = .on, |
| 216 | + _ condition: BuildSettingCondition? = nil |
| 217 | + ) -> SwiftSetting { |
| 218 | ++ let argument = switch mode { |
| 219 | ++ case .preview: "\(name):preview" |
| 220 | ++ case .mode: name |
| 221 | ++ } |
| 222 | ++ |
| 223 | + return SwiftSetting( |
| 224 | +- name: "enableUpcomingFeature", value: [name], condition: condition) |
| 225 | ++ name: "enableUpcomingFeature", value: [argument], condition: condition) |
| 226 | + } |
| 227 | +``` |
| 228 | +```diff |
| 229 | + public static func enableExperimentalFeature( |
| 230 | + _ name: String, |
| 231 | ++ mode: SwiftFeatureMode = .on, |
| 232 | + _ condition: BuildSettingCondition? = nil |
| 233 | + ) -> SwiftSetting { |
| 234 | ++ let argument = switch mode { |
| 235 | ++ case .preview: "\(name):preview" |
| 236 | ++ case .mode: name |
| 237 | ++ } |
| 238 | ++ |
| 239 | + return SwiftSetting( |
| 240 | +- name: "enableExperimentalFeature", value: [name], condition: condition) |
| 241 | ++ name: "enableExperimentalFeature", value: [argument], condition: condition) |
| 242 | + } |
| 243 | +``` |
| 244 | + |
| 245 | +For example: |
| 246 | + |
| 247 | +``` |
| 248 | +SwiftSetting.enableUpcomingFeature("InternalImportsByDefault", mode: .preview) |
| 249 | +``` |
| 250 | + |
| 251 | +### Diagnostics |
| 252 | + |
| 253 | +* Diagnostics emitted by preview mode must be grouped |
| 254 | +* It will generally be hard for developers to work out diagnostic affiliation, |
| 255 | + especially when multiple features are enabled in preview mode. We may want to |
| 256 | + vend the diagnostic group (named after the feature) to IDEs and annotate |
| 257 | + diagnostics accordingly (`-print-diagnostics-groups`) in our formatters. |
| 258 | +* Establish conventions around severity. |
| 259 | + |
| 260 | +## Source compatibility |
| 261 | + |
| 262 | +This proposal has no impact on existing code. |
| 263 | + |
| 264 | +## ABI compatibility |
| 265 | + |
| 266 | +This proposal has no impact on existing code. |
| 267 | + |
| 268 | +## Implications on adoption |
| 269 | + |
| 270 | +TBD |
| 271 | + |
| 272 | +## Future directions |
| 273 | + |
| 274 | +TBD. Talk about `swift migrate`. |
| 275 | + |
| 276 | +## Alternatives considered |
| 277 | + |
| 278 | +? |
| 279 | + |
| 280 | +## Acknowledgements |
| 281 | + |
| 282 | +This proposal is inspired by documents devised by [Allan Shortlidge][Allan] |
| 283 | +and [Holly Borla][Holly] (@bhorla). |
| 284 | + |
| 285 | +[Holly]: https://github.com/hborla |
| 286 | +[Allan]: https://github.com/tshortli |
| 287 | + |
0 commit comments