@@ -2,16 +2,16 @@ use clippy_config::msrvs::{self, Msrv};
2
2
use clippy_config:: Conf ;
3
3
use clippy_utils:: diagnostics:: { span_lint_and_then, span_lint_hir_and_then} ;
4
4
use clippy_utils:: is_doc_hidden;
5
- use clippy_utils:: source:: SpanRangeExt ;
6
- use rustc_ast :: ast :: { self , VisibilityKind } ;
5
+ use clippy_utils:: source:: snippet_indent ;
6
+ use itertools :: Itertools ;
7
7
use rustc_ast:: attr;
8
8
use rustc_data_structures:: fx:: FxHashSet ;
9
9
use rustc_errors:: Applicability ;
10
10
use rustc_hir:: def:: { CtorKind , CtorOf , DefKind , Res } ;
11
- use rustc_hir:: { self as hir , Expr , ExprKind , QPath } ;
12
- use rustc_lint:: { EarlyContext , EarlyLintPass , LateContext , LateLintPass , LintContext } ;
11
+ use rustc_hir:: { Expr , ExprKind , Item , ItemKind , QPath , TyKind , VariantData } ;
12
+ use rustc_lint:: { LateContext , LateLintPass } ;
13
13
use rustc_session:: impl_lint_pass;
14
- use rustc_span:: def_id:: { DefId , LocalDefId } ;
14
+ use rustc_span:: def_id:: LocalDefId ;
15
15
use rustc_span:: { sym, Span } ;
16
16
17
17
declare_clippy_lint ! {
@@ -62,29 +62,13 @@ declare_clippy_lint! {
62
62
"manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
63
63
}
64
64
65
- #[ expect( clippy:: module_name_repetitions) ]
66
- pub struct ManualNonExhaustiveStruct {
65
+ pub struct ManualNonExhaustive {
67
66
msrv : Msrv ,
68
- }
69
-
70
- impl ManualNonExhaustiveStruct {
71
- pub fn new ( conf : & ' static Conf ) -> Self {
72
- Self {
73
- msrv : conf. msrv . clone ( ) ,
74
- }
75
- }
76
- }
77
-
78
- impl_lint_pass ! ( ManualNonExhaustiveStruct => [ MANUAL_NON_EXHAUSTIVE ] ) ;
79
-
80
- #[ expect( clippy:: module_name_repetitions) ]
81
- pub struct ManualNonExhaustiveEnum {
82
- msrv : Msrv ,
83
- constructed_enum_variants : FxHashSet < ( DefId , DefId ) > ,
67
+ constructed_enum_variants : FxHashSet < LocalDefId > ,
84
68
potential_enums : Vec < ( LocalDefId , LocalDefId , Span , Span ) > ,
85
69
}
86
70
87
- impl ManualNonExhaustiveEnum {
71
+ impl ManualNonExhaustive {
88
72
pub fn new ( conf : & ' static Conf ) -> Self {
89
73
Self {
90
74
msrv : conf. msrv . clone ( ) ,
@@ -94,96 +78,78 @@ impl ManualNonExhaustiveEnum {
94
78
}
95
79
}
96
80
97
- impl_lint_pass ! ( ManualNonExhaustiveEnum => [ MANUAL_NON_EXHAUSTIVE ] ) ;
98
-
99
- impl EarlyLintPass for ManualNonExhaustiveStruct {
100
- fn check_item ( & mut self , cx : & EarlyContext < ' _ > , item : & ast:: Item ) {
101
- if let ast:: ItemKind :: Struct ( variant_data, _) = & item. kind
102
- && let ( fields, delimiter) = match variant_data {
103
- ast:: VariantData :: Struct { fields, .. } => ( & * * fields, '{' ) ,
104
- ast:: VariantData :: Tuple ( fields, _) => ( & * * fields, '(' ) ,
105
- ast:: VariantData :: Unit ( _) => return ,
106
- }
107
- && fields. len ( ) > 1
108
- && self . msrv . meets ( msrvs:: NON_EXHAUSTIVE )
109
- {
110
- let mut iter = fields. iter ( ) . filter_map ( |f| match f. vis . kind {
111
- VisibilityKind :: Public => None ,
112
- VisibilityKind :: Inherited => Some ( Ok ( f) ) ,
113
- VisibilityKind :: Restricted { .. } => Some ( Err ( ( ) ) ) ,
114
- } ) ;
115
- if let Some ( Ok ( field) ) = iter. next ( )
116
- && iter. next ( ) . is_none ( )
117
- && field. ty . kind . is_unit ( )
118
- {
119
- span_lint_and_then (
120
- cx,
121
- MANUAL_NON_EXHAUSTIVE ,
122
- item. span ,
123
- "this seems like a manual implementation of the non-exhaustive pattern" ,
124
- |diag| {
125
- if !item. attrs . iter ( ) . any ( |attr| attr. has_name ( sym:: non_exhaustive) )
126
- && let header_span = cx. sess ( ) . source_map ( ) . span_until_char ( item. span , delimiter)
127
- && let Some ( snippet) = header_span. get_source_text ( cx)
128
- {
129
- diag. span_suggestion (
130
- header_span,
131
- "add the attribute" ,
132
- format ! ( "#[non_exhaustive] {snippet}" ) ,
133
- Applicability :: Unspecified ,
134
- ) ;
135
- }
136
- diag. span_help ( field. span , "remove this field" ) ;
137
- } ,
138
- ) ;
139
- }
140
- }
141
- }
81
+ impl_lint_pass ! ( ManualNonExhaustive => [ MANUAL_NON_EXHAUSTIVE ] ) ;
142
82
143
- extract_msrv_attr ! ( EarlyContext ) ;
144
- }
145
-
146
- impl < ' tcx > LateLintPass < ' tcx > for ManualNonExhaustiveEnum {
147
- fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: Item < ' _ > ) {
148
- if !self . msrv . meets ( msrvs:: NON_EXHAUSTIVE ) {
83
+ impl < ' tcx > LateLintPass < ' tcx > for ManualNonExhaustive {
84
+ fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx Item < ' _ > ) {
85
+ if !self . msrv . meets ( msrvs:: NON_EXHAUSTIVE ) || !cx. effective_visibilities . is_exported ( item. owner_id . def_id ) {
149
86
return ;
150
87
}
151
88
152
- if let hir:: ItemKind :: Enum ( def, _) = & item. kind
153
- && def. variants . len ( ) > 1
154
- {
155
- let mut iter = def. variants . iter ( ) . filter_map ( |v| {
156
- ( matches ! ( v. data, hir:: VariantData :: Unit ( _, _) )
157
- && is_doc_hidden ( cx. tcx . hir ( ) . attrs ( v. hir_id ) )
158
- && !attr:: contains_name ( cx. tcx . hir ( ) . attrs ( item. hir_id ( ) ) , sym:: non_exhaustive) )
159
- . then_some ( ( v. def_id , v. span ) )
160
- } ) ;
161
- if let Some ( ( id, span) ) = iter. next ( )
162
- && iter. next ( ) . is_none ( )
163
- {
164
- self . potential_enums . push ( ( item. owner_id . def_id , id, item. span , span) ) ;
165
- }
89
+ match item. kind {
90
+ ItemKind :: Enum ( def, _) if def. variants . len ( ) > 1 => {
91
+ let iter = def. variants . iter ( ) . filter_map ( |v| {
92
+ ( matches ! ( v. data, VariantData :: Unit ( _, _) ) && is_doc_hidden ( cx. tcx . hir ( ) . attrs ( v. hir_id ) ) )
93
+ . then_some ( ( v. def_id , v. span ) )
94
+ } ) ;
95
+ if let Ok ( ( id, span) ) = iter. exactly_one ( )
96
+ && !attr:: contains_name ( cx. tcx . hir ( ) . attrs ( item. hir_id ( ) ) , sym:: non_exhaustive)
97
+ {
98
+ self . potential_enums . push ( ( item. owner_id . def_id , id, item. span , span) ) ;
99
+ }
100
+ } ,
101
+ ItemKind :: Struct ( variant_data, _) => {
102
+ let fields = variant_data. fields ( ) ;
103
+ let private_fields = fields
104
+ . iter ( )
105
+ . filter ( |field| !cx. effective_visibilities . is_exported ( field. def_id ) ) ;
106
+ if fields. len ( ) > 1
107
+ && let Ok ( field) = private_fields. exactly_one ( )
108
+ && let TyKind :: Tup ( [ ] ) = field. ty . kind
109
+ {
110
+ span_lint_and_then (
111
+ cx,
112
+ MANUAL_NON_EXHAUSTIVE ,
113
+ item. span ,
114
+ "this seems like a manual implementation of the non-exhaustive pattern" ,
115
+ |diag| {
116
+ if let Some ( non_exhaustive) =
117
+ attr:: find_by_name ( cx. tcx . hir ( ) . attrs ( item. hir_id ( ) ) , sym:: non_exhaustive)
118
+ {
119
+ diag. span_note ( non_exhaustive. span , "the struct is already non-exhaustive" ) ;
120
+ } else {
121
+ let indent = snippet_indent ( cx, item. span ) . unwrap_or_default ( ) ;
122
+ diag. span_suggestion_verbose (
123
+ item. span . shrink_to_lo ( ) ,
124
+ "use the `#[non_exhaustive]` attribute instead" ,
125
+ format ! ( "#[non_exhaustive]\n {indent}" ) ,
126
+ Applicability :: MaybeIncorrect ,
127
+ ) ;
128
+ }
129
+ diag. span_help ( field. span , "remove this field" ) ;
130
+ } ,
131
+ ) ;
132
+ }
133
+ } ,
134
+ _ => { } ,
166
135
}
167
136
}
168
137
169
138
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > ) {
170
139
if let ExprKind :: Path ( QPath :: Resolved ( None , p) ) = & e. kind
171
- && let Res :: Def ( DefKind :: Ctor ( CtorOf :: Variant , CtorKind :: Const ) , id) = p. res
140
+ && let Res :: Def ( DefKind :: Ctor ( CtorOf :: Variant , CtorKind :: Const ) , ctor_id) = p. res
141
+ && let Some ( local_ctor) = ctor_id. as_local ( )
172
142
{
173
- let variant_id = cx. tcx . parent ( id) ;
174
- let enum_id = cx. tcx . parent ( variant_id) ;
175
-
176
- self . constructed_enum_variants . insert ( ( enum_id, variant_id) ) ;
143
+ let variant_id = cx. tcx . local_parent ( local_ctor) ;
144
+ self . constructed_enum_variants . insert ( variant_id) ;
177
145
}
178
146
}
179
147
180
148
fn check_crate_post ( & mut self , cx : & LateContext < ' tcx > ) {
181
- for & ( enum_id, _, enum_span, variant_span) in
182
- self . potential_enums . iter ( ) . filter ( |& & ( enum_id, variant_id, _, _) | {
183
- !self
184
- . constructed_enum_variants
185
- . contains ( & ( enum_id. to_def_id ( ) , variant_id. to_def_id ( ) ) )
186
- } )
149
+ for & ( enum_id, _, enum_span, variant_span) in self
150
+ . potential_enums
151
+ . iter ( )
152
+ . filter ( |( _, variant_id, _, _) | !self . constructed_enum_variants . contains ( variant_id) )
187
153
{
188
154
let hir_id = cx. tcx . local_def_id_to_hir_id ( enum_id) ;
189
155
span_lint_hir_and_then (
@@ -193,15 +159,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
193
159
enum_span,
194
160
"this seems like a manual implementation of the non-exhaustive pattern" ,
195
161
|diag| {
196
- let header_span = cx. sess ( ) . source_map ( ) . span_until_char ( enum_span, '{' ) ;
197
- if let Some ( snippet) = header_span. get_source_text ( cx) {
198
- diag. span_suggestion (
199
- header_span,
200
- "add the attribute" ,
201
- format ! ( "#[non_exhaustive] {snippet}" ) ,
202
- Applicability :: Unspecified ,
203
- ) ;
204
- }
162
+ let indent = snippet_indent ( cx, enum_span) . unwrap_or_default ( ) ;
163
+ diag. span_suggestion_verbose (
164
+ enum_span. shrink_to_lo ( ) ,
165
+ "use the `#[non_exhaustive]` attribute instead" ,
166
+ format ! ( "#[non_exhaustive]\n {indent}" ) ,
167
+ Applicability :: MaybeIncorrect ,
168
+ ) ;
205
169
diag. span_help ( variant_span, "remove this variant" ) ;
206
170
} ,
207
171
) ;
0 commit comments