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