9
9
* Upcoming Feature Flag: ` ExtensibleEnums `
10
10
* Review: ([ pitch] ( https://forums.swift.org/... ) )
11
11
12
+ Previously pitched in:
13
+ - https://forums.swift.org/t/extensible-enumerations-for-non-resilient-libraries/35900
14
+ - https://forums.swift.org/t/pitch-non-frozen-enumerations/68373
15
+
16
+ > ** Differences to previous proposal**
17
+
18
+ > This proposal expands on the previous proposals and incorperates the language
19
+ > steering groups feedback of exploring language features to solve the
20
+ > motivating problem. It also provides a migration path for existing modules.
21
+
12
22
## Introduction
13
23
14
24
This proposal addresses the long standing behavioural difference of ` enum ` s in
@@ -139,59 +149,107 @@ non-resilient Swift.
139
149
We propose to introduce a new language feature ` ExtensibleEnums ` that aligns the
140
150
behaviour of enumerations in both language dialects. This will make ** public**
141
151
enumerations in packages a safe default and leave maintainers the choice of
142
- extending them later on. We also propose to enable this new language feature
143
- by default with the next lagnuage mode.
144
-
145
- In modules with the language feature enabled, developers can use the existing
146
- ` @frozen ` attribute to mark an enumeration as non-extensible, allowing consumers
147
- of the module to exhaustively switch over the cases. This makes committing to the
148
- API of an enum an active choice for developers.
149
-
150
- Modules consuming other modules with the language feature enabled will be forced
151
- to add an ` @unknown default: ` case to any switch state for enumerations that are
152
- not marked with ` @frozen ` . Importantly, this only applies to enums that are
153
- imported from other modules that are not in the same package. For enums inside
154
- the same modules of the declaring package switches are still required to be
155
- exhaustive and don't require an ` @unknown default: ` case.
156
-
157
- Since enabling a language feature applies to the whole module at once we also
158
- propose adding a new attribute ` @extensible ` analogous to ` @frozen ` . This
159
- attribute allows developers to make a case-by-case decision on each enumeration
160
- if it should be extensible or not by applying one of the two attributes. The
161
- language feature ` ExtensibleEnums ` can be thought of as implicitly adding
162
- ` @extensible ` to all enums that are not explicitly marked as ` @frozen ` .
163
-
164
- In resilient modules, the ` @extensible ` attribute doesn't affect API nor ABI
165
- since the behaviour of enumerations in modules compiled with library evolution
166
- mode are already extensible by default. We believe that extensible enums are the
167
- right default choice in both resilient and non-resilient modules and the new
168
- proposed ` @extensible ` attribute primiarly exists to give developers a migration
169
- path.
170
-
171
- In non-resilient modules, adding the ` @extensible ` attribute to non-public enums
172
- will produce a warning since those enums can only be matched exhaustively.
152
+ extending them later on. We also propose to enable this new language feature by
153
+ default with the next lagnuage mode.
173
154
174
- ## Source compatibility
155
+ We also propose to introduce two new attributes.
156
+ - ` @nonExtensible ` : For marking an enumeration as not extensible.
157
+ - ` @extensible ` : For marking an enumeration as extensible.
175
158
176
- Enabling the language feature ` ExtensibleEnums ` in a module that contains public
177
- enumerations is a source breaking change.
178
- Changing the annotation from ` @frozen ` to ` @extensible ` is a source breaking
179
- change.
180
- Changing the annotation from ` @extensible ` to ` @frozen ` is a source compatible
181
- change and will only result in a warning code that used ` @unknown default: `
182
- clause. This allows developers to commit to the API of an enum in a non-source
183
- breaking way.
184
- Adding an ` @extensible ` annotation to an exisitng public enum is a source
185
- breaking change in modules that have ** not** enabled the ` ExtensibleEnums `
186
- language features or are compiled with resiliency.
159
+ Modules consuming other modules with the language feature enabled will be
160
+ required to add an ` @unknown default: ` case to any switch state for enumerations
161
+ that are not marked with ` @nonExtensible ` .
162
+
163
+ An example of using the language feature and the keywords is below:
164
+
165
+ ``` swift
166
+ /// Module A
167
+ @extensible // or language feature ExtensibleEnums is enabled
168
+ enum MyEnum {
169
+ case foo
170
+ case bar
171
+ }
172
+
173
+ @nonExtensible
174
+ enum MyFinalEnum {
175
+ case justMe
176
+ }
177
+
178
+ /// Module B
179
+ switch myEnum { // error: Switch covers known cases, but 'MyEnum' may have additional unknown values, possibly added in future versions
180
+ case .foo : break
181
+ case .bar : break
182
+ }
183
+
184
+ // The below produces no warnings since the enum is marked as nonExtensible
185
+ switch myFinalEnum {
186
+ case .justMe : break
187
+ }
188
+ ```
187
189
188
- ## Effect on ABI stability
190
+ ## Detailed design
189
191
190
- This attribute does not affect the ABI, as it is a no-op when used in a resilient library.
192
+ ### Migration path
191
193
192
- ## Effect on API resilience
194
+ The proposed new language feature is the first langauge feature that has impact
195
+ on the consumers of a module and not the module itself. Enabling the langauge
196
+ feature in a non-resilient module with public enumerations is a source breaking
197
+ change.
193
198
194
- This proposal only affects API resilience of non-resilient libraries, by enabling more changes to be made without API breakage.
199
+ The two proposed annotations ` @extensible/@nonExtensible ` give developers tools
200
+ to opt-in to the new language feature or in the future language mode without
201
+ breaking their consumers. This paves a path for a gradual migration. Developers
202
+ can mark all of their exisiting public enumerations as ` @nonExtensible ` and then
203
+ turn on the language feature. Similarly, developers can also mark new
204
+ enumerations as ` @extensible ` without turning on the language feature yet.
205
+
206
+ In a future language mode, individual modules can still be opted in one at a
207
+ time into the new language mode and apply the annotations as needed to avoid
208
+ source breakages.
209
+
210
+ When the language feature is turned on and a public enumeration is marked as
211
+ ` @extensible ` it will produce a warning that the annotation isn't required.
212
+
213
+ In non-resilient modules without the language feature turned on, adding the
214
+ ` @extensible ` attribute to non-public enums will produce a warning since those
215
+ enums can only be matched exhaustively.
216
+
217
+ ### Implications on code in the same package
218
+
219
+ Code inside the same package still needs to exhaustively switch over
220
+ enumerations defined in the same package. Switches over enums of the same
221
+ package containing an ` @unknown default ` will produce a compiler warning.
222
+
223
+ ### Impact on resilient modules & ` @frozen ` attribute
224
+
225
+ Explicitly enabling the language feature in resilient modules will produce a
226
+ compiler warning since that is already the default behaviour. Using the
227
+ ` @nonExtensible ` annotation will lead to a compiler error since users of
228
+ resilient modules must use the ` @frozen ` attribute instead.
229
+
230
+ Since some modules support compiling in resilient and non-resilient modes,
231
+ developers need a way to mark enums as non-extensible for both. ` @nonExtensible `
232
+ produces an error when compiling with resiliency; hence, developers must use
233
+ ` @frozen ` . To make supporting both modes easier ` @frozen ` will also work in
234
+ non-resilient modules and make enumerations extensible.
235
+
236
+ ## Source compatibility
237
+
238
+ - Enabling the language feature ` ExtensibleEnums ` in a module that contains
239
+ public enumerations is a source breaking change unless all existing public
240
+ enumerations are marked with ` @nonExtensible `
241
+ - Adding an ` @extensible ` annotation to an exisitng public enum is a source
242
+ breaking change in modules that have ** not** enabled the ` ExtensibleEnums `
243
+ language features or are compiled with resiliency.
244
+ - Changing the annotation from ` @nonExtensible/@frozen ` to ` @extensible ` is a
245
+ source breaking change.
246
+ - Changing the annotation from ` @extensible ` to ` @nonExtensible/@frozen ` is a
247
+ source compatible change and will only result in a warning code that used
248
+ ` @unknown default: ` clause. This allows developers to commit to the API of an
249
+ enum in a non-source breaking way.
250
+
251
+ ## ABI compatibility
252
+ The new attributes do not affect the ABI, as it is a no-op when used in a resilient library.
195
253
196
254
## Future directions
197
255
@@ -201,7 +259,6 @@ Enums can be used for errors. Catching and pattern matching enums could add
201
259
support for an ` @unknown catch ` to make pattern matching of typed throws align
202
260
with ` switch ` pattern matching.
203
261
204
-
205
262
## Alternatives considered
206
263
207
264
### Only provide the ` @extensible ` annotation
@@ -210,4 +267,14 @@ We believe that the default behaviour in both language dialects should be that
210
267
public enumerations are extensible. One of Swift's goals, is safe defaults and
211
268
the current non-extensible default in non-resilient modules doesn't achieve that
212
269
goal. That's why we propose a new language feature to change the default in a
213
- future Swift language mode.
270
+ future Swift language mode.
271
+
272
+ ### Usign ` @frozen ` and introducing ` @nonFrozen `
273
+
274
+ We considered names such as ` @nonFrozen ` for ` @extensible ` and using ` @frozen `
275
+ for ` @nonExtensible ` ; however, we believe that _ frozen_ is a concept that
276
+ includes more than exhaustive matching. It is heavily tied to resiliency and
277
+ also has ABI impact. That's why decoupled annotations that only focus on the
278
+ extensability is better suited. ` @exhaustive/@nonExhaustive ` would fit that bill
279
+ as well but we believe that ` @extensible ` better expresses the intention of the
280
+ author.
0 commit comments