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