1 use clippy_config
::msrvs
::{self, Msrv}
;
2 use clippy_utils
::diagnostics
::{span_lint_and_then, span_lint_hir_and_then}
;
3 use clippy_utils
::is_doc_hidden
;
4 use clippy_utils
::source
::snippet_opt
;
5 use rustc_ast
::ast
::{self, VisibilityKind}
;
7 use rustc_data_structures
::fx
::FxHashSet
;
8 use rustc_errors
::Applicability
;
9 use rustc_hir
::def
::{CtorKind, CtorOf, DefKind, Res}
;
10 use rustc_hir
::{self as hir, Expr, ExprKind, QPath}
;
11 use rustc_lint
::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}
;
12 use rustc_session
::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
{
69 impl ManualNonExhaustiveStruct
{
71 pub fn new(msrv
: Msrv
) -> Self {
76 impl_lint_pass
!(ManualNonExhaustiveStruct
=> [MANUAL_NON_EXHAUSTIVE
]);
78 #[expect(clippy::module_name_repetitions)]
79 pub struct ManualNonExhaustiveEnum
{
81 constructed_enum_variants
: FxHashSet
<(DefId
, DefId
)>,
82 potential_enums
: Vec
<(LocalDefId
, LocalDefId
, Span
, Span
)>,
85 impl ManualNonExhaustiveEnum
{
87 pub fn new(msrv
: Msrv
) -> 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 !self.msrv
.meets(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()
124 MANUAL_NON_EXHAUSTIVE
,
126 "this seems like a manual implementation of the non-exhaustive pattern",
128 if !item
.attrs
.iter().any(|attr
| attr
.has_name(sym
::non_exhaustive
))
129 && let header_span
= cx
.sess().source_map().span_until_char(item
.span
, delimiter
)
130 && let Some(snippet
) = snippet_opt(cx
, header_span
)
132 diag
.span_suggestion(
135 format
!("#[non_exhaustive] {snippet}"),
136 Applicability
::Unspecified
,
139 diag
.span_help(field
.span
, "remove this field");
146 extract_msrv_attr
!(EarlyContext
);
149 impl<'tcx
> LateLintPass
<'tcx
> for ManualNonExhaustiveEnum
{
150 fn check_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::Item
<'_
>) {
151 if !self.msrv
.meets(msrvs
::NON_EXHAUSTIVE
) {
155 if let hir
::ItemKind
::Enum(def
, _
) = &item
.kind
156 && def
.variants
.len() > 1
158 let mut iter
= def
.variants
.iter().filter_map(|v
| {
159 (matches
!(v
.data
, hir
::VariantData
::Unit(_
, _
))
160 && is_doc_hidden(cx
.tcx
.hir().attrs(v
.hir_id
))
161 && !attr
::contains_name(cx
.tcx
.hir().attrs(item
.hir_id()), sym
::non_exhaustive
))
162 .then_some((v
.def_id
, v
.span
))
164 if let Some((id
, span
)) = iter
.next()
165 && iter
.next().is_none()
167 self.potential_enums
.push((item
.owner_id
.def_id
, id
, item
.span
, span
));
172 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, e
: &'tcx Expr
<'_
>) {
173 if let ExprKind
::Path(QPath
::Resolved(None
, p
)) = &e
.kind
174 && let Res
::Def(DefKind
::Ctor(CtorOf
::Variant
, CtorKind
::Const
), id
) = p
.res
176 let variant_id
= cx
.tcx
.parent(id
);
177 let enum_id
= cx
.tcx
.parent(variant_id
);
179 self.constructed_enum_variants
.insert((enum_id
, variant_id
));
183 fn check_crate_post(&mut self, cx
: &LateContext
<'tcx
>) {
184 for &(enum_id
, _
, enum_span
, variant_span
) in
185 self.potential_enums
.iter().filter(|&&(enum_id
, variant_id
, _
, _
)| {
187 .constructed_enum_variants
188 .contains(&(enum_id
.to_def_id(), variant_id
.to_def_id()))
191 let hir_id
= cx
.tcx
.local_def_id_to_hir_id(enum_id
);
192 span_lint_hir_and_then(
194 MANUAL_NON_EXHAUSTIVE
,
197 "this seems like a manual implementation of the non-exhaustive pattern",
199 let header_span
= cx
.sess().source_map().span_until_char(enum_span
, '
{'
);
200 if let Some(snippet
) = snippet_opt(cx
, header_span
) {
201 diag
.span_suggestion(
204 format
!("#[non_exhaustive] {snippet}"),
205 Applicability
::Unspecified
,
208 diag
.span_help(variant_span
, "remove this variant");
214 extract_msrv_attr
!(LateContext
);