1
1
use crate::utils::{snippet_opt, span_lint_and_then};
2
2
use if_chain::if_chain;
3
- use rustc_ast::ast::{Attribute, Ident, Item, ItemKind, StructField, TyKind , Variant, VariantData, VisibilityKind};
3
+ use rustc_ast::ast::{Attribute, Item, ItemKind, StructField, Variant, VariantData, VisibilityKind};
4
4
use rustc_attr as attr;
5
5
use rustc_errors::Applicability;
6
6
use rustc_lint::{EarlyContext, EarlyLintPass};
7
7
use rustc_session::{declare_lint_pass, declare_tool_lint};
8
+ use rustc_span::Span;
8
9
9
10
declare_clippy_lint! {
10
11
/// **What it does:** Checks for manual implementations of the non-exhaustive pattern.
@@ -90,29 +91,30 @@ fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants
90
91
}
91
92
92
93
if_chain! {
93
- if !attr::contains_name(&item.attrs, sym!(non_exhaustive));
94
- let markers = variants.iter().filter(|v| is_non_exhaustive_marker(v)).count();
95
- if markers == 1 && variants.len() > markers;
96
- if let Some(variant) = variants.last();
97
- if is_non_exhaustive_marker(variant);
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;
98
97
then {
99
98
span_lint_and_then(
100
99
cx,
101
100
MANUAL_NON_EXHAUSTIVE,
102
101
item.span,
103
102
"this seems like a manual implementation of the non-exhaustive pattern",
104
103
|diag| {
105
- let header_span = cx.sess.source_map().span_until_char(item.span, '{');
106
-
107
- if let Some(snippet) = snippet_opt(cx, header_span) {
108
- diag.span_suggestion(
109
- item.span,
110
- "add the attribute",
111
- format!("#[non_exhaustive] {}", snippet),
112
- Applicability::Unspecified,
113
- );
114
- diag.span_help(variant.span, "and remove this variant");
104
+ if_chain! {
105
+ if !attr::contains_name(&item.attrs, 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
+ }
115
116
}
117
+ diag.span_help(marker.span, "remove this variant");
116
118
});
117
119
}
118
120
}
@@ -123,44 +125,48 @@ fn check_manual_non_exhaustive_struct(cx: &EarlyContext<'_>, item: &Item, data:
123
125
matches!(field.vis.node, VisibilityKind::Inherited)
124
126
}
125
127
126
- fn is_non_exhaustive_marker(name: &Option<Ident>) -> bool {
127
- name.map(|n| n.as_str().starts_with('_')).unwrap_or(true)
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
+ _ => unreachable!("`VariantData::Unit` is already handled above"),
137
+ };
138
+
139
+ cx.sess.source_map().span_until_char(item.span, delimiter)
128
140
}
129
141
130
142
let fields = data.fields();
131
143
let private_fields = fields.iter().filter(|f| is_private(f)).count();
132
144
let public_fields = fields.iter().filter(|f| f.vis.node.is_pub()).count();
133
145
134
146
if_chain! {
135
- if !attr::contains_name(&item.attrs, sym!(non_exhaustive));
136
- if private_fields == 1 && public_fields >= private_fields && public_fields == fields.len() - 1;
137
- if let Some(field) = fields.iter().find(|f| is_private(f));
138
- if is_non_exhaustive_marker(&field.ident);
139
- if let TyKind::Tup(tup_fields) = &field.ty.kind;
140
- if tup_fields.is_empty();
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));
141
149
then {
142
150
span_lint_and_then(
143
151
cx,
144
152
MANUAL_NON_EXHAUSTIVE,
145
153
item.span,
146
154
"this seems like a manual implementation of the non-exhaustive pattern",
147
155
|diag| {
148
- let delimiter = match data {
149
- VariantData::Struct(..) => '{',
150
- VariantData::Tuple(..) => '(',
151
- _ => unreachable!(),
152
- };
153
- let header_span = cx.sess.source_map().span_until_char(item.span, delimiter);
154
-
155
- if let Some(snippet) = snippet_opt(cx, header_span) {
156
- diag.span_suggestion(
157
- item.span,
158
- "add the attribute",
159
- format!("#[non_exhaustive] {}", snippet),
160
- Applicability::Unspecified,
161
- );
162
- diag.span_help(field.span, "and remove this field");
156
+ if_chain! {
157
+ if !attr::contains_name(&item.attrs, 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
+ }
163
168
}
169
+ diag.span_help(marker.span, "remove this field");
164
170
});
165
171
}
166
172
}
0 commit comments