Now, do you mean it is not an intentional design? Switching on unions with non-exhaustive tags should not have the else prong (which captures no named tags), or should always have the else prong, or unions with non-exhaustive tags should be disallowed?
I am not entirely sure what is intentional, I think for that it would be good to have a comment from the core team or other compiler devs familiar with why status quo works like it does.
The forth option is:
status quo, switching on unions with non-exhaustive tags may have a else prong:
with else:
if all prongs are used, the else is dead code and ignored (for exhaustive tag this is a compile error)
otherwise used for the missing prongs
without else:
if all prongs are used, the code compiles
otherwise compile error for missing prong
To address this:
If you want to handle all unions (regardless of tag type) I think you can:
use an (inline) else prong without handling every other prong (requires having at least one not handled prong) to avoid the exhaustive compile error
convert the tag type to be always exhaustive or non-exhaustive based on the behavior you want and switch on that while accessing the payload manually (or via comptime), or convert the union type too
or use only inline else to handle all prongs
But this doesnāt seem very ergonomic.
I am not sure whether you meant this.
But maybe it is fine to require either non- or exhaustive enum tag types, instead of a mixture of both for any of your generic types?
Itās intentional to allow non-exhaustive enums as the tag type of a union. They still have distinct tags, so thereās no ambiguity there - all enum values that donāt correspond to a tag just wonāt have a corresponding union tag either.
Allowing an unreachable else prong when switching on such unions is indeed an oversight though, thanks for noticing it! Since a tagged union is only instantiable for exactly the tags of its tag type a union with a nonexhaustive tag type is no different from a union with an exhaustive one in that regard.
For a const E = enum(u8) { a, b, _ }, const U = union(E) { a, b } this:
so thereās no compelling reason to allow any forms besides the last one. Especially the first form being disallowed helps avoiding dead code; without changing the definition of U there is literally no way to reach the else prong here.