1 use clippy_utils
::diagnostics
::{span_lint_and_then, span_lint_hir_and_then}
;
2 use clippy_utils
::source
::snippet_opt
;
3 use clippy_utils
::{is_doc_hidden, meets_msrv, msrvs}
;
4 use rustc_ast
::ast
::{self, VisibilityKind}
;
5 use rustc_data_structures
::fx
::FxHashSet
;
6 use rustc_errors
::Applicability
;
7 use rustc_hir
::def
::{CtorKind, CtorOf, DefKind, Res}
;
8 use rustc_hir
::{self as hir, Expr, ExprKind, QPath}
;
9 use rustc_lint
::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}
;
10 use rustc_middle
::ty
::DefIdTree
;
11 use rustc_semver
::RustcVersion
;
12 use rustc_session
::{declare_tool_lint, impl_lint_pass}
;
13 use rustc_span
::def_id
::{DefId, LocalDefId}
;
14 use rustc_span
::{sym, Span}
;
16 declare_clippy_lint
! {
18 /// Checks for manual implementations of the non-exhaustive pattern.
20 /// ### Why is this bad?
21 /// Using the #[non_exhaustive] attribute expresses better the intent
22 /// and allows possible optimizations when applied to enums.
39 /// struct T(pub i32, pub i32, ());
56 /// struct T(pub i32, pub i32);
58 #[clippy::version = "1.45.0"]
59 pub MANUAL_NON_EXHAUSTIVE
,
61 "manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
64 #[expect(clippy::module_name_repetitions)]
65 pub struct ManualNonExhaustiveStruct
{
66 msrv
: Option
<RustcVersion
>,
69 impl ManualNonExhaustiveStruct
{
71 pub fn new(msrv
: Option
<RustcVersion
>) -> Self {
76 impl_lint_pass
!(ManualNonExhaustiveStruct
=> [MANUAL_NON_EXHAUSTIVE
]);
78 #[expect(clippy::module_name_repetitions)]
79 pub struct ManualNonExhaustiveEnum
{
80 msrv
: Option
<RustcVersion
>,
81 constructed_enum_variants
: FxHashSet
<(DefId
, DefId
)>,
82 potential_enums
: Vec
<(LocalDefId
, LocalDefId
, Span
, Span
)>,
85 impl ManualNonExhaustiveEnum
{
87 pub fn new(msrv
: Option
<RustcVersion
>) -> Self {
90 constructed_enum_variants
: FxHashSet
::default(),
91 potential_enums
: Vec
::new(),
96 impl_lint_pass
!(ManualNonExhaustiveEnum
=> [MANUAL_NON_EXHAUSTIVE
]);
98 impl EarlyLintPass
for ManualNonExhaustiveStruct
{
99 fn check_item(&mut self, cx
: &EarlyContext
<'_
>, item
: &ast
::Item
) {
100 if !meets_msrv(self.msrv
, msrvs
::NON_EXHAUSTIVE
) {
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,
110 if fields
.len() <= 1 {
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(())),
118 if let Some(Ok(field
)) = iter
.next()
119 && iter
.next().is_none()
120 && field
.ty
.kind
.is_unit()
121 && field
.ident
.map_or(true, |name
| name
.as_str().starts_with('_'
))
125 MANUAL_NON_EXHAUSTIVE
,
127 "this seems like a manual implementation of the non-exhaustive pattern",
129 if !item
.attrs
.iter().any(|attr
| attr
.has_name(sym
::non_exhaustive
))
130 && let header_span
= cx
.sess().source_map().span_until_char(item
.span
, delimiter
)
131 && let Some(snippet
) = snippet_opt(cx
, header_span
)
133 diag
.span_suggestion(
136 format
!("#[non_exhaustive] {}", snippet
),
137 Applicability
::Unspecified
,
140 diag
.span_help(field
.span
, "remove this field");
147 extract_msrv_attr
!(EarlyContext
);
150 impl<'tcx
> LateLintPass
<'tcx
> for ManualNonExhaustiveEnum
{
151 fn check_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::Item
<'_
>) {
152 if !meets_msrv(self.msrv
, msrvs
::NON_EXHAUSTIVE
) {
156 if let hir
::ItemKind
::Enum(def
, _
) = &item
.kind
157 && def
.variants
.len() > 1
159 let mut iter
= def
.variants
.iter().filter_map(|v
| {
160 let id
= cx
.tcx
.hir().local_def_id(v
.id
);
161 (matches
!(v
.data
, hir
::VariantData
::Unit(_
))
162 && v
.ident
.as_str().starts_with('_'
)
163 && is_doc_hidden(cx
.tcx
.hir().attrs(v
.id
)))
164 .then_some((id
, v
.span
))
166 if let Some((id
, span
)) = iter
.next()
167 && iter
.next().is_none()
169 self.potential_enums
.push((item
.def_id
, id
, item
.span
, span
));
174 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, e
: &'tcx Expr
<'_
>) {
175 if let ExprKind
::Path(QPath
::Resolved(None
, p
)) = &e
.kind
176 && let [.., name
] = p
.segments
177 && let Res
::Def(DefKind
::Ctor(CtorOf
::Variant
, CtorKind
::Const
), id
) = p
.res
178 && name
.ident
.as_str().starts_with('_'
)
180 let variant_id
= cx
.tcx
.parent(id
);
181 let enum_id
= cx
.tcx
.parent(variant_id
);
183 self.constructed_enum_variants
.insert((enum_id
, variant_id
));
187 fn check_crate_post(&mut self, cx
: &LateContext
<'tcx
>) {
188 for &(enum_id
, _
, enum_span
, variant_span
) in
189 self.potential_enums
.iter().filter(|&&(enum_id
, variant_id
, _
, _
)| {
191 .constructed_enum_variants
192 .contains(&(enum_id
.to_def_id(), variant_id
.to_def_id()))
195 let hir_id
= cx
.tcx
.hir().local_def_id_to_hir_id(enum_id
);
196 span_lint_hir_and_then(
198 MANUAL_NON_EXHAUSTIVE
,
201 "this seems like a manual implementation of the non-exhaustive pattern",
203 if !cx
.tcx
.adt_def(enum_id
).is_variant_list_non_exhaustive()
204 && let header_span
= cx
.sess().source_map().span_until_char(enum_span
, '
{'
)
205 && let Some(snippet
) = snippet_opt(cx
, header_span
)
207 diag
.span_suggestion(
210 format
!("#[non_exhaustive] {}", snippet
),
211 Applicability
::Unspecified
,
214 diag
.span_help(variant_span
, "remove this variant");
220 extract_msrv_attr
!(LateContext
);