The categorization would need to fit nicely into a tree of types, which seems unlikely, so a tag-based approach would be better. With a bit of custom DSL you can enforce exhaustiveness.
;; can store at compile time the tags for an op
(define-op (if a b c)
(:tags my-tags:control my-tags:special)
...)
Elsewhere: ;; can check at compile-time if all ops
;; for that category are tried
(ematch-op (e :tags my-tags:control)
((if test then else) ...)
((while test body) ...))
Maybe this can be done with Rust macros too
The former is available in Rust, just with a bit more boilerplate. The latter is probably achievable in some way if the language were to change and, even if not necessarily relevant to this problem, I could see being useful in some places...