I haven't done compiler work, but your note makes sense.
However I've been writing a fair amount of ReScript (OCaml but in JavaScript: The Good Parts) for UI work, and we never use catch-all in pattern matching.
It is an all-or-nothing proposition: you want to have complete certainty all the time that the compiler will catch all possible cases when the type is modified.
Without that certainty, discriminated unions are simply a lot of work for nothing in return.
This can lead to verbose code, but sometimes it is solved by generic handlers.
let onControlFlow = (stmt, cb) => {
switch (stmt) {
| If(body)
| While(body) => cb(body)
| Module(_)
| Let(_)
| Statement(_) => ()
}
}
This is an unrealistic contortion of your example, but in user interfaces there are often cases where there is some sort of grouping within an ADT, and thus we can apply the same function to the tagged data common to it.