Edit: lots of replies showing how TypeScript can be made to do exhaustiveness checking. It's neat and all but it's a lot of gymnastics compared to languages that just have this built in, which again is part of the appeal of Gleam for me.
For the most straightforward nominative pattern matching, I would write a small match function:
type A = { kind: "kindA", a: "dataA" }
type B = { kind: "kindB", b: "dataB" }
type Sum = A | B
const match = <
const V extends { kind: string },
const C extends { [ kind in V[ "kind" ] ]: ( value: V & { kind: kind } ) => unknown }
>( value: V, cases: C ) => cases[ value.kind as V[ "kind" ] ]( value ) as ReturnType<C[ V[ "kind" ] ]>
// You check the type of result, change the type of value to A or B, make the cases non-exhaustive...
const howToUse = ( value: Sum ) => {
const result = match( value, {
kindA: _ => _.a,
kindB: _ => _.b
} )
}
I ran into this exact issue when I was working on a piece of typescript that interoperates with a rust server, here's how I was able to do exhaustiveness checking (the errors aren't the prettiest, but it works): https://github.com/wwtos/mjuo/blob/main/vpo-frontend/src/lib...
Yeah, that can be annoying. Until pattern matching makes it into JS, the main way people deal with this is to use a library. For example, ts-pattern lets you stick `.exhaustive()` at the end of a match call and I believe you get the error at typechecking time, not runtime.
The function call will fail at compile time if yourUnion is anything more than never, which you can use in your else case of if statements that narrow the discriminated union.