1
- use clippy_utils:: diagnostics :: span_lint_and_then ;
2
- use clippy_utils:: source :: snippet_opt ;
3
- use rustc_ast :: ast :: { Item , ItemKind , Variant , VariantData } ;
1
+ use clippy_utils:: attrs :: span_contains_cfg ;
2
+ use clippy_utils:: diagnostics :: { span_lint_and_then , span_lint_hir_and_then } ;
3
+ use rustc_data_structures :: fx :: FxIndexMap ;
4
4
use rustc_errors:: Applicability ;
5
- use rustc_lexer:: TokenKind ;
6
- use rustc_lint:: { EarlyContext , EarlyLintPass } ;
7
- use rustc_session:: declare_lint_pass;
5
+ use rustc_hir:: def:: CtorOf ;
6
+ use rustc_hir:: def:: DefKind :: Ctor ;
7
+ use rustc_hir:: def:: Res :: Def ;
8
+ use rustc_hir:: def_id:: LocalDefId ;
9
+ use rustc_hir:: { Expr , ExprKind , Item , ItemKind , Node , Path , QPath , Variant , VariantData } ;
10
+ use rustc_lint:: { LateContext , LateLintPass } ;
11
+ use rustc_middle:: ty:: TyCtxt ;
12
+ use rustc_session:: impl_lint_pass;
8
13
use rustc_span:: Span ;
9
14
10
15
declare_clippy_lint ! {
@@ -70,15 +75,28 @@ declare_clippy_lint! {
70
75
"finds enum variants with empty brackets"
71
76
}
72
77
73
- declare_lint_pass ! ( EmptyWithBrackets => [ EMPTY_STRUCTS_WITH_BRACKETS , EMPTY_ENUM_VARIANTS_WITH_BRACKETS ] ) ;
78
+ #[ derive( Debug ) ]
79
+ enum Usage {
80
+ Unused { redundant_use_sites : Vec < Span > } ,
81
+ Used ,
82
+ NoDefinition { redundant_use_sites : Vec < Span > } ,
83
+ }
84
+
85
+ #[ derive( Default ) ]
86
+ pub struct EmptyWithBrackets {
87
+ // Value holds `Usage::Used` if the empty tuple variant was used as a function
88
+ empty_tuple_enum_variants : FxIndexMap < LocalDefId , Usage > ,
89
+ }
90
+
91
+ impl_lint_pass ! ( EmptyWithBrackets => [ EMPTY_STRUCTS_WITH_BRACKETS , EMPTY_ENUM_VARIANTS_WITH_BRACKETS ] ) ;
74
92
75
- impl EarlyLintPass for EmptyWithBrackets {
76
- fn check_item ( & mut self , cx : & EarlyContext < ' _ > , item : & Item ) {
93
+ impl LateLintPass < ' _ > for EmptyWithBrackets {
94
+ fn check_item ( & mut self , cx : & LateContext < ' _ > , item : & Item < ' _ > ) {
77
95
let span_after_ident = item. span . with_lo ( item. ident . span . hi ( ) ) ;
78
96
79
97
if let ItemKind :: Struct ( var_data, _) = & item. kind
80
98
&& has_brackets ( var_data)
81
- && has_no_fields ( cx, var_data, span_after_ident)
99
+ && has_no_fields ( cx, Some ( var_data) , span_after_ident)
82
100
{
83
101
span_lint_and_then (
84
102
cx,
@@ -97,70 +115,166 @@ impl EarlyLintPass for EmptyWithBrackets {
97
115
}
98
116
}
99
117
100
- fn check_variant ( & mut self , cx : & EarlyContext < ' _ > , variant : & Variant ) {
118
+ fn check_variant ( & mut self , cx : & LateContext < ' _ > , variant : & Variant < ' _ > ) {
101
119
let span_after_ident = variant. span . with_lo ( variant. ident . span . hi ( ) ) ;
102
120
103
- if has_brackets ( & variant. data ) && has_no_fields ( cx, & variant. data , span_after_ident) {
104
- span_lint_and_then (
121
+ if has_no_fields ( cx, Some ( & variant. data ) , span_after_ident) {
122
+ match variant. data {
123
+ VariantData :: Struct { .. } => {
124
+ span_lint_and_then (
125
+ cx,
126
+ EMPTY_ENUM_VARIANTS_WITH_BRACKETS ,
127
+ span_after_ident,
128
+ "enum variant has empty brackets" ,
129
+ |diagnostic| {
130
+ diagnostic. span_suggestion_hidden (
131
+ span_after_ident,
132
+ "remove the brackets" ,
133
+ "" ,
134
+ Applicability :: MaybeIncorrect ,
135
+ ) ;
136
+ } ,
137
+ ) ;
138
+ } ,
139
+ VariantData :: Tuple ( .., local_def_id) => {
140
+ // Don't lint reachable tuple enums
141
+ if cx. effective_visibilities . is_reachable ( variant. def_id ) {
142
+ return ;
143
+ }
144
+ if let Some ( entry) = self . empty_tuple_enum_variants . get_mut ( & local_def_id) {
145
+ if let Usage :: NoDefinition { redundant_use_sites } = entry {
146
+ * entry = Usage :: Unused {
147
+ redundant_use_sites : redundant_use_sites. clone ( ) ,
148
+ } ;
149
+ }
150
+ } else {
151
+ self . empty_tuple_enum_variants . insert (
152
+ local_def_id,
153
+ Usage :: Unused {
154
+ redundant_use_sites : vec ! [ ] ,
155
+ } ,
156
+ ) ;
157
+ }
158
+ } ,
159
+ VariantData :: Unit ( ..) => { } ,
160
+ }
161
+ }
162
+ }
163
+
164
+ fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
165
+ if let Some ( def_id) = check_expr_for_enum_as_function ( expr) {
166
+ if let Some ( parentheses_span) = call_parentheses_span ( cx. tcx , expr) {
167
+ // Do not count expressions from macro expansion as a redundant use site.
168
+ if expr. span . from_expansion ( ) {
169
+ return ;
170
+ }
171
+ match self . empty_tuple_enum_variants . get_mut ( & def_id) {
172
+ Some (
173
+ Usage :: Unused {
174
+ ref mut redundant_use_sites,
175
+ }
176
+ | Usage :: NoDefinition {
177
+ ref mut redundant_use_sites,
178
+ } ,
179
+ ) => {
180
+ redundant_use_sites. push ( parentheses_span) ;
181
+ } ,
182
+ None => {
183
+ self . empty_tuple_enum_variants . insert (
184
+ def_id,
185
+ Usage :: NoDefinition {
186
+ redundant_use_sites : vec ! [ parentheses_span] ,
187
+ } ,
188
+ ) ;
189
+ } ,
190
+ _ => { } ,
191
+ }
192
+ } else {
193
+ self . empty_tuple_enum_variants . insert ( def_id, Usage :: Used ) ;
194
+ }
195
+ }
196
+ }
197
+
198
+ fn check_crate_post ( & mut self , cx : & LateContext < ' _ > ) {
199
+ for ( local_def_id, usage) in & self . empty_tuple_enum_variants {
200
+ let Usage :: Unused { redundant_use_sites } = usage else {
201
+ continue ;
202
+ } ;
203
+ let Node :: Variant ( variant) = cx. tcx . hir_node (
204
+ cx. tcx
205
+ . local_def_id_to_hir_id ( cx. tcx . parent ( local_def_id. to_def_id ( ) ) . expect_local ( ) ) ,
206
+ ) else {
207
+ continue ;
208
+ } ;
209
+ let span = variant. span . with_lo ( variant. ident . span . hi ( ) ) ;
210
+ span_lint_hir_and_then (
105
211
cx,
106
212
EMPTY_ENUM_VARIANTS_WITH_BRACKETS ,
107
- span_after_ident,
213
+ variant. hir_id ,
214
+ span,
108
215
"enum variant has empty brackets" ,
109
216
|diagnostic| {
110
- diagnostic. span_suggestion_hidden (
111
- span_after_ident,
112
- "remove the brackets" ,
113
- "" ,
114
- Applicability :: MaybeIncorrect ,
115
- ) ;
217
+ if redundant_use_sites. is_empty ( ) {
218
+ diagnostic. span_suggestion_hidden (
219
+ span,
220
+ "remove the brackets" ,
221
+ "" ,
222
+ Applicability :: MaybeIncorrect ,
223
+ ) ;
224
+ } else {
225
+ let mut parentheses_spans: Vec < _ > =
226
+ redundant_use_sites. iter ( ) . map ( |span| ( * span, String :: new ( ) ) ) . collect ( ) ;
227
+ parentheses_spans. push ( ( span, String :: new ( ) ) ) ;
228
+ diagnostic. multipart_suggestion (
229
+ "remove the brackets" ,
230
+ parentheses_spans,
231
+ Applicability :: MaybeIncorrect ,
232
+ ) ;
233
+ }
116
234
} ,
117
235
) ;
118
236
}
119
237
}
120
238
}
121
239
122
- fn has_no_ident_token ( braces_span_str : & str ) -> bool {
123
- !rustc_lexer :: tokenize ( braces_span_str ) . any ( |t| t . kind == TokenKind :: Ident )
240
+ fn has_brackets ( var_data : & VariantData < ' _ > ) -> bool {
241
+ !matches ! ( var_data , VariantData :: Unit ( .. ) )
124
242
}
125
243
126
- fn has_brackets ( var_data : & VariantData ) -> bool {
127
- !matches ! ( var_data, VariantData :: Unit ( _) )
128
- }
129
-
130
- fn has_no_fields ( cx : & EarlyContext < ' _ > , var_data : & VariantData , braces_span : Span ) -> bool {
131
- if !var_data. fields ( ) . is_empty ( ) {
244
+ fn has_no_fields ( cx : & LateContext < ' _ > , var_data_opt : Option < & VariantData < ' _ > > , braces_span : Span ) -> bool {
245
+ if let Some ( var_data) = var_data_opt
246
+ && !var_data. fields ( ) . is_empty ( )
247
+ {
132
248
return false ;
133
249
}
134
250
135
251
// there might still be field declarations hidden from the AST
136
252
// (conditionally compiled code using #[cfg(..)])
137
-
138
- let Some ( braces_span_str) = snippet_opt ( cx, braces_span) else {
139
- return false ;
140
- } ;
141
-
142
- has_no_ident_token ( braces_span_str. as_ref ( ) )
253
+ !span_contains_cfg ( cx, braces_span)
143
254
}
144
255
145
- #[ cfg( test) ]
146
- mod unit_test {
147
- use super :: * ;
148
-
149
- #[ test]
150
- fn test_has_no_ident_token ( ) {
151
- let input = "{ field: u8 }" ;
152
- assert ! ( !has_no_ident_token( input) ) ;
153
-
154
- let input = "(u8, String);" ;
155
- assert ! ( !has_no_ident_token( input) ) ;
156
-
157
- let input = " {
158
- // test = 5
159
- }
160
- " ;
161
- assert ! ( has_no_ident_token( input) ) ;
256
+ fn call_parentheses_span ( tcx : TyCtxt < ' _ > , expr : & Expr < ' _ > ) -> Option < Span > {
257
+ if let Node :: Expr ( parent) = tcx. parent_hir_node ( expr. hir_id )
258
+ && let ExprKind :: Call ( callee, ..) = parent. kind
259
+ && callee. hir_id == expr. hir_id
260
+ {
261
+ Some ( parent. span . with_lo ( expr. span . hi ( ) ) )
262
+ } else {
263
+ None
264
+ }
265
+ }
162
266
163
- let input = " ();" ;
164
- assert ! ( has_no_ident_token( input) ) ;
267
+ fn check_expr_for_enum_as_function ( expr : & Expr < ' _ > ) -> Option < LocalDefId > {
268
+ if let ExprKind :: Path ( QPath :: Resolved (
269
+ _,
270
+ Path {
271
+ res : Def ( Ctor ( CtorOf :: Variant , _) , def_id) ,
272
+ ..
273
+ } ,
274
+ ) ) = expr. kind
275
+ {
276
+ def_id. as_local ( )
277
+ } else {
278
+ None
165
279
}
166
280
}
0 commit comments