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};
9 use rustc_semver::RustcVersion;
10 use rustc_session::{declare_tool_lint, impl_lint_pass};
11 use rustc_span::{sym, Span};
13 declare_clippy_lint! {
15 /// Checks for manual implementations of the non-exhaustive pattern.
17 /// ### Why is this bad?
18 /// Using the #[non_exhaustive] attribute expresses better the intent
19 /// and allows possible optimizations when applied to enums.
36 /// struct T(pub i32, pub i32, ());
53 /// struct T(pub i32, pub i32);
55 pub MANUAL_NON_EXHAUSTIVE,
57 "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
61 pub struct ManualNonExhaustive {
62 msrv: Option<RustcVersion>,
65 impl ManualNonExhaustive {
67 pub fn new(msrv: Option<RustcVersion>) -> Self {
72 impl_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]);
74 impl EarlyLintPass for ManualNonExhaustive {
75 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
76 if !meets_msrv(self.msrv.as_ref(), &msrvs::NON_EXHAUSTIVE) {
81 ItemKind::Enum(def, _) => {
82 check_manual_non_exhaustive_enum(cx, item, &def.variants);
84 ItemKind::Struct(variant_data, _) => {
85 if let VariantData::Unit(..) = variant_data {
89 check_manual_non_exhaustive_struct(cx, item, variant_data);
95 extract_msrv_attr!(EarlyContext);
98 fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants: &[Variant]) {
99 fn is_non_exhaustive_marker(variant: &Variant) -> bool {
100 matches!(variant.data, VariantData::Unit(_))
101 && variant.ident.as_str().starts_with('_')
102 && is_doc_hidden(&variant.attrs)
105 let mut markers = variants.iter().filter(|v| is_non_exhaustive_marker(v));
107 if let Some(marker) = markers.next();
108 if markers.count() == 0 && variants.len() > 1;
112 MANUAL_NON_EXHAUSTIVE,
114 "this seems like a manual implementation of the non-exhaustive pattern",
117 if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive));
118 let header_span = cx.sess.source_map().span_until_char(item.span, '{');
119 if let Some(snippet) = snippet_opt(cx, header_span);
121 diag.span_suggestion(
124 format!("#[non_exhaustive] {}", snippet),
125 Applicability::Unspecified,
129 diag.span_help(marker.span, "remove this variant");
135 fn check_manual_non_exhaustive_struct(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) {
136 fn is_private(field: &FieldDef) -> bool {
137 matches!(field.vis.kind, VisibilityKind::Inherited)
140 fn is_non_exhaustive_marker(field: &FieldDef) -> bool {
141 is_private(field) && field.ty.kind.is_unit() && field.ident.map_or(true, |n| n.as_str().starts_with('_'))
144 fn find_header_span(cx: &EarlyContext<'_>, item: &Item, data: &VariantData) -> Span {
145 let delimiter = match data {
146 VariantData::Struct(..) => '{',
147 VariantData::Tuple(..) => '(',
148 VariantData::Unit(_) => unreachable!("`VariantData::Unit` is already handled above"),
151 cx.sess.source_map().span_until_char(item.span, delimiter)
154 let fields = data.fields();
155 let private_fields = fields.iter().filter(|f| is_private(f)).count();
156 let public_fields = fields.iter().filter(|f| f.vis.kind.is_pub()).count();
159 if private_fields == 1 && public_fields >= 1 && public_fields == fields.len() - 1;
160 if let Some(marker) = fields.iter().find(|f| is_non_exhaustive_marker(f));
164 MANUAL_NON_EXHAUSTIVE,
166 "this seems like a manual implementation of the non-exhaustive pattern",
169 if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive));
170 let header_span = find_header_span(cx, item, data);
171 if let Some(snippet) = snippet_opt(cx, header_span);
173 diag.span_suggestion(
176 format!("#[non_exhaustive] {}", snippet),
177 Applicability::Unspecified,
181 diag.span_help(marker.span, "remove this field");