Skip to content

Commit 71eb669

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

File tree

1 file changed

+116
-49
lines changed

1 file changed

+116
-49
lines changed

proposals/NNNN-extensible-enums.md

+116-49
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@
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+
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+
1222
## Introduction
1323

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

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

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+
```
187189

188-
## Effect on ABI stability
190+
## Detailed design
189191

190-
This attribute does not affect the ABI, as it is a no-op when used in a resilient library.
192+
### Migration path
191193

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

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

196254
## Future directions
197255

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

204-
205262
## Alternatives considered
206263

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

Comments
 (0)