Skip to content

Commit 340bc2a

Browse files
committed
add #[rustc_must_match_exhaustively]
1 parent fd57ad4 commit 340bc2a

12 files changed

Lines changed: 244 additions & 2 deletions

File tree

compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,3 +1349,12 @@ impl<S: Stage> NoArgsAttributeParser<S> for RustcIntrinsicConstStableIndirectPar
13491349
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]);
13501350
const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcIntrinsicConstStableIndirect;
13511351
}
1352+
1353+
pub(crate) struct RustcExhaustiveParser;
1354+
1355+
impl<S: Stage> NoArgsAttributeParser<S> for RustcExhaustiveParser {
1356+
const PATH: &'static [Symbol] = &[sym::rustc_must_match_exhaustively];
1357+
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
1358+
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Enum)]);
1359+
const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcMustMatchExhaustively;
1360+
}

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ attribute_parsers!(
299299
Single<WithoutArgs<RustcEffectiveVisibilityParser>>,
300300
Single<WithoutArgs<RustcEiiForeignItemParser>>,
301301
Single<WithoutArgs<RustcEvaluateWhereClausesParser>>,
302+
Single<WithoutArgs<RustcExhaustiveParser>>,
302303
Single<WithoutArgs<RustcHasIncoherentInherentImplsParser>>,
303304
Single<WithoutArgs<RustcHiddenTypeOfOpaquesParser>>,
304305
Single<WithoutArgs<RustcInheritOverflowChecksParser>>,

compiler/rustc_feature/src/builtin_attrs.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,10 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
755755
),
756756
rustc_attr!(rustc_force_inline,"`#[rustc_force_inline]` forces a free function to be inlined"),
757757
rustc_attr!(rustc_scalable_vector,"`#[rustc_scalable_vector]` defines a scalable vector type"),
758+
rustc_attr!(
759+
rustc_must_match_exhaustively,
760+
"enums with `#[rustc_must_match_exhaustively]` must be matched on with a match block that mentions all variants explicitly"
761+
),
758762

759763
// ==========================================================================
760764
// Internal attributes, Testing:

compiler/rustc_hir/src/attrs/data_structures.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,9 @@ pub enum AttributeKind {
16131613
fn_names: ThinVec<Ident>,
16141614
},
16151615

1616+
/// Represents `#[rustc_must_match_exhaustively]`
1617+
RustcMustMatchExhaustively(Span),
1618+
16161619
/// Represents `#[rustc_never_returns_null_ptr]`
16171620
RustcNeverReturnsNullPtr,
16181621

compiler/rustc_hir/src/attrs/encode_cross_crate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ impl AttributeKind {
157157
RustcMain => No,
158158
RustcMir(..) => Yes,
159159
RustcMustImplementOneOf { .. } => No,
160+
RustcMustMatchExhaustively(..) => Yes,
160161
RustcNeverReturnsNullPtr => Yes,
161162
RustcNeverTypeOptions { .. } => No,
162163
RustcNoImplicitAutorefs => Yes,

compiler/rustc_lint/src/internal.rs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ use rustc_span::{Span, sym};
1515
use crate::lints::{
1616
AttributeKindInFindAttr, BadOptAccessDiag, DefaultHashTypesDiag,
1717
ImplicitSysrootCrateImportDiag, LintPassByHand, NonGlobImportTypeIrInherent, QueryInstability,
18-
QueryUntracked, SpanUseEqCtxtDiag, SymbolInternStringLiteralDiag, TyQualified, TykindDiag,
19-
TykindKind, TypeIrDirectUse, TypeIrInherentUsage, TypeIrTraitUsage,
18+
QueryUntracked, RustcMustMatchExhaustivelyNotExhaustive, SpanUseEqCtxtDiag,
19+
SymbolInternStringLiteralDiag, TyQualified, TykindDiag, TykindKind, TypeIrDirectUse,
20+
TypeIrInherentUsage, TypeIrTraitUsage,
2021
};
2122
use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
2223

@@ -713,3 +714,89 @@ impl EarlyLintPass for BadUseOfFindAttr {
713714
}
714715
}
715716
}
717+
718+
declare_tool_lint! {
719+
pub rustc::RUSTC_MUST_MATCH_EXHAUSTIVELY,
720+
Allow,
721+
"Forbids matches with wildcards, or if-let matching on enums marked with `#[rustc_must_match_exhaustively]`",
722+
report_in_external_macro: true
723+
}
724+
declare_lint_pass!(RustcMustMatchExhaustively => [RUSTC_MUST_MATCH_EXHAUSTIVELY]);
725+
726+
fn is_rustc_must_match_exhaustively(cx: &LateContext<'_>, id: HirId) -> Option<Span> {
727+
let res = cx.typeck_results();
728+
729+
let ty = res.node_type(id);
730+
731+
let ty = if let ty::Ref(_, ty, _) = ty.kind() { *ty } else { ty };
732+
733+
if let Some(adt_def) = ty.ty_adt_def()
734+
&& adt_def.is_enum()
735+
{
736+
find_attr!(cx.tcx, adt_def.did(), RustcMustMatchExhaustively(span) => *span)
737+
} else {
738+
None
739+
}
740+
}
741+
742+
fn pat_is_not_exhaustive_heuristic(pat: &hir::Pat<'_>) -> Option<(Span, &'static str)> {
743+
match pat.kind {
744+
hir::PatKind::Missing => None,
745+
hir::PatKind::Wild => Some((pat.span, "because of this wildcard pattern")),
746+
hir::PatKind::Binding(_, _, _, Some(pat)) => pat_is_not_exhaustive_heuristic(pat),
747+
hir::PatKind::Binding(..) => Some((pat.span, "because of this variable binding")),
748+
hir::PatKind::Struct(..) => None,
749+
hir::PatKind::TupleStruct(..) => None,
750+
hir::PatKind::Or(..) => None,
751+
hir::PatKind::Never => None,
752+
hir::PatKind::Tuple(..) => None,
753+
hir::PatKind::Box(pat) => pat_is_not_exhaustive_heuristic(&*pat),
754+
hir::PatKind::Deref(pat) => pat_is_not_exhaustive_heuristic(&*pat),
755+
hir::PatKind::Ref(pat, _, _) => pat_is_not_exhaustive_heuristic(&*pat),
756+
hir::PatKind::Expr(..) => None,
757+
hir::PatKind::Guard(..) => None,
758+
hir::PatKind::Range(..) => None,
759+
hir::PatKind::Slice(..) => None,
760+
hir::PatKind::Err(..) => None,
761+
}
762+
}
763+
764+
impl<'tcx> LateLintPass<'tcx> for RustcMustMatchExhaustively {
765+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) {
766+
match expr.kind {
767+
// This is not perfect exhaustiveness checking, that's why this is just a rustc internal
768+
// attribute. But it catches most reasonable cases
769+
hir::ExprKind::Match(expr, arms, _) => {
770+
if let Some(attr_span) = is_rustc_must_match_exhaustively(cx, expr.hir_id) {
771+
for arm in arms {
772+
if let Some((span, message)) = pat_is_not_exhaustive_heuristic(arm.pat) {
773+
cx.emit_span_lint(
774+
RUSTC_MUST_MATCH_EXHAUSTIVELY,
775+
expr.span,
776+
RustcMustMatchExhaustivelyNotExhaustive {
777+
attr_span,
778+
pat_span: span,
779+
message,
780+
},
781+
);
782+
}
783+
}
784+
}
785+
}
786+
hir::ExprKind::If(expr, ..) if let ExprKind::Let(expr) = expr.kind => {
787+
if let Some(attr_span) = is_rustc_must_match_exhaustively(cx, expr.init.hir_id) {
788+
cx.emit_span_lint(
789+
RUSTC_MUST_MATCH_EXHAUSTIVELY,
790+
expr.span,
791+
RustcMustMatchExhaustivelyNotExhaustive {
792+
attr_span,
793+
pat_span: expr.span,
794+
message: "using if let only matches on one variant (try using `match`)",
795+
},
796+
);
797+
}
798+
}
799+
_ => {}
800+
}
801+
}
802+
}

compiler/rustc_lint/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,8 @@ fn register_internals(store: &mut LintStore) {
670670
store.register_early_pass(|| Box::new(ImplicitSysrootCrateImport));
671671
store.register_lints(&BadUseOfFindAttr::lint_vec());
672672
store.register_early_pass(|| Box::new(BadUseOfFindAttr));
673+
store.register_lints(&RustcMustMatchExhaustively::lint_vec());
674+
store.register_late_pass(|_| Box::new(RustcMustMatchExhaustively));
673675
store.register_group(
674676
false,
675677
"rustc::internal",
@@ -690,6 +692,7 @@ fn register_internals(store: &mut LintStore) {
690692
LintId::of(DIRECT_USE_OF_RUSTC_TYPE_IR),
691693
LintId::of(IMPLICIT_SYSROOT_CRATE_IMPORT),
692694
LintId::of(BAD_USE_OF_FIND_ATTR),
695+
LintId::of(RUSTC_MUST_MATCH_EXHAUSTIVELY),
693696
],
694697
);
695698
}

compiler/rustc_lint/src/lints.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,18 @@ pub(crate) struct ImplicitSysrootCrateImportDiag<'a> {
11621162
#[help("remove `AttributeKind`")]
11631163
pub(crate) struct AttributeKindInFindAttr;
11641164

1165+
#[derive(Diagnostic)]
1166+
#[diag("match is not exhaustive")]
1167+
#[help("explicitly list all variants of the enum in a `match`")]
1168+
pub(crate) struct RustcMustMatchExhaustivelyNotExhaustive {
1169+
#[label("required because of this attribute")]
1170+
pub attr_span: Span,
1171+
1172+
#[note("{$message}")]
1173+
pub pat_span: Span,
1174+
pub message: &'static str,
1175+
}
1176+
11651177
// let_underscore.rs
11661178
#[derive(Diagnostic)]
11671179
pub(crate) enum NonBindingLet {

compiler/rustc_passes/src/check_attr.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
335335
| AttributeKind::RustcMacroTransparency(_)
336336
| AttributeKind::RustcMain
337337
| AttributeKind::RustcMir(_)
338+
| AttributeKind::RustcMustMatchExhaustively(..)
338339
| AttributeKind::RustcNeverReturnsNullPtr
339340
| AttributeKind::RustcNeverTypeOptions {..}
340341
| AttributeKind::RustcNoImplicitAutorefs

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1753,6 +1753,7 @@ symbols! {
17531753
rustc_main,
17541754
rustc_mir,
17551755
rustc_must_implement_one_of,
1756+
rustc_must_match_exhaustively,
17561757
rustc_never_returns_null_ptr,
17571758
rustc_never_type_options,
17581759
rustc_no_implicit_autorefs,

0 commit comments

Comments
 (0)