Access control for enum cases

Sorry, this is a little bit hidden in the Proposed Solution section:

  1. When a downstream module tried to switch on a value of type A , it needs to use a default: clause, as y is not accessible for pattern matching.

So it matches what you are saying. Trying to match in module B in your example will fail, you need a default: clause.

Put another way, an enum case consists of two components: a pattern and an initializer/constructor. With private, both the pattern and initializer are private. With private(init), only the init is private. Even if a pattern is private, the exhaustivity relation (Space(enum InternalCases) is a union of Space(case a) and Space(case b)) is unchanged, and the requirement for switch to be exhaustive is unchanged, which means that a default case is needed. [Well, technically, this is not 100% true. default would not be needed if b were defined like internal case b(Never), since for exhaustivity checking purposes, cases with an associated value of type Never are treated as unreachable.]


[Maybe I'm misunderstanding what you are saying but:] This isn't an issue. Today, swiftinterfaces are only supported in the presence of library evolution and enums are non-frozen by default under library evolution, which means that adding new cases is allowed. Even if there are no non-public cases shown in a swiftinterface, a client cannot exhaustively match on a non-frozen enum.

This is related to @lukasa's pitch Extensible Enumerations for Non-Resilient Libraries. While this pitch doesn't support an explicit spelling for "this type doesn't have any internal cases right now, but it may add them in the future, so you can't match on it exhaustively" for non-resilient libraries, you could hypothetically mimic this using an extra case, say internal case reserved (which is a little bit tedious, but Rust libraries did use this pattern until Rust added first-class support for marking enums using #[non_exhaustive]).

From that thread, it's not entirely clear to me what the "right" solution is for this -- Joe suggests potentially changing the default semantics of enums in Swift 6 and Jordan also brings up some concerns. Since it's not clear what an ideal solution to that would look like, I don't introduce any syntax for that in this pitch.


What I was trying was that, today, some people might have a mental model of @unknown default: as:

@unknown default will emit a warning only if all cases not matched.

That mental model would be correct today, since all cases of a public enum are public for existing Swift code. However, after this change, that mental model needs to be updated to:

@unknown default will emit a warning only if all public cases not matched.

2 Likes