You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: proposals/0000-swift-settings.md
+144-124
Original file line number
Diff line number
Diff line change
@@ -11,30 +11,28 @@
11
11
## Introduction
12
12
13
13
We propose introducing a new macro called `#SwiftSettings` that allows for
14
-
compiler flags to be enabled on a single file. This can be used to enable
15
-
warningsAsError, strict concurrency, and default isolation. For example:
14
+
compiler flags to be enabled on a single file. Initially this will only be used
15
+
to support controlling the default isolation. For example:
16
16
17
17
```swift
18
18
#SwiftSettings(
19
-
.warningsAsErrors(.concurrency),
20
-
.strictConcurrency(.complete),
21
19
.defaultIsolation(MainActor.self)
22
20
)
23
21
```
24
22
23
+
In the future, we wish to extend this to also include support for controlling
24
+
`warningsAsErrors`, `strictConcurrency`, and other flags. See future directions
25
+
for more details.
26
+
25
27
## Motivation
26
28
27
29
Today Swift contains multiple ways of changing compiler and language behavior
28
-
from the command line including:
29
-
30
-
* Enabling warnings as errors for diagnostic groups.
31
-
* Enabling strict concurrency when compiling pre-Swift 6 code.
32
-
* Specifying the default actor isolation via [Controlling Default Actor Isolation]().
33
-
34
-
Since these compiler and language behaviors can only be manipulated via the
35
-
command line and the compiler compiles one module at a time, users are forced to
36
-
apply these flags one module at a time instead of one file at a time. This
37
-
results in several forms of developer friction that we outline below.
30
+
from the command line including specifying the default actor isolation via
31
+
[Controlling Default Actor Isolation](). Since these compiler and language
32
+
behaviors can only be manipulated via the command line and the compiler compiles
33
+
one module at a time, users are forced to apply these flags one module at a time
34
+
instead of one file at a time. This forces unnecessary module splitting
35
+
resulting in developer friction.
38
36
39
37
### Unnecessary Module Splitting
40
38
@@ -61,48 +59,6 @@ splitting would be forced:
61
59
one must either split the database helpers into a separate module or must mark
62
60
most declarations in the database code with nonisolated by hand.
63
61
64
-
### Preventing per-file based warningsAsErrors migration of large modules to strict concurrency
65
-
66
-
Swift has taken the position that updating modules for strict concurrency should
67
-
be accomplished by:
68
-
69
-
1. Enabling strict concurrency on the entire module causing the compiler to emit
70
-
warnings.
71
-
72
-
2. Updating the module incrementally until all of the warnings have been
73
-
eliminated from the module.
74
-
75
-
3. Updating the language version of the module to swift 6 so from that point on
76
-
warnings will become errors to prevent backsliding.
77
-
78
-
This creates developer friction when applied to large modules, since the size of
79
-
the module makes it difficult to eliminate all of the warnings at once. This
80
-
results in either the module being updated over time on main during which main
81
-
is left in an intermediate, partially updated state or the module being updated
82
-
on a branch that is merged once all updates have been completed. In the former
83
-
case, it is easy to introduce new warnings as new code is added to the codebase
84
-
since only warnings are being used. In the later case, the branch must be kept
85
-
up to date in the face of changes being made to the codebase on main, forcing
86
-
one to continually need to update the branch to resolve merge conflicts against
87
-
main.
88
-
89
-
In contrast by allowing for strict concurrency to be applied one file at a time,
90
-
users can update code using a warningsAsErrors based migration path. This
91
-
involves instead:
92
-
93
-
1. Enabling strict concurrency and warningsAsErrors for strict concurrency on
94
-
the specific file.
95
-
96
-
2. Fixing all of the errors in the file.
97
-
98
-
3. Commiting the updated file into main.
99
-
100
-
Since one is only updating one file at a time and using warningsAsErrors, the
101
-
updates can be done incrementally on a per file basis, backsliding cannot occur
102
-
in the file, and most importantly main is never in an intermediate, partially
103
-
updated state. We think that this approach will make it significantly easier for
104
-
larger modules to be migrated to strict concurrency.
105
-
106
62
## Proposed solution
107
63
108
64
We propose introducing a new macro called `#SwiftSettings` that takes a list of
@@ -115,36 +71,27 @@ appropriate settings before compiling the rest of the file. An example of such a
115
71
116
72
```swift
117
73
#SwiftSettings(
118
-
.warningsAsErrors(.concurrency),
119
-
.strictConcurrency(.complete),
120
74
.defaultIsolation(MainActor.self)
121
75
)
122
76
```
123
77
124
-
By specifying compiler options in such a manner, we are able to solve all of the
125
-
problems above:
126
-
127
-
1. The programmer can avoid module splitting when they wish to use compiler
128
-
flags only on a specific file.
129
-
130
-
2. The programmer can specify strict concurrency on a per file basis instead of
131
-
only on a per module basis allowing for warningsAsError to be used per file
132
-
instead of per module migration to use new features.
78
+
By specifying compiler options in such a manner, we are able to avoid the need
79
+
for module splitting if a user wishes to change the default isolation only on a
80
+
specific file.
133
81
134
82
As an additional benefit since strongly typed methods are being used instead of
135
83
providing a single method that takes a string to specify the command line
136
84
parameter code completion can make the compiler options discoverable to the user
137
85
in the IDE.
138
86
139
-
NOTE: By default, only a few specified command line options will be
140
-
supported. In the future, this can be loosened as appropriate (see future
141
-
directions).
87
+
NOTE: Initially we will only support default isolation. In the future, this can
88
+
be loosened as appropriate (see future directions).
142
89
143
90
## Detailed design
144
91
145
92
We will introduce into the standard library a new declaration macro called
146
93
`#SwiftSettings` and a new struct called `SwiftSetting` that `#SwiftSettings`
147
-
takes as a parameter:
94
+
takes as a variadic parameter:
148
95
149
96
```swift
150
97
@freestanding(declaration)
@@ -155,7 +102,7 @@ public struct SwiftSetting {
155
102
}
156
103
```
157
104
158
-
`#SwiftSettings` will expand to an empty string and is used only for syntactic
105
+
`#SwiftSettings` will expand to an empty string and is used only for its syntax
159
106
and documentation in the IDE.
160
107
161
108
`SwiftSetting` is a resilient struct that cannot be constructed without a
@@ -183,26 +130,18 @@ Each static method on `SwiftSetting` is expecting to return a `SwiftSetting`
183
130
struct. The actual value returned is not important since this code will not be
184
131
executed.
185
132
186
-
By default, `#SwiftSetting` will only support an explicit group of command line
187
-
flags. These are:
188
-
189
-
* strict-concurrency
190
-
* warnings-as-errors
191
-
* default isolation.
192
-
193
-
These options are compatible with `#SwiftSettings` since they possess the
194
-
following three characteristics:
133
+
`#SwiftSetting` will only support an explicit opted-in group of command line
134
+
flags. This initially will only include default isolation. All such options must
135
+
possess the following characteristics to be compatible with `#SwiftSettings`:
195
136
196
-
*Impact parsing
137
+
*No impact on parsing
197
138
198
-
* Emit additional warnings and errors
199
-
200
-
* Modify the program in a manner that can be reflected in a textual interface
139
+
* Only modify the program in a manner that can be reflected in a textual interface
201
140
without the need for `#SwiftSettings` to be serialized into a textual
202
141
interface file.
203
142
204
-
*Are not options that cause module wide effects that cannot be constrained to a
205
-
single file. An example of such a setting is `enable-library-evolution`.
143
+
*Does not cause module wide effects that cannot be constrained to a single
144
+
file. An example of such a setting is `enable-library-evolution`.
206
145
207
146
In the future, more options can be added as appropriate (see future
208
147
directions). In order to ensure that any such options also obey the above
@@ -211,11 +150,97 @@ restrictions, we expect that any feature that wants to be supported by
211
150
and source stability impacts of adding support to `#SwiftSettings`. At minimum,
212
151
the feature must obey the above requirements.
213
152
153
+
Each argument can be passed to `#SwiftSettings` exactly once. Otherwise, an
154
+
error will be emitted:
155
+
156
+
```swift
157
+
#SwiftSetting(
158
+
.defaultIsolation(MainActor.self),
159
+
.defaultIsolation(nil) // Error!
160
+
)
161
+
162
+
#SwiftSetting(
163
+
.defaultIsolation(MainActor.self) // Error!
164
+
)
165
+
```
166
+
214
167
If a command line option is set to different values at the module and file
215
168
level, the setting at the file level will take precedence.
216
169
217
-
Beyond adding support for `defaultIsolation` in Concurrency, we will also add
218
-
support for strict concurrency and warningsAsErrors.
170
+
## Source compatibility
171
+
172
+
The addition of the `#SwiftSettings` and `SwiftSetting` cannot impact the
173
+
parsing of other constructs since the name shadowing rules in the compiler will
174
+
ensure that any local macros or types that shadow those names will take
175
+
precedence. If the user wishes to still use `#SwiftSettings`, the user can spell
176
+
the macro as `#Swift.SwiftSettings` as needed.
177
+
178
+
Importantly this means that `#SwiftSettings` if used in the swiftpm code base or
179
+
in `Package.swift` files would always need to be namespaced. The authors think
180
+
that this is an acceptable trade-off since:
181
+
182
+
* The `swiftpm` codebase is one project out of many and can just namespace as
183
+
appropriate.
184
+
*`Package.swift` files really shouldn't need `#SwiftSettings` since they are
185
+
very self contained APIs.
186
+
187
+
## ABI compatibility
188
+
189
+
This proposal does not inherently break ABI. But it can break ABI if a command
190
+
line option is enabled that causes the generated code's ABI to change. As part
191
+
of enabling an option, one must consider such implications.
192
+
193
+
## Implications on adoption
194
+
195
+
Adopters of this proposal should be aware that while this feature does not
196
+
inherently break ABI, enabling options using `#SwiftSettings` that break ABI can
197
+
result in ABI breakage.
198
+
199
+
## Future directions
200
+
201
+
### Adding support for warningsAsErrors and strictConcurrency to allow for per-file warningsAsErrors based migration of large modules to strict concurrency
202
+
203
+
Swift has taken the position that updating modules for strict concurrency should
204
+
be accomplished by:
205
+
206
+
1. Enabling strict concurrency on the entire module causing the compiler to emit
207
+
warnings.
208
+
209
+
2. Updating the module incrementally until all of the warnings have been
210
+
eliminated from the module.
211
+
212
+
3. Updating the language version of the module to swift 6 so from that point on
213
+
warnings will become errors to prevent backsliding.
214
+
215
+
This creates developer friction when applied to large modules, since the size of
216
+
the module makes it difficult to eliminate all of the warnings at once. This
217
+
results in either the module being updated over time on main during which main
218
+
is left in an intermediate, partially updated state or the module being updated
219
+
on a branch that is merged once all updates have been completed. In the former
220
+
case, it is easy to introduce new warnings as new code is added to the codebase
221
+
since only warnings are being used. In the later case, the branch must be kept
222
+
up to date in the face of changes being made to the codebase on main, forcing
223
+
one to continually need to update the branch to resolve merge conflicts against
224
+
main.
225
+
226
+
In contrast by allowing for strict concurrency to be applied one file at a time,
227
+
users can update code using a warningsAsErrors based migration path. This
228
+
involves instead:
229
+
230
+
1. Enabling strict concurrency and warningsAsErrors for strict concurrency on
231
+
the specific file.
232
+
233
+
2. Fixing all of the errors in the file.
234
+
235
+
3. Commiting the updated file into main.
236
+
237
+
Since one is only updating one file at a time and using warningsAsErrors, the
238
+
updates can be done incrementally on a per file basis, backsliding cannot occur
239
+
in the file, and most importantly main is never in an intermediate, partially
240
+
updated state. We think that this approach will make it significantly easier for
241
+
larger modules to be migrated to strict concurrency.
242
+
243
+
#### `strictConcurrency`
219
244
220
245
Support for strict concurrency will be added by defining an extension of
221
246
`SwiftSetting` in `Concurrency` that contains an enum that specifies the cases
@@ -234,17 +259,20 @@ extension SwiftSetting {
234
259
}
235
260
```
236
261
262
+
#### `warningsAsErrors`
263
+
237
264
Support for warningsAsErrors will be added by defining an extension of
238
265
`SwiftSetting` in stdlibCore that defines a struct called DiagnosticGroup and a
239
-
static function called `warningsAsErrors`:
266
+
static function called `warningsAsErrors` that takes a variadic list of
267
+
DiagnosticGroup that should be applied:
240
268
241
269
```swift
242
270
extensionSwiftSetting {
243
271
publicstructDiagnosticGroup {
244
272
publicinit() { fatalError("Cannot construct a DiagnosticGroup") }
0 commit comments