The #[non_exhaustive] attribute prevents exhaustively matching an enum outside of the defining crate. However, it's sometimes useful to have the opposite of this behavior - that is, prevent non-exhaustively matching an enum within the defining crate.
For example, non-exhaustively matching on DefPathData or TyKind is often the wrong choice. By writing an exhaustive match (explicitly mentioning all variants), any additions/removals from the enum will require explicit acknowledgment at the match site, helping to prevent bugs.
Examples from rustc:
It would be useful to have a compiler lint to enforce this programmatically.
As an initial implementation, we could add an unstable attribute #[rustc_exhaustive] and an internal lint EXHAUSTIVE_MATCH. This lint fires on any non-exhaustive match or destructuring let expression (not if let/while let for single enum variants) that match on an ADT annotated with #[rustc_exhaustive]. Since this would be a lint, it could be allowed on a case-by-case basis (e.g. performing an early exit on TyKind::Error or TyKind::Infer).
If this proves useful, it could be made available to all Rust programs after an RFC.
This also seems similar to #[must_use].
This lint fires on any non-exhaustive
matchor destructuringletexpression (notif let/while letfor single enum variants) that match on an ADT annotated with#[rustc_exhaustive].
The caveat about if let & while let makes this not the dual of #[non_exhaustive], which allows if let on a variant because of the implicit _ pattern from the else branch (it's implemented this way in the compiler due to desugaring to match). Also, if we see if let as a combination of if and let, then the behavior for let diverges in expression and statement form (meaning we cannot fuse the two some day).
I'm also skeptical of this as a property of the ADT itself, although I see why this was chosen. For example, while there are plenty of places where we would e.g. want to match exhaustively on ExprKind there are also situations where we don't care (e.g., in diagnostics). I suspect you suggested if let work this way because of the situational character, but that also weakens the feature.
As for implementation complexity, I defer to @Nadrieril & @varkor.
On a scale from 1 (only a few lines of code) to 5 (in-depth refactor), I'd expect 2-3 because it goes a bit against the grain of the uniformity of the exhaustiveness algorithm. It would be very easy to just detect uses of the _ pattern for a type with the #[rustc_exhaustive] attribute. It's more painful to detect only those uses of _ that occur after another concrete pattern.
I'm a bit dubious about how this would work in practice, because you're effectively special-casing the top level of pattern matching. Additionally, I'm not convinced that it's common to have data types which you _always_ want to exhaustively match on. If you want to do an exhaustive match, I think comments telling future contributors not to add a wildcard is enough, and it can be left to the reviewer's judgement to decide whether or not a wildcard is warranted.
We may not need to special-case the top-level. For example, I would expect the following to trigger the lint:
match Some(x) {
Some(StatementKind::AscribeUserType(..)) => {}
_ => {}
}
Most helpful comment
I'm a bit dubious about how this would work in practice, because you're effectively special-casing the top level of pattern matching. Additionally, I'm not convinced that it's common to have data types which you _always_ want to exhaustively match on. If you want to do an exhaustive match, I think comments telling future contributors not to add a wildcard is enough, and it can be left to the reviewer's judgement to decide whether or not a wildcard is warranted.