1 use clippy_utils::attrs::is_doc_hidden;
2 use clippy_utils::diagnostics::span_lint_and_then;
3 use clippy_utils::source::snippet_opt;
4 use clippy_utils::{is_lint_allowed, meets_msrv, msrvs};
5 use rustc_ast::ast::{self, VisibilityKind};
6 use rustc_data_structures::fx::FxHashSet;
7 use rustc_errors::Applicability;
8 use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
9 use rustc_hir::{self as hir, Expr, ExprKind, QPath};
10 use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
11 use rustc_middle::ty::DefIdTree;
12 use rustc_semver::RustcVersion;
13 use rustc_session::{declare_tool_lint, impl_lint_pass};
14 use rustc_span::def_id::{DefId, LocalDefId};
15 use rustc_span::{sym, Span};
17 declare_clippy_lint! {
19 /// Checks for manual implementations of the non-exhaustive pattern.
21 /// ### Why is this bad?
22 /// Using the #[non_exhaustive] attribute expresses better the intent
23 /// and allows possible optimizations when applied to enums.
40 /// struct T(pub i32, pub i32, ());
57 /// struct T(pub i32, pub i32);
59 #[clippy::version = "1.45.0"]
60 pub MANUAL_NON_EXHAUSTIVE,
62 "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
65 #[expect(clippy::module_name_repetitions)]
66 pub struct ManualNonExhaustiveStruct {
67 msrv: Option<RustcVersion>,
70 impl ManualNonExhaustiveStruct {
72 pub fn new(msrv: Option<RustcVersion>) -> Self {
77 impl_lint_pass!(ManualNonExhaustiveStruct => [MANUAL_NON_EXHAUSTIVE]);
79 #[expect(clippy::module_name_repetitions)]
80 pub struct ManualNonExhaustiveEnum {
81 msrv: Option<RustcVersion>,
82 constructed_enum_variants: FxHashSet<(DefId, DefId)>,
83 potential_enums: Vec<(LocalDefId, LocalDefId, Span, Span)>,
86 impl ManualNonExhaustiveEnum {
88 pub fn new(msrv: Option<RustcVersion>) -> Self {
91 constructed_enum_variants: FxHashSet::default(),
92 potential_enums: Vec::new(),
97 impl_lint_pass!(ManualNonExhaustiveEnum => [MANUAL_NON_EXHAUSTIVE]);
99 impl EarlyLintPass for ManualNonExhaustiveStruct {
100 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
101 if !meets_msrv(self.msrv, msrvs::NON_EXHAUSTIVE) {
105 if let ast::ItemKind::Struct(variant_data, _) = &item.kind {
106 let (fields, delimiter) = match variant_data {
107 ast::VariantData::Struct(fields, _) => (&**fields, '{'),
108 ast::VariantData::Tuple(fields, _) => (&**fields, '('),
109 ast::VariantData::Unit(_) => return,
111 if fields.len() <= 1 {
114 let mut iter = fields.iter().filter_map(|f| match f.vis.kind {
115 VisibilityKind::Public => None,
116 VisibilityKind::Inherited => Some(Ok(f)),
119 if let Some(Ok(field)) = iter.next()
120 && iter.next().is_none()
121 && field.ty.kind.is_unit()
122 && field.ident.map_or(true, |name| name.as_str().starts_with('_'))
126 MANUAL_NON_EXHAUSTIVE,
128 "this seems like a manual implementation of the non-exhaustive pattern",
130 if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive))
131 && let header_span = cx.sess().source_map().span_until_char(item.span, delimiter)
132 && let Some(snippet) = snippet_opt(cx, header_span)
134 diag.span_suggestion(
137 format!("#[non_exhaustive] {}", snippet),
138 Applicability::Unspecified,
141 diag.span_help(field.span, "remove this field");
148 extract_msrv_attr!(EarlyContext);
151 impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
152 fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
153 if !meets_msrv(self.msrv, msrvs::NON_EXHAUSTIVE) {
157 if let hir::ItemKind::Enum(def, _) = &item.kind
158 && def.variants.len() > 1
160 let mut iter = def.variants.iter().filter_map(|v| {
161 let id = cx.tcx.hir().local_def_id(v.id);
162 (matches!(v.data, hir::VariantData::Unit(_))
163 && v.ident.as_str().starts_with('_')
164 && is_doc_hidden(cx.tcx.get_attrs(id.to_def_id())))
165 .then(|| (id, v.span))
167 if let Some((id, span)) = iter.next()
168 && iter.next().is_none()
170 self.potential_enums.push((item.def_id, id, item.span, span));
175 fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
176 if let ExprKind::Path(QPath::Resolved(None, p)) = &e.kind
177 && let [.., name] = p.segments
178 && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res
179 && name.ident.as_str().starts_with('_')
181 let variant_id = cx.tcx.parent(id);
182 let enum_id = cx.tcx.parent(variant_id);
184 self.constructed_enum_variants.insert((enum_id, variant_id));
188 fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
189 for &(enum_id, _, enum_span, variant_span) in
190 self.potential_enums.iter().filter(|&&(enum_id, variant_id, _, _)| {
192 .constructed_enum_variants
193 .contains(&(enum_id.to_def_id(), variant_id.to_def_id()))
194 && !is_lint_allowed(cx, MANUAL_NON_EXHAUSTIVE, cx.tcx.hir().local_def_id_to_hir_id(enum_id))
199 MANUAL_NON_EXHAUSTIVE,
201 "this seems like a manual implementation of the non-exhaustive pattern",
203 if !cx.tcx.adt_def(enum_id).is_variant_list_non_exhaustive()
204 && let header_span = cx.sess().source_map().span_until_char(enum_span, '{')
205 && let Some(snippet) = snippet_opt(cx, header_span)
207 diag.span_suggestion(
210 format!("#[non_exhaustive] {}", snippet),
211 Applicability::Unspecified,
214 diag.span_help(variant_span, "remove this variant");
220 extract_msrv_attr!(LateContext);