Skip to content

Commit 27d96b4

Browse files
committed
Initial draft of proposal for compilerSettings.
1 parent 8b38f17 commit 27d96b4

File tree

1 file changed

+284
-0
lines changed

1 file changed

+284
-0
lines changed

proposals/0000-compiler-settings.md

+284
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
# compilerSettings: a top level statement for enabling compiler flags file wide
2+
3+
* Proposal: [SE-NNNN](NNNN-filename.md)
4+
* Authors: [Michael Gottesman](https://github.com/gottesmm)
5+
* Review Manager: TBD
6+
* Status: **Awaiting implementation**
7+
* Vision: [[Prospective Vision] Improving the approachability of data-race safety](https://forums.swift.org/t/prospective-vision-improving-the-approachability-of-data-race-safety/76183)
8+
* Review: ([pitch](https://forums.swift.org/...))
9+
10+
## Introduction
11+
12+
We propose introducing a new top level statement called `compilerSettings` that
13+
allows for compiler flags to be enabled on a single file. This can be used to
14+
enable warningsAsError, upcoming features, and default isolation among other
15+
flags. For example:
16+
17+
```swift
18+
compilerSettings [
19+
.warningAsError(.concurrency),
20+
.strictConcurrency(.complete),
21+
.enableUpcomingFeature(.existentialAny),
22+
.defaultIsolation(MainActor.self),
23+
]
24+
```
25+
26+
## Motivation
27+
28+
Today Swift contains multiple ways of changing compiler and language behavior
29+
from the command line including:
30+
31+
* Enabling experimental and upcoming features.
32+
* Enabling warnings as errors for diagnostic groups.
33+
* Enabling strict concurrency when compiling pre-Swift 6 code.
34+
* Specifying the default actor isolation via [Controlling Default Actor Isolation]().
35+
* Requiring code to use [Strict Memory Safety]().
36+
37+
Since these compiler and language behaviors can only be manipulated via the
38+
command line and the compiler compiles one module at a time, users are forced to
39+
apply these flags one module at a time instead of one file at a time. This
40+
results in several forms of developer friction that we outline below.
41+
42+
### Unnecessary Module Splitting
43+
44+
Since compiler flags can only be applied to entire modules, one cannot apply a
45+
compiler flag to a subset of files of a module. When confronted with this, users
46+
are forced to split the subset of files into a separate module creating an
47+
unnecessary module split. We foresee that this will occur more often in the
48+
future if new features like [Controlling Default Actor Isolation]() and [Strict
49+
MemorySafety]() are accepted by Swift Evolution. Consider the following
50+
situations where unnecessary module splitting would be forced:
51+
52+
* Consider a framework that by default uses `nonisolated` isolation, but wants
53+
to expose a set of UI elements for users of the framework. In such a case, it
54+
is reasonable to expect the author to want to make those specific files
55+
containing UI elements to have a default isolation of `@MainActor` instead of
56+
nonisolated. Without this feature one must introduce an additional module to
57+
contain the UI elements for the framework when semantically there is really
58+
only one framework.
59+
60+
* Imagine an app that has enabled `@MainActor` isolation by default since it
61+
contains mostly UI elements but that wishes to implement helper abstractions
62+
that interact with a database on a background task. This helper code is
63+
naturally expressed as having a default isolation of nonisolated implying that
64+
one must either split the database helpers into a separate module or must mark
65+
most declarations in the database code with nonisolated by hand.
66+
67+
* Visualize a banking framework that contains both UI elements and functionality
68+
for directly manipulating the account data of a customer. In such a case the
69+
code that actually modifies customer data may want to enable strict memory
70+
safety to increase security. In contrast, strict memory safety is not
71+
necessary for the UI elements that the framework defines. To support both uses
72+
cases, a module split must be introduced.
73+
74+
### Preventing per-file based migration of large modules using warningsAsErrors
75+
76+
Swift has taken the position that updating modules for a new language mode or
77+
feature should be accomplished by:
78+
79+
1. Enabling warnings on the entire module by enabling upcoming features from the
80+
command line.
81+
82+
2. Updating the module incrementally until all of the warnings have been
83+
eliminated from the module.
84+
85+
3. Updating the language version of the module so from that point on warnings
86+
will become errors to prevent backsliding.
87+
88+
This creates developer friction when applied to large modules since the size of
89+
the module makes it difficult to eliminate all of the warnings at once. This
90+
results in either the module being updated over time on main during which main
91+
is left in an intermediate, partially updated state or the module being updated
92+
on a branch that is merged once all updates have been completed. In the former
93+
case, it is easy to introduce new warnings as new code is added to the codebase
94+
since only warnings are being used. In the later case, the branch must be kept
95+
up to date in the face of changes being made to the codebase on main, forcing
96+
one to continually need to update the branch to resolve merge conflicts against
97+
main.
98+
99+
In contrast by allowing for command line flags to be applied one file at a time,
100+
users can update code using a warningsAsErrors based migration path. This
101+
involves instead:
102+
103+
1. Enabling warningsAsErrors for the new feature on the specific file.
104+
105+
2. Fixing all of the errors in the file.
106+
107+
3. Commiting the updated file into main.
108+
109+
Since one is only updating one file at a time and using warningsAsErrors, the
110+
updates can be done incrementally on a per file basis, backsliding cannot occur
111+
in the file, and most importantly main is never in an intermediate, partially
112+
updated state.
113+
114+
## Proposed solution
115+
116+
We propose introducing a new top level statement called `compilerSettings` that
117+
takes a list of enums of type `CompilerSetting`. Each enum case's associated
118+
values represent arguments that would be passed as an argument to the compiler
119+
flag on the command line. The compiler upon parsing a `compilerSettings`
120+
statement updates its internal state to set the appropriate settings before
121+
compiling the rest of the file. An example of such a `compilerSettings`
122+
statement is the following:
123+
124+
```swift
125+
compilerSettings [
126+
.warningAsError(.concurrency),
127+
.strictConcurrency(.complete),
128+
.enableUpcomingFeature(.existentialAny),
129+
.defaultIsolation(MainActor.self),
130+
]
131+
```
132+
133+
By specifying compiler options in such a manner, we are able to solve all of the
134+
problems above:
135+
136+
1. The programmer can avoid module splitting when they wish to use compiler
137+
flags only on a specific file.
138+
139+
2. A programmer can specify options on a per file basis instead of only on a per
140+
module basis allowing for warningsAsError to be used per file instead of per
141+
module migration to use new features.
142+
143+
As an additional benefit since enums are being used, code completion can make
144+
the compiler options discoverable to the user in the IDE.
145+
146+
## Detailed design
147+
148+
We will update the swift grammer to allow for a new top level statement called
149+
`compilerSettings` that takes a list of expressions:
150+
151+
```text
152+
top-level-statement ::= 'compilerSettings' '[' (expr ',')* expr ','? ']'
153+
```
154+
155+
Each expression passed to `compilerSettings` is an instance of the enum
156+
`CompilerSetting` that specifies a command line option that is to be applied to
157+
the file. `CompilerSetting` will be a resilient enum defined in the standard
158+
library that provides the following API:
159+
160+
```
161+
public enum CompilerSetting {
162+
case enableUpcomingFeature(UpcomingFeatureCompilerSetting)
163+
case enableExperimentalFeature(ExperimentalFeatureCompilerSetting)
164+
case warningAsError(WarningAsErrorCompilerSetting)
165+
case defaultIsolation(Actor.Type)
166+
case strictConcurrency(StrictConcurrencyCompilerSetting)
167+
}
168+
```
169+
170+
We purposely make `CompilerSetting` resilient so additional kinds of compiler
171+
settings can be added over time without breaking ABI.
172+
173+
`CompilerSetting` provides an enum case for each command line option and each
174+
case is able to provide associated values to specify arguments that would
175+
normally be passed on the command line to the option. These associated values
176+
can be one of:
177+
178+
* an already existing type (e.x.: `Actor.Type`)
179+
180+
* a custom fixed enum defined as a subtype of `CompilerSetting` (e.x.:
181+
`StrictConcurrencyCompilerSetting`)
182+
183+
* a custom enum defined as a subtype of `CompilerSetting` that the compiler
184+
synthesizes cases for depending on internal compiler data (e.x.: features for
185+
`UpcomingFeatureCompilerSetting`)
186+
187+
We purposely keep all custom types defined for `CompilerSetting` as subtypes of
188+
`CompilerSetting` in order to avoid polluting the global namespace. Thus we
189+
would necessarily define in the stdlib all of the custom types as:
190+
191+
```swift
192+
extension CompilerSetting {
193+
// Cases synthesized by the compiler.
194+
public enum UpcomingFeatureCompilerSetting { }
195+
196+
// Cases synthesized by the compiler.
197+
public enum ExperimentalFeatureCompilerSetting { }
198+
199+
// Cases synthesized by the compiler.
200+
public enum WarningsAsErrorsCompilerSetting { }
201+
202+
// We know ahead of time all of the cases, so this is specified explicitly.
203+
public enum StrictConcurrency {
204+
case minimal
205+
case targeted
206+
case complete
207+
}
208+
}
209+
```
210+
211+
In order to ensure that compiler settings are easy to find in the file, we
212+
require that `compilerSettings` be the first AST node in the file before any
213+
other statements including import statements.
214+
215+
By default command line flags will not be allowed to be passed to
216+
`compilerSettings`. Instead, we will require a compiler flag to explicitly opt
217+
in to being supported by `compilerSettings`. We require that since:
218+
219+
1. Certain compiler flags like `-enable-library-evolution` have effects that can
220+
not be constrained to a single file since they are inherently module wide
221+
flags.
222+
223+
2. Other compiler flags whose impacts would necessitate exposing
224+
`compilerSettings` in textual modules. We view supported `compilerSettings`
225+
as an anti-goal since appearing in the module header makes enabling
226+
`compilerSettings` affect the way the module is interpreted by users, while
227+
we are designing `compilerSettings` to have an impact strictly local to the
228+
file. NOTE: This does not preclude compiler options that modify the way the
229+
code in the file is exposed in textual modules by modifying the code's
230+
representation in the textual module directly.
231+
232+
## Source compatibility
233+
234+
The addition of the `compilerSetting` keyword does not impact parsing of other
235+
constructs since it can only be parsed as part of top level code in a position
236+
where one must have a keyword preventing any ambiguity. Adding the enum
237+
`CompilerSetting` cannot cause a name conflict since the name shadowing rules
238+
added to the compiler for `Result` will also guarantee that any user defined
239+
`CompilerSetting` will take precedence over the `CompilerSetting` type. In such
240+
a situation, the user can spell `CompilerSetting` as `Swift.CompilerSetting` as
241+
needed. All of the subtypes of `CompilerSetting` cannot cause any source
242+
compatibility issues since they are namespaced within `CompilerSetting`.
243+
244+
## ABI compatibility
245+
246+
This proposal does not inherently break ABI. But it can break ABI if a command
247+
line option is enabled that causes the generated code's ABI to change.
248+
249+
## Implications on adoption
250+
251+
Adopters of this proposal should be aware that while this feature does not
252+
inherently break ABI, enabling options using `compilerSettings` that break ABI
253+
can result in ABI breakage.
254+
255+
## Future directions
256+
257+
Even though we purposely chose not to reuse `SwiftSetting` from `SwiftPM` (see
258+
Alternatives Considered), we could add a new static method onto `SwiftSetting`
259+
called `SwiftSetting.defineCompilerSetting` or provide an overload for
260+
`SwiftSetting.define` that takes a `CompilerSetting` enum and uses it to pass
261+
flags onto a specific target. This would allow for SwiftPM users to take
262+
advantage of the discoverability provided by using enums in comparison with the
263+
stringly typed APIs that `SwiftSetting` uses today to enable experimental and
264+
upcoming features. This generalized API also would ensure that `SwiftSetting`
265+
does not need to be updated to support more kinds of command line options since
266+
the compiler would be able to "inject" support for such options into
267+
`SwiftPM`. This would most likely require out sub-enum cases to be String enums
268+
so that SwiftPM can just use their string representation.
269+
270+
## Alternatives considered
271+
272+
Instead of using `compilerSettings` and `CompilerSetting`, we could instead use
273+
`swiftSettings` and `SwiftSetting` respectively. The reason why the authors
274+
avoided this is that `swiftSetting` and `SwiftSetting` are currently used by
275+
swiftpm to specify build settings resulting in potential user confusion since
276+
`SwiftSetting` uses stringly typed APIs instead of the more discoverable enum
277+
based APIs above. If instead one attempted to move `SwiftSetting` into the
278+
standard library, one would have to expose many parts of SwiftPM's internal
279+
types into the standard library such as `BuildSettingData`,
280+
`BuildSettingCondition`, `Platform`, `BuildConfiguration`, and more.
281+
282+
## Acknowledgments
283+
284+
TODO

0 commit comments

Comments
 (0)