diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index d77c804af697..a3ae9737d4f2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -1349,3 +1349,12 @@ impl NoArgsAttributeParser for RustcIntrinsicConstStableIndirectPar const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcIntrinsicConstStableIndirect; } + +pub(crate) struct RustcExhaustiveParser; + +impl NoArgsAttributeParser for RustcExhaustiveParser { + const PATH: &'static [Symbol] = &[sym::rustc_must_match_exhaustively]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Enum)]); + const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcMustMatchExhaustively; +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 6ab3f98e2015..836c6b5edb25 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -297,6 +297,7 @@ mod late { Single>, Single>, Single>, + Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index acbcba90fbcc..d02bd73f42e5 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -1414,6 +1414,10 @@ pub struct BuiltinAttribute { rustc_scalable_vector, Normal, template!(List: &["count"]), WarnFollowing, EncodeCrossCrate::Yes, "`#[rustc_scalable_vector]` defines a scalable vector type" ), + rustc_attr!( + rustc_must_match_exhaustively, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes, + "enums with `#[rustc_must_match_exhaustively]` must be matched on with a match block that mentions all variants explicitly" + ), // ========================================================================== // Internal attributes, Testing: diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index a18ddff94709..f7be4515546a 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1471,6 +1471,9 @@ pub enum AttributeKind { fn_names: ThinVec, }, + /// Represents `#[rustc_must_match_exhaustively]` + RustcMustMatchExhaustively(Span), + /// Represents `#[rustc_never_returns_null_ptr]` RustcNeverReturnsNullPtr, diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index c19fc6976c6e..85bc9dfce926 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -156,6 +156,7 @@ pub fn encode_cross_crate(&self) -> EncodeCrossCrate { RustcMain => No, RustcMir(..) => Yes, RustcMustImplementOneOf { .. } => No, + RustcMustMatchExhaustively(..) => Yes, RustcNeverReturnsNullPtr => Yes, RustcNeverTypeOptions { .. } => No, RustcNoImplicitAutorefs => Yes, diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs index 357aa94feb1e..c0b113610f67 100644 --- a/compiler/rustc_lint/src/internal.rs +++ b/compiler/rustc_lint/src/internal.rs @@ -15,8 +15,9 @@ use crate::lints::{ AttributeKindInFindAttr, BadOptAccessDiag, DefaultHashTypesDiag, ImplicitSysrootCrateImportDiag, LintPassByHand, NonGlobImportTypeIrInherent, QueryInstability, - QueryUntracked, SpanUseEqCtxtDiag, SymbolInternStringLiteralDiag, TyQualified, TykindDiag, - TykindKind, TypeIrDirectUse, TypeIrInherentUsage, TypeIrTraitUsage, + QueryUntracked, RustcMustMatchExhaustivelyNotExhaustive, SpanUseEqCtxtDiag, + SymbolInternStringLiteralDiag, TyQualified, TykindDiag, TykindKind, TypeIrDirectUse, + TypeIrInherentUsage, TypeIrTraitUsage, }; use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; @@ -713,3 +714,89 @@ fn find_attr_kind_in_pat(cx: &EarlyContext<'_>, pat: &Pat) { } } } + +declare_tool_lint! { + pub rustc::RUSTC_MUST_MATCH_EXHAUSTIVELY, + Allow, + "Forbids matches with wildcards, or if-let matching on enums marked with `#[rustc_must_match_exhaustively]`", + report_in_external_macro: true +} +declare_lint_pass!(RustcMustMatchExhaustively => [RUSTC_MUST_MATCH_EXHAUSTIVELY]); + +fn is_rustc_must_match_exhaustively(cx: &LateContext<'_>, id: HirId) -> Option { + let res = cx.typeck_results(); + + let ty = res.node_type(id); + + let ty = if let ty::Ref(_, ty, _) = ty.kind() { *ty } else { ty }; + + if let Some(adt_def) = ty.ty_adt_def() + && adt_def.is_enum() + { + find_attr!(cx.tcx, adt_def.did(), RustcMustMatchExhaustively(span) => *span) + } else { + None + } +} + +fn pat_is_not_exhaustive_heuristic(pat: &hir::Pat<'_>) -> Option<(Span, &'static str)> { + match pat.kind { + hir::PatKind::Missing => None, + hir::PatKind::Wild => Some((pat.span, "because of this wildcard pattern")), + hir::PatKind::Binding(_, _, _, Some(pat)) => pat_is_not_exhaustive_heuristic(pat), + hir::PatKind::Binding(..) => Some((pat.span, "because of this variable binding")), + hir::PatKind::Struct(..) => None, + hir::PatKind::TupleStruct(..) => None, + hir::PatKind::Or(..) => None, + hir::PatKind::Never => None, + hir::PatKind::Tuple(..) => None, + hir::PatKind::Box(pat) => pat_is_not_exhaustive_heuristic(&*pat), + hir::PatKind::Deref(pat) => pat_is_not_exhaustive_heuristic(&*pat), + hir::PatKind::Ref(pat, _, _) => pat_is_not_exhaustive_heuristic(&*pat), + hir::PatKind::Expr(..) => None, + hir::PatKind::Guard(..) => None, + hir::PatKind::Range(..) => None, + hir::PatKind::Slice(..) => None, + hir::PatKind::Err(..) => None, + } +} + +impl<'tcx> LateLintPass<'tcx> for RustcMustMatchExhaustively { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) { + match expr.kind { + // This is not perfect exhaustiveness checking, that's why this is just a rustc internal + // attribute. But it catches most reasonable cases + hir::ExprKind::Match(expr, arms, _) => { + if let Some(attr_span) = is_rustc_must_match_exhaustively(cx, expr.hir_id) { + for arm in arms { + if let Some((span, message)) = pat_is_not_exhaustive_heuristic(arm.pat) { + cx.emit_span_lint( + RUSTC_MUST_MATCH_EXHAUSTIVELY, + expr.span, + RustcMustMatchExhaustivelyNotExhaustive { + attr_span, + pat_span: span, + message, + }, + ); + } + } + } + } + hir::ExprKind::If(expr, ..) if let ExprKind::Let(expr) = expr.kind => { + if let Some(attr_span) = is_rustc_must_match_exhaustively(cx, expr.init.hir_id) { + cx.emit_span_lint( + RUSTC_MUST_MATCH_EXHAUSTIVELY, + expr.span, + RustcMustMatchExhaustivelyNotExhaustive { + attr_span, + pat_span: expr.span, + message: "using if let only matches on one variant (try using `match`)", + }, + ); + } + } + _ => {} + } + } +} diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 30b1e736ef3b..9fa550143345 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -668,6 +668,8 @@ fn register_internals(store: &mut LintStore) { store.register_early_pass(|| Box::new(ImplicitSysrootCrateImport)); store.register_lints(&BadUseOfFindAttr::lint_vec()); store.register_early_pass(|| Box::new(BadUseOfFindAttr)); + store.register_lints(&RustcMustMatchExhaustively::lint_vec()); + store.register_late_pass(|_| Box::new(RustcMustMatchExhaustively)); store.register_group( false, "rustc::internal", @@ -688,6 +690,7 @@ fn register_internals(store: &mut LintStore) { LintId::of(DIRECT_USE_OF_RUSTC_TYPE_IR), LintId::of(IMPLICIT_SYSROOT_CRATE_IMPORT), LintId::of(BAD_USE_OF_FIND_ATTR), + LintId::of(RUSTC_MUST_MATCH_EXHAUSTIVELY), ], ); } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 4279ab230df5..bfc3d3989e10 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -1162,6 +1162,18 @@ pub(crate) struct ImplicitSysrootCrateImportDiag<'a> { #[help("remove `AttributeKind`")] pub(crate) struct AttributeKindInFindAttr; +#[derive(Diagnostic)] +#[diag("match is not exhaustive")] +#[help("explicitly list all variants of the enum in a `match`")] +pub(crate) struct RustcMustMatchExhaustivelyNotExhaustive { + #[label("required because of this attribute")] + pub attr_span: Span, + + #[note("{$message}")] + pub pat_span: Span, + pub message: &'static str, +} + // let_underscore.rs #[derive(Diagnostic)] pub(crate) enum NonBindingLet { diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 6aeb0ae57e75..9afaa4317610 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -339,6 +339,7 @@ fn check_attributes( | AttributeKind::RustcMacroTransparency(_) | AttributeKind::RustcMain | AttributeKind::RustcMir(_) + | AttributeKind::RustcMustMatchExhaustively(..) | AttributeKind::RustcNeverReturnsNullPtr | AttributeKind::RustcNeverTypeOptions {..} | AttributeKind::RustcNoImplicitAutorefs diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 33bc5a578e8b..f2276658ebda 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1753,6 +1753,7 @@ rustc_main, rustc_mir, rustc_must_implement_one_of, + rustc_must_match_exhaustively, rustc_never_returns_null_ptr, rustc_never_type_options, rustc_no_implicit_autorefs, diff --git a/tests/ui-fulldeps/internal-lints/must_match_exhaustively.rs b/tests/ui-fulldeps/internal-lints/must_match_exhaustively.rs new file mode 100644 index 000000000000..cb76c9754dc4 --- /dev/null +++ b/tests/ui-fulldeps/internal-lints/must_match_exhaustively.rs @@ -0,0 +1,49 @@ +//@ compile-flags: -Z unstable-options +//@ ignore-stage1 + +#![feature(rustc_private)] +#![feature(rustc_attrs)] +#![deny(rustc::rustc_must_match_exhaustively)] + + +#[rustc_must_match_exhaustively] +#[derive(Copy, Clone)] +enum Foo { + A {field: u32}, + B, +} + +fn foo(f: Foo) { + match f { + Foo::A {..}=> {} + Foo::B => {} + } + + match f { + //~^ ERROR match is not exhaustive + Foo::A {..} => {} + _ => {} + } + + match f { + //~^ ERROR match is not exhaustive + Foo::A {..} => {} + a => {} + } + + match &f { + //~^ ERROR match is not exhaustive + Foo::A {..} => {} + a => {} + } + + match f { + Foo::A {..} => {} + a@Foo::B => {} + } + + if let Foo::A {..} = f {} + //~^ ERROR match is not exhaustive +} + +fn main() {} diff --git a/tests/ui-fulldeps/internal-lints/must_match_exhaustively.stderr b/tests/ui-fulldeps/internal-lints/must_match_exhaustively.stderr new file mode 100644 index 000000000000..04f112bbe00b --- /dev/null +++ b/tests/ui-fulldeps/internal-lints/must_match_exhaustively.stderr @@ -0,0 +1,71 @@ +error: match is not exhaustive + --> $DIR/must_match_exhaustively.rs:22:11 + | +LL | #[rustc_must_match_exhaustively] + | -------------------------------- required because of this attribute +... +LL | match f { + | ^ + | + = help: explicitly list all variants of the enum in a `match` +note: because of this wildcard pattern + --> $DIR/must_match_exhaustively.rs:25:9 + | +LL | _ => {} + | ^ +note: the lint level is defined here + --> $DIR/must_match_exhaustively.rs:6:9 + | +LL | #![deny(rustc::rustc_must_match_exhaustively)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: match is not exhaustive + --> $DIR/must_match_exhaustively.rs:28:11 + | +LL | #[rustc_must_match_exhaustively] + | -------------------------------- required because of this attribute +... +LL | match f { + | ^ + | + = help: explicitly list all variants of the enum in a `match` +note: because of this variable binding + --> $DIR/must_match_exhaustively.rs:31:9 + | +LL | a => {} + | ^ + +error: match is not exhaustive + --> $DIR/must_match_exhaustively.rs:34:11 + | +LL | #[rustc_must_match_exhaustively] + | -------------------------------- required because of this attribute +... +LL | match &f { + | ^^ + | + = help: explicitly list all variants of the enum in a `match` +note: because of this variable binding + --> $DIR/must_match_exhaustively.rs:37:9 + | +LL | a => {} + | ^ + +error: match is not exhaustive + --> $DIR/must_match_exhaustively.rs:45:8 + | +LL | #[rustc_must_match_exhaustively] + | -------------------------------- required because of this attribute +... +LL | if let Foo::A {..} = f {} + | ^^^^^^^^^^^^^^^^^^^ + | + = help: explicitly list all variants of the enum in a `match` +note: using if let only matches on one variant (try using `match`) + --> $DIR/must_match_exhaustively.rs:45:8 + | +LL | if let Foo::A {..} = f {} + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors +