1
1
use clippy_utils:: attrs:: is_doc_hidden;
2
2
use clippy_utils:: diagnostics:: span_lint_and_then;
3
3
use clippy_utils:: source:: snippet_opt;
4
- use clippy_utils:: { meets_msrv, msrvs} ;
4
+ use clippy_utils:: { is_lint_allowed , meets_msrv, msrvs} ;
5
5
use if_chain:: if_chain;
6
- use rustc_ast:: ast:: { FieldDef , Item , ItemKind , Variant , VariantData , VisibilityKind } ;
6
+ use rustc_ast:: ast:: { self , FieldDef , VisibilityKind } ;
7
+ use rustc_data_structures:: fx:: FxHashSet ;
7
8
use rustc_errors:: Applicability ;
8
- use rustc_lint:: { EarlyContext , EarlyLintPass , LintContext } ;
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_middle:: ty:: DefIdTree ;
9
13
use rustc_semver:: RustcVersion ;
10
14
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
15
+ use rustc_span:: def_id:: { DefId , LocalDefId } ;
11
16
use rustc_span:: { sym, Span } ;
12
17
13
18
declare_clippy_lint ! {
@@ -58,55 +63,84 @@ declare_clippy_lint! {
58
63
"manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
59
64
}
60
65
61
- #[ derive ( Clone ) ]
62
- pub struct ManualNonExhaustive {
66
+ #[ allow ( clippy :: module_name_repetitions ) ]
67
+ pub struct ManualNonExhaustiveStruct {
63
68
msrv : Option < RustcVersion > ,
64
69
}
65
70
66
- impl ManualNonExhaustive {
71
+ impl ManualNonExhaustiveStruct {
67
72
#[ must_use]
68
73
pub fn new ( msrv : Option < RustcVersion > ) -> Self {
69
74
Self { msrv }
70
75
}
71
76
}
72
77
73
- impl_lint_pass ! ( ManualNonExhaustive => [ MANUAL_NON_EXHAUSTIVE ] ) ;
78
+ impl_lint_pass ! ( ManualNonExhaustiveStruct => [ MANUAL_NON_EXHAUSTIVE ] ) ;
74
79
75
- impl EarlyLintPass for ManualNonExhaustive {
76
- fn check_item ( & mut self , cx : & EarlyContext < ' _ > , item : & Item ) {
80
+ #[ allow( clippy:: module_name_repetitions) ]
81
+ pub struct ManualNonExhaustiveEnum {
82
+ msrv : Option < RustcVersion > ,
83
+ constructed_enum_variants : FxHashSet < ( DefId , DefId ) > ,
84
+ potential_enums : Vec < ( LocalDefId , LocalDefId , Span , Span ) > ,
85
+ }
86
+
87
+ impl ManualNonExhaustiveEnum {
88
+ #[ must_use]
89
+ pub fn new ( msrv : Option < RustcVersion > ) -> Self {
90
+ Self {
91
+ msrv,
92
+ constructed_enum_variants : FxHashSet :: default ( ) ,
93
+ potential_enums : Vec :: new ( ) ,
94
+ }
95
+ }
96
+ }
97
+
98
+ impl_lint_pass ! ( ManualNonExhaustiveEnum => [ MANUAL_NON_EXHAUSTIVE ] ) ;
99
+
100
+ impl EarlyLintPass for ManualNonExhaustiveStruct {
101
+ fn check_item ( & mut self , cx : & EarlyContext < ' _ > , item : & ast:: Item ) {
77
102
if !meets_msrv ( self . msrv . as_ref ( ) , & msrvs:: NON_EXHAUSTIVE ) {
78
103
return ;
79
104
}
80
105
81
- match & item. kind {
82
- ItemKind :: Enum ( def, _) => {
83
- check_manual_non_exhaustive_enum ( cx, item, & def. variants ) ;
84
- } ,
85
- ItemKind :: Struct ( variant_data, _) => {
86
- if let VariantData :: Unit ( ..) = variant_data {
87
- return ;
88
- }
89
-
90
- check_manual_non_exhaustive_struct ( cx, item, variant_data) ;
91
- } ,
92
- _ => { } ,
106
+ if let ast:: ItemKind :: Struct ( variant_data, _) = & item. kind {
107
+ if let ast:: VariantData :: Unit ( ..) = variant_data {
108
+ return ;
109
+ }
110
+
111
+ check_manual_non_exhaustive_struct ( cx, item, variant_data) ;
93
112
}
94
113
}
95
114
96
115
extract_msrv_attr ! ( EarlyContext ) ;
97
116
}
98
117
99
- fn check_manual_non_exhaustive_enum ( cx : & EarlyContext < ' _ > , item : & Item , variants : & [ Variant ] ) {
100
- fn is_non_exhaustive_marker ( variant : & Variant ) -> bool {
101
- matches ! ( variant. data, VariantData :: Unit ( _) )
102
- && variant. ident . as_str ( ) . starts_with ( '_' )
103
- && is_doc_hidden ( & variant. attrs )
118
+ fn check_manual_non_exhaustive_struct ( cx : & EarlyContext < ' _ > , item : & ast:: Item , data : & ast:: VariantData ) {
119
+ fn is_private ( field : & FieldDef ) -> bool {
120
+ matches ! ( field. vis. kind, VisibilityKind :: Inherited )
121
+ }
122
+
123
+ fn is_non_exhaustive_marker ( field : & FieldDef ) -> bool {
124
+ is_private ( field) && field. ty . kind . is_unit ( ) && field. ident . map_or ( true , |n| n. as_str ( ) . starts_with ( '_' ) )
125
+ }
126
+
127
+ fn find_header_span ( cx : & EarlyContext < ' _ > , item : & ast:: Item , data : & ast:: VariantData ) -> Span {
128
+ let delimiter = match data {
129
+ ast:: VariantData :: Struct ( ..) => '{' ,
130
+ ast:: VariantData :: Tuple ( ..) => '(' ,
131
+ ast:: VariantData :: Unit ( _) => unreachable ! ( "`VariantData::Unit` is already handled above" ) ,
132
+ } ;
133
+
134
+ cx. sess ( ) . source_map ( ) . span_until_char ( item. span , delimiter)
104
135
}
105
136
106
- let mut markers = variants. iter ( ) . filter ( |v| is_non_exhaustive_marker ( v) ) ;
137
+ let fields = data. fields ( ) ;
138
+ let private_fields = fields. iter ( ) . filter ( |f| is_private ( f) ) . count ( ) ;
139
+ let public_fields = fields. iter ( ) . filter ( |f| f. vis . kind . is_pub ( ) ) . count ( ) ;
140
+
107
141
if_chain ! {
108
- if let Some ( marker ) = markers . next ( ) ;
109
- if markers . count ( ) == 0 && variants . len ( ) > 1 ;
142
+ if private_fields == 1 && public_fields >= 1 && public_fields == fields . len ( ) - 1 ;
143
+ if let Some ( marker ) = fields . iter ( ) . find ( |f| is_non_exhaustive_marker ( f ) ) ;
110
144
then {
111
145
span_lint_and_then(
112
146
cx,
@@ -116,7 +150,7 @@ fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants
116
150
|diag| {
117
151
if_chain! {
118
152
if !item. attrs. iter( ) . any( |attr| attr. has_name( sym:: non_exhaustive) ) ;
119
- let header_span = cx . sess ( ) . source_map ( ) . span_until_char ( item. span , '{' ) ;
153
+ let header_span = find_header_span ( cx , item, data ) ;
120
154
if let Some ( snippet) = snippet_opt( cx, header_span) ;
121
155
then {
122
156
diag. span_suggestion(
@@ -127,60 +161,79 @@ fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants
127
161
) ;
128
162
}
129
163
}
130
- diag. span_help( marker. span, "remove this variant " ) ;
164
+ diag. span_help( marker. span, "remove this field " ) ;
131
165
} ) ;
132
166
}
133
167
}
134
168
}
135
169
136
- fn check_manual_non_exhaustive_struct ( cx : & EarlyContext < ' _ > , item : & Item , data : & VariantData ) {
137
- fn is_private ( field : & FieldDef ) -> bool {
138
- matches ! ( field. vis. kind, VisibilityKind :: Inherited )
139
- }
170
+ impl < ' tcx > LateLintPass < ' tcx > for ManualNonExhaustiveEnum {
171
+ fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: Item < ' _ > ) {
172
+ if !meets_msrv ( self . msrv . as_ref ( ) , & msrvs:: NON_EXHAUSTIVE ) {
173
+ return ;
174
+ }
140
175
141
- fn is_non_exhaustive_marker ( field : & FieldDef ) -> bool {
142
- is_private ( field) && field. ty . kind . is_unit ( ) && field. ident . map_or ( true , |n| n. as_str ( ) . starts_with ( '_' ) )
176
+ if let hir:: ItemKind :: Enum ( def, _) = & item. kind
177
+ && def. variants . len ( ) > 1
178
+ {
179
+ let mut iter = def. variants . iter ( ) . filter_map ( |v| {
180
+ let id = cx. tcx . hir ( ) . local_def_id ( v. id ) ;
181
+ ( matches ! ( v. data, hir:: VariantData :: Unit ( _) )
182
+ && v. ident . as_str ( ) . starts_with ( '_' )
183
+ && is_doc_hidden ( cx. tcx . get_attrs ( id. to_def_id ( ) ) ) )
184
+ . then ( || ( id, v. span ) )
185
+ } ) ;
186
+ if let Some ( ( id, span) ) = iter. next ( )
187
+ && iter. next ( ) . is_none ( )
188
+ {
189
+ self . potential_enums . push ( ( item. def_id , id, item. span , span) ) ;
190
+ }
191
+ }
143
192
}
144
193
145
- fn find_header_span ( cx : & EarlyContext < ' _ > , item : & Item , data : & VariantData ) -> Span {
146
- let delimiter = match data {
147
- VariantData :: Struct ( ..) => '{' ,
148
- VariantData :: Tuple ( ..) => '(' ,
149
- VariantData :: Unit ( _) => unreachable ! ( "`VariantData::Unit` is already handled above" ) ,
150
- } ;
151
-
152
- cx. sess ( ) . source_map ( ) . span_until_char ( item. span , delimiter)
194
+ fn check_expr ( & mut self , cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > ) {
195
+ if let ExprKind :: Path ( QPath :: Resolved ( None , p) ) = & e. kind
196
+ && let [ .., name] = p. segments
197
+ && let Res :: Def ( DefKind :: Ctor ( CtorOf :: Variant , CtorKind :: Const ) , id) = p. res
198
+ && name. ident . as_str ( ) . starts_with ( '_' )
199
+ && let Some ( variant_id) = cx. tcx . parent ( id)
200
+ && let Some ( enum_id) = cx. tcx . parent ( variant_id)
201
+ {
202
+ self . constructed_enum_variants . insert ( ( enum_id, variant_id) ) ;
203
+ }
153
204
}
154
205
155
- let fields = data. fields ( ) ;
156
- let private_fields = fields. iter ( ) . filter ( |f| is_private ( f) ) . count ( ) ;
157
- let public_fields = fields. iter ( ) . filter ( |f| f. vis . kind . is_pub ( ) ) . count ( ) ;
158
-
159
- if_chain ! {
160
- if private_fields == 1 && public_fields >= 1 && public_fields == fields. len( ) - 1 ;
161
- if let Some ( marker) = fields. iter( ) . find( |f| is_non_exhaustive_marker( f) ) ;
162
- then {
206
+ fn check_crate_post ( & mut self , cx : & LateContext < ' tcx > ) {
207
+ for & ( enum_id, _, enum_span, variant_span) in
208
+ self . potential_enums . iter ( ) . filter ( |& & ( enum_id, variant_id, _, _) | {
209
+ !self
210
+ . constructed_enum_variants
211
+ . contains ( & ( enum_id. to_def_id ( ) , variant_id. to_def_id ( ) ) )
212
+ && !is_lint_allowed ( cx, MANUAL_NON_EXHAUSTIVE , cx. tcx . hir ( ) . local_def_id_to_hir_id ( enum_id) )
213
+ } )
214
+ {
163
215
span_lint_and_then (
164
216
cx,
165
217
MANUAL_NON_EXHAUSTIVE ,
166
- item . span ,
218
+ enum_span ,
167
219
"this seems like a manual implementation of the non-exhaustive pattern" ,
168
220
|diag| {
169
- if_chain! {
170
- if !item. attrs. iter( ) . any( |attr| attr. has_name( sym:: non_exhaustive) ) ;
171
- let header_span = find_header_span( cx, item, data) ;
172
- if let Some ( snippet) = snippet_opt( cx, header_span) ;
173
- then {
221
+ if !cx. tcx . adt_def ( enum_id) . is_variant_list_non_exhaustive ( )
222
+ && let header_span = cx. sess ( ) . source_map ( ) . span_until_char ( enum_span, '{' )
223
+ && let Some ( snippet) = snippet_opt ( cx, header_span)
224
+ {
174
225
diag. span_suggestion (
175
226
header_span,
176
227
"add the attribute" ,
177
228
format ! ( "#[non_exhaustive] {}" , snippet) ,
178
229
Applicability :: Unspecified ,
179
230
) ;
180
- }
181
231
}
182
- diag. span_help( marker. span, "remove this field" ) ;
183
- } ) ;
232
+ diag. span_help ( variant_span, "remove this variant" ) ;
233
+ } ,
234
+ ) ;
184
235
}
185
236
}
237
+
238
+ extract_msrv_attr ! ( LateContext ) ;
186
239
}
0 commit comments