Skip to content

Commit 0143cbf

Browse files
committed
Update for #SwiftSettings
1 parent 3029caa commit 0143cbf

File tree

1 file changed

+144
-124
lines changed

1 file changed

+144
-124
lines changed

proposals/0000-swift-settings.md

+144-124
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,28 @@
1111
## Introduction
1212

1313
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:
1616

1717
```swift
1818
#SwiftSettings(
19-
.warningsAsErrors(.concurrency),
20-
.strictConcurrency(.complete),
2119
.defaultIsolation(MainActor.self)
2220
)
2321
```
2422

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+
2527
## Motivation
2628

2729
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.
3836

3937
### Unnecessary Module Splitting
4038

@@ -61,48 +59,6 @@ splitting would be forced:
6159
one must either split the database helpers into a separate module or must mark
6260
most declarations in the database code with nonisolated by hand.
6361

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-
10662
## Proposed solution
10763

10864
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
11571

11672
```swift
11773
#SwiftSettings(
118-
.warningsAsErrors(.concurrency),
119-
.strictConcurrency(.complete),
12074
.defaultIsolation(MainActor.self)
12175
)
12276
```
12377

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.
13381

13482
As an additional benefit since strongly typed methods are being used instead of
13583
providing a single method that takes a string to specify the command line
13684
parameter code completion can make the compiler options discoverable to the user
13785
in the IDE.
13886

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).
14289

14390
## Detailed design
14491

14592
We will introduce into the standard library a new declaration macro called
14693
`#SwiftSettings` and a new struct called `SwiftSetting` that `#SwiftSettings`
147-
takes as a parameter:
94+
takes as a variadic parameter:
14895

14996
```swift
15097
@freestanding(declaration)
@@ -155,7 +102,7 @@ public struct SwiftSetting {
155102
}
156103
```
157104

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
159106
and documentation in the IDE.
160107

161108
`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`
183130
struct. The actual value returned is not important since this code will not be
184131
executed.
185132

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`:
195136

196-
* Impact parsing
137+
* No impact on parsing
197138

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
201140
without the need for `#SwiftSettings` to be serialized into a textual
202141
interface file.
203142

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`.
206145

207146
In the future, more options can be added as appropriate (see future
208147
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
211150
and source stability impacts of adding support to `#SwiftSettings`. At minimum,
212151
the feature must obey the above requirements.
213152

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+
214167
If a command line option is set to different values at the module and file
215168
level, the setting at the file level will take precedence.
216169

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`
219244

220245
Support for strict concurrency will be added by defining an extension of
221246
`SwiftSetting` in `Concurrency` that contains an enum that specifies the cases
@@ -234,17 +259,20 @@ extension SwiftSetting {
234259
}
235260
```
236261

262+
#### `warningsAsErrors`
263+
237264
Support for warningsAsErrors will be added by defining an extension of
238265
`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:
240268

241269
```swift
242270
extension SwiftSetting {
243271
public struct DiagnosticGroup {
244272
public init() { fatalError("Cannot construct a DiagnosticGroup") }
245273
}
246274

247-
public static func warningsAsErrors(_ diagnosticGroup: DiagnosticGroup) -> SwiftSetting { ... }
275+
public static func warningsAsErrors(_ diagnosticGroup: DiagnosticGroup...) -> SwiftSetting { SwiftSetting() }
248276
}
249277
```
250278

@@ -273,37 +301,6 @@ Thus one can specify that concurrency warnings should be errors by writing:
273301
)
274302
```
275303

276-
## Source compatibility
277-
278-
The addition of the `#SwiftSettings` and `SwiftSetting` cannot impact the
279-
parsing of other constructs since the name shadowing rules in the compiler will
280-
ensure that any local macros or types that shadow those names will take
281-
precedence. If the user wishes to still use `#SwiftSettings`, the user can spell
282-
the macro as `#Swift.SwiftSettings` as needed.
283-
284-
Importantly this means that `#SwiftSettings` if used in the swiftpm code base or
285-
in `Package.swift` files would always need to be namespaced. The authors think
286-
that this is an acceptable trade-off since:
287-
288-
* The `swiftpm` codebase is one project out of many and can just namespace as
289-
appropriate.
290-
* `Package.swift` files really shouldn't need `#SwiftSettings` since they are
291-
very self contained APIs.
292-
293-
## ABI compatibility
294-
295-
This proposal does not inherently break ABI. But it can break ABI if a command
296-
line option is enabled that causes the generated code's ABI to change. As part
297-
of enabling an option, one must consider such implications.
298-
299-
## Implications on adoption
300-
301-
Adopters of this proposal should be aware that while this feature does not
302-
inherently break ABI, enabling options using `#SwiftSettings` that break ABI can
303-
result in ABI breakage.
304-
305-
## Future directions
306-
307304
### Adding support for Upcoming Features
308305

309306
We could add support for Upcoming Features to `#SwiftSettings`. By default all
@@ -330,6 +327,19 @@ time and use availability to disable the cases associated with features that do
330327
not support `SwiftSettings`. The nice thing about the latter approach is that
331328
all features can be found in the IDE.
332329

330+
### Adding support for Strict Memory Safety
331+
332+
We could also in the future add support to `#SwiftSettings` for Strict Memory
333+
Safety.
334+
335+
### Adding the ability to opt out of module-level flags like warningsAsErrors
336+
337+
We could add support for turning off features like warningsAsErrors at the file
338+
level. This would involve adding a new `disableWarningsAsErrors` method onto
339+
`SwiftSetting`. This would be useful when only a few files fail warningsAsErrors
340+
and the user wishes to only suppress warningsAsErrors in those specific files in
341+
order to avoid needing to unnnecessarily split a module.
342+
333343
### Adding new APIs to SwiftPM that take SwiftSetting
334344

335345
Even though we purposely chose not to reuse `SwiftSetting` from `SwiftPM` (see
@@ -414,8 +424,18 @@ compiler settings since we are attempting to specifically support compiler
414424
settings that semantically can have file wide implications. If we wanted to
415425
support push/pop of compiler settings it would involve different trade-offs and
416426
restrictions on what settings would be able to be used since the restrictions
417-
would necessarily need to be greater.
427+
would necessarily need to be greater. As an example, we could never allow for
428+
429+
### Extending SwiftSetting to include parsing options
430+
431+
We could make it so that parsing `#SwiftSetting` is done directly in the parser
432+
when we parse part of the macro grammar. This would work by evaluating the
433+
`#SwiftSetting` from its syntax and performing our pattern matching at parse
434+
time instead of at type checker time. We would then not create an AST for
435+
`#SwiftSetting`. This current proposal is compatible with the parsing approach
436+
since in such a case, `#SwiftSetting` would never show up at AST time.
418437

419438
## Acknowledgments
420439

421-
TODO
440+
I would like to thank Holly Borla, Doug Gregor, Konrad Malawski and the rest of
441+
Swift Evolution for feedback on this proposal.

0 commit comments

Comments
 (0)