]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs
bump version to 1.80.1+dfsg1-1~bpo12+pve1
[rustc.git] / src / tools / clippy / clippy_lints / src / manual_non_exhaustive.rs
1 use clippy_config::msrvs::{self, Msrv};
2 use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
3 use clippy_utils::is_doc_hidden;
4 use clippy_utils::source::snippet_opt;
5 use rustc_ast::ast::{self, VisibilityKind};
6 use rustc_ast::attr;
7 use rustc_data_structures::fx::FxHashSet;
8 use rustc_errors::Applicability;
9 use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
10 use rustc_hir::{self as hir, Expr, ExprKind, QPath};
11 use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
12 use rustc_session::impl_lint_pass;
13 use rustc_span::def_id::{DefId, LocalDefId};
14 use rustc_span::{sym, Span};
15
16 declare_clippy_lint! {
17 /// ### What it does
18 /// Checks for manual implementations of the non-exhaustive pattern.
19 ///
20 /// ### Why is this bad?
21 /// Using the #[non_exhaustive] attribute expresses better the intent
22 /// and allows possible optimizations when applied to enums.
23 ///
24 /// ### Example
25 /// ```no_run
26 /// struct S {
27 /// pub a: i32,
28 /// pub b: i32,
29 /// _c: (),
30 /// }
31 ///
32 /// enum E {
33 /// A,
34 /// B,
35 /// #[doc(hidden)]
36 /// _C,
37 /// }
38 ///
39 /// struct T(pub i32, pub i32, ());
40 /// ```
41 /// Use instead:
42 /// ```no_run
43 /// #[non_exhaustive]
44 /// struct S {
45 /// pub a: i32,
46 /// pub b: i32,
47 /// }
48 ///
49 /// #[non_exhaustive]
50 /// enum E {
51 /// A,
52 /// B,
53 /// }
54 ///
55 /// #[non_exhaustive]
56 /// struct T(pub i32, pub i32);
57 /// ```
58 #[clippy::version = "1.45.0"]
59 pub MANUAL_NON_EXHAUSTIVE,
60 style,
61 "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
62 }
63
64 #[expect(clippy::module_name_repetitions)]
65 pub struct ManualNonExhaustiveStruct {
66 msrv: Msrv,
67 }
68
69 impl ManualNonExhaustiveStruct {
70 #[must_use]
71 pub fn new(msrv: Msrv) -> Self {
72 Self { msrv }
73 }
74 }
75
76 impl_lint_pass!(ManualNonExhaustiveStruct => [MANUAL_NON_EXHAUSTIVE]);
77
78 #[expect(clippy::module_name_repetitions)]
79 pub struct ManualNonExhaustiveEnum {
80 msrv: Msrv,
81 constructed_enum_variants: FxHashSet<(DefId, DefId)>,
82 potential_enums: Vec<(LocalDefId, LocalDefId, Span, Span)>,
83 }
84
85 impl ManualNonExhaustiveEnum {
86 #[must_use]
87 pub fn new(msrv: Msrv) -> Self {
88 Self {
89 msrv,
90 constructed_enum_variants: FxHashSet::default(),
91 potential_enums: Vec::new(),
92 }
93 }
94 }
95
96 impl_lint_pass!(ManualNonExhaustiveEnum => [MANUAL_NON_EXHAUSTIVE]);
97
98 impl EarlyLintPass for ManualNonExhaustiveStruct {
99 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
100 if !self.msrv.meets(msrvs::NON_EXHAUSTIVE) {
101 return;
102 }
103
104 if let ast::ItemKind::Struct(variant_data, _) = &item.kind {
105 let (fields, delimiter) = match variant_data {
106 ast::VariantData::Struct { fields, .. } => (&**fields, '{'),
107 ast::VariantData::Tuple(fields, _) => (&**fields, '('),
108 ast::VariantData::Unit(_) => return,
109 };
110 if fields.len() <= 1 {
111 return;
112 }
113 let mut iter = fields.iter().filter_map(|f| match f.vis.kind {
114 VisibilityKind::Public => None,
115 VisibilityKind::Inherited => Some(Ok(f)),
116 VisibilityKind::Restricted { .. } => Some(Err(())),
117 });
118 if let Some(Ok(field)) = iter.next()
119 && iter.next().is_none()
120 && field.ty.kind.is_unit()
121 {
122 span_lint_and_then(
123 cx,
124 MANUAL_NON_EXHAUSTIVE,
125 item.span,
126 "this seems like a manual implementation of the non-exhaustive pattern",
127 |diag| {
128 if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive))
129 && let header_span = cx.sess().source_map().span_until_char(item.span, delimiter)
130 && let Some(snippet) = snippet_opt(cx, header_span)
131 {
132 diag.span_suggestion(
133 header_span,
134 "add the attribute",
135 format!("#[non_exhaustive] {snippet}"),
136 Applicability::Unspecified,
137 );
138 }
139 diag.span_help(field.span, "remove this field");
140 },
141 );
142 }
143 }
144 }
145
146 extract_msrv_attr!(EarlyContext);
147 }
148
149 impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
150 fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
151 if !self.msrv.meets(msrvs::NON_EXHAUSTIVE) {
152 return;
153 }
154
155 if let hir::ItemKind::Enum(def, _) = &item.kind
156 && def.variants.len() > 1
157 {
158 let mut iter = def.variants.iter().filter_map(|v| {
159 (matches!(v.data, hir::VariantData::Unit(_, _))
160 && is_doc_hidden(cx.tcx.hir().attrs(v.hir_id))
161 && !attr::contains_name(cx.tcx.hir().attrs(item.hir_id()), sym::non_exhaustive))
162 .then_some((v.def_id, v.span))
163 });
164 if let Some((id, span)) = iter.next()
165 && iter.next().is_none()
166 {
167 self.potential_enums.push((item.owner_id.def_id, id, item.span, span));
168 }
169 }
170 }
171
172 fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
173 if let ExprKind::Path(QPath::Resolved(None, p)) = &e.kind
174 && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res
175 {
176 let variant_id = cx.tcx.parent(id);
177 let enum_id = cx.tcx.parent(variant_id);
178
179 self.constructed_enum_variants.insert((enum_id, variant_id));
180 }
181 }
182
183 fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
184 for &(enum_id, _, enum_span, variant_span) in
185 self.potential_enums.iter().filter(|&&(enum_id, variant_id, _, _)| {
186 !self
187 .constructed_enum_variants
188 .contains(&(enum_id.to_def_id(), variant_id.to_def_id()))
189 })
190 {
191 let hir_id = cx.tcx.local_def_id_to_hir_id(enum_id);
192 span_lint_hir_and_then(
193 cx,
194 MANUAL_NON_EXHAUSTIVE,
195 hir_id,
196 enum_span,
197 "this seems like a manual implementation of the non-exhaustive pattern",
198 |diag| {
199 let header_span = cx.sess().source_map().span_until_char(enum_span, '{');
200 if let Some(snippet) = snippet_opt(cx, header_span) {
201 diag.span_suggestion(
202 header_span,
203 "add the attribute",
204 format!("#[non_exhaustive] {snippet}"),
205 Applicability::Unspecified,
206 );
207 }
208 diag.span_help(variant_span, "remove this variant");
209 },
210 );
211 }
212 }
213
214 extract_msrv_attr!(LateContext);
215 }