Skip to content

Commit e9b6fd8

Browse files
committed
Pitch feedback: Adding migration path and expand on implications inside the same package
1 parent 0735a82 commit e9b6fd8

File tree

1 file changed

+110
-49
lines changed

1 file changed

+110
-49
lines changed

proposals/NNNN-extensible-enums.md

+110-49
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
* Upcoming Feature Flag: `ExtensibleEnums`
1010
* Review: ([pitch](https://forums.swift.org/...))
1111

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+
1216
## Introduction
1317

1418
This proposal addresses the long standing behavioural difference of `enum`s in
@@ -139,59 +143,107 @@ non-resilient Swift.
139143
We propose to introduce a new language feature `ExtensibleEnums` that aligns the
140144
behaviour of enumerations in both language dialects. This will make **public**
141145
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.
173148

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

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
187185

188-
## Effect on ABI stability
186+
### Migration path
189187

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

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

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

196248
## Future directions
197249

@@ -201,7 +253,6 @@ Enums can be used for errors. Catching and pattern matching enums could add
201253
support for an `@unknown catch` to make pattern matching of typed throws align
202254
with `switch` pattern matching.
203255

204-
205256
## Alternatives considered
206257

207258
### Only provide the `@extensible` annotation
@@ -210,4 +261,14 @@ We believe that the default behaviour in both language dialects should be that
210261
public enumerations are extensible. One of Swift's goals, is safe defaults and
211262
the current non-extensible default in non-resilient modules doesn't achieve that
212263
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

Comments
 (0)