When a type can consist of multiple alternatives, "exhaustiveness checking" refers to the compiler verifying that all the possible alternatives have been considered, in say a pattern-match expression.
One common place where exhaustiveness checking is used is when you have an enum (aka "ADT") defining a closed set of several possible cases.
enum TrafficLight:
case Green, Yellow, Red
def action(light: TrafficLight): String = light match
case TrafficLight.Red => "Stop"
case TrafficLight.Yellow => "Caution"
case TrafficLight.Green => "Go"
Scala also permits "Union Types", which allow you to combine unrelated types with an Or relation
//nrelated types
object Red
object Yellow
object Green
object Blue
type TrafficLight = Green.type | Yellow.type | Red.type
Union types can be useful when you need to compose multiple different subsets drawn from the same vocabulary of elements, which a single enum/ADT cannot do:
type AmbulanceLight = Red.type | Blue.type
It's a subtle distinction, but today an LLM explained to me why union types don't allow exhaustiveness checking, but an enums/ADT do:
In Scala 3's design, union types (A | B | C) are structural, not nominal — the compiler doesn't know the union is "closed." So pattern matching on a union type won't warn on non-exhaustive matches the way sealed traits/enums do.
Ah, of course! The old structural vs nominal distinction! The compiler couldn't know the union is "closed" could it?
The problem is, the LLM is bullshitting. Bamboozling me with a meaningless sentence composed of plausible jargon.
In truth, exhaustiveness checking works equally well for union types as for enums.
-Ben