]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/manual_non_exhaustive.rs
resolve the conflict in compiler/rustc_session/src/parse.rs
[rust.git] / clippy_lints / src / manual_non_exhaustive.rs
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::{meets_msrv, msrvs};
5 use if_chain::if_chain;
6 use rustc_ast::ast::{FieldDef, Item, ItemKind, Variant, VariantData, VisibilityKind};
7 use rustc_errors::Applicability;
8 use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
9 use rustc_semver::RustcVersion;
10 use rustc_session::{declare_tool_lint, impl_lint_pass};
11 use rustc_span::{sym, Span};
12
13 declare_clippy_lint! {
14     /// ### What it does
15     /// Checks for manual implementations of the non-exhaustive pattern.
16     ///
17     /// ### Why is this bad?
18     /// Using the #[non_exhaustive] attribute expresses better the intent
19     /// and allows possible optimizations when applied to enums.
20     ///
21     /// ### Example
22     /// ```rust
23     /// struct S {
24     ///     pub a: i32,
25     ///     pub b: i32,
26     ///     _c: (),
27     /// }
28     ///
29     /// enum E {
30     ///     A,
31     ///     B,
32     ///     #[doc(hidden)]
33     ///     _C,
34     /// }
35     ///
36     /// struct T(pub i32, pub i32, ());
37     /// ```
38     /// Use instead:
39     /// ```rust
40     /// #[non_exhaustive]
41     /// struct S {
42     ///     pub a: i32,
43     ///     pub b: i32,
44     /// }
45     ///
46     /// #[non_exhaustive]
47     /// enum E {
48     ///     A,
49     ///     B,
50     /// }
51     ///
52     /// #[non_exhaustive]
53     /// struct T(pub i32, pub i32);
54     /// ```
55     #[clippy::version = "1.45.0"]
56     pub MANUAL_NON_EXHAUSTIVE,
57     style,
58     "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
59 }
60
61 #[derive(Clone)]
62 pub struct ManualNonExhaustive {
63     msrv: Option<RustcVersion>,
64 }
65
66 impl ManualNonExhaustive {
67     #[must_use]
68     pub fn new(msrv: Option<RustcVersion>) -> Self {
69         Self { msrv }
70     }
71 }
72
73 impl_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]);
74
75 impl EarlyLintPass for ManualNonExhaustive {
76     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
77         if !meets_msrv(self.msrv.as_ref(), &msrvs::NON_EXHAUSTIVE) {
78             return;
79         }
80
81         match &item.kind {
82             ItemKind::Enum(def, _) => {
83                 check_manual_non_exhaustive_enum(cx, item, &def.variants);
84             },
85             ItemKind::Struct(variant_data, _) => {
86                 if let VariantData::Unit(..) = variant_data {
87                     return;
88                 }
89
90                 check_manual_non_exhaustive_struct(cx, item, variant_data);
91             },
92             _ => {},
93         }
94     }
95
96     extract_msrv_attr!(EarlyContext);
97 }
98
99 fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants: &[Variant]) {
100     fn is_non_exhaustive_marker(variant: &Variant) -> bool {
101         matches!(variant.data, VariantData::Unit(_))
102             && variant.ident.as_str().starts_with('_')
103             && is_doc_hidden(&variant.attrs)
104     }
105
106     let mut markers = variants.iter().filter(|v| is_non_exhaustive_marker(v));
107     if_chain! {
108         if let Some(marker) = markers.next();
109         if markers.count() == 0 && variants.len() > 1;
110         then {
111             span_lint_and_then(
112                 cx,
113                 MANUAL_NON_EXHAUSTIVE,
114                 item.span,
115                 "this seems like a manual implementation of the non-exhaustive pattern",
116                 |diag| {
117                     if_chain! {
118                         if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive));
119                         let header_span = cx.sess().source_map().span_until_char(item.span, '{');
120                         if let Some(snippet) = snippet_opt(cx, header_span);
121                         then {
122                             diag.span_suggestion(
123                                 header_span,
124                                 "add the attribute",
125                                 format!("#[non_exhaustive] {}", snippet),
126                                 Applicability::Unspecified,
127                             );
128                         }
129                     }
130                     diag.span_help(marker.span, "remove this variant");
131                 });
132         }
133     }
134 }
135
136 fn check_manual_non_exhaustive_struct(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) {
137     fn is_private(field: &FieldDef) -> bool {
138         matches!(field.vis.kind, VisibilityKind::Inherited)
139     }
140
141     fn is_non_exhaustive_marker(field: &FieldDef) -> bool {
142         is_private(field) && field.ty.kind.is_unit() && field.ident.map_or(true, |n| n.as_str().starts_with('_'))
143     }
144
145     fn find_header_span(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) -> Span {
146         let delimiter = match data {
147             VariantData::Struct(..) => '{',
148             VariantData::Tuple(..) => '(',
149             VariantData::Unit(_) => unreachable!("`VariantData::Unit` is already handled above"),
150         };
151
152         cx.sess().source_map().span_until_char(item.span, delimiter)
153     }
154
155     let fields = data.fields();
156     let private_fields = fields.iter().filter(|f| is_private(f)).count();
157     let public_fields = fields.iter().filter(|f| f.vis.kind.is_pub()).count();
158
159     if_chain! {
160         if private_fields == 1 && public_fields >= 1 && public_fields == fields.len() - 1;
161         if let Some(marker) = fields.iter().find(|f| is_non_exhaustive_marker(f));
162         then {
163             span_lint_and_then(
164                 cx,
165                 MANUAL_NON_EXHAUSTIVE,
166                 item.span,
167                 "this seems like a manual implementation of the non-exhaustive pattern",
168                 |diag| {
169                     if_chain! {
170                         if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive));
171                         let header_span = find_header_span(cx, item, data);
172                         if let Some(snippet) = snippet_opt(cx, header_span);
173                         then {
174                             diag.span_suggestion(
175                                 header_span,
176                                 "add the attribute",
177                                 format!("#[non_exhaustive] {}", snippet),
178                                 Applicability::Unspecified,
179                             );
180                         }
181                     }
182                     diag.span_help(marker.span, "remove this field");
183                 });
184         }
185     }
186 }