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,10 +75,23 @@ 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
if let ItemKind :: Struct ( ident, var_data, _) = & item. kind
78
96
&& has_brackets ( var_data)
79
97
&& let span_after_ident = item. span . with_lo ( ident. span . hi ( ) )
@@ -96,70 +114,175 @@ impl EarlyLintPass for EmptyWithBrackets {
96
114
}
97
115
}
98
116
99
- fn check_variant ( & mut self , cx : & EarlyContext < ' _ > , variant : & Variant ) {
117
+ fn check_variant ( & mut self , cx : & LateContext < ' _ > , variant : & Variant < ' _ > ) {
118
+ // the span of the parentheses/braces
100
119
let span_after_ident = variant. span . with_lo ( variant. ident . span . hi ( ) ) ;
101
120
102
- if has_brackets ( & variant. data ) && has_no_fields ( cx, & variant. data , span_after_ident) {
103
- span_lint_and_then (
121
+ if has_no_fields ( cx, & variant. data , span_after_ident) {
122
+ match variant. data {
123
+ VariantData :: Struct { .. } => {
124
+ // Empty struct variants can be linted immediately
125
+ span_lint_and_then (
126
+ cx,
127
+ EMPTY_ENUM_VARIANTS_WITH_BRACKETS ,
128
+ span_after_ident,
129
+ "enum variant has empty brackets" ,
130
+ |diagnostic| {
131
+ diagnostic. span_suggestion_hidden (
132
+ span_after_ident,
133
+ "remove the brackets" ,
134
+ "" ,
135
+ Applicability :: MaybeIncorrect ,
136
+ ) ;
137
+ } ,
138
+ ) ;
139
+ } ,
140
+ VariantData :: Tuple ( .., local_def_id) => {
141
+ // Don't lint reachable tuple enums
142
+ if cx. effective_visibilities . is_reachable ( variant. def_id ) {
143
+ return ;
144
+ }
145
+ if let Some ( entry) = self . empty_tuple_enum_variants . get_mut ( & local_def_id) {
146
+ // empty_tuple_enum_variants contains Usage::NoDefinition if the variant was called before the
147
+ // definition was encountered. Now that there's a definition, convert it
148
+ // to Usage::Unused.
149
+ if let Usage :: NoDefinition { redundant_use_sites } = entry {
150
+ * entry = Usage :: Unused {
151
+ redundant_use_sites : redundant_use_sites. clone ( ) ,
152
+ } ;
153
+ }
154
+ } else {
155
+ self . empty_tuple_enum_variants . insert (
156
+ local_def_id,
157
+ Usage :: Unused {
158
+ redundant_use_sites : vec ! [ ] ,
159
+ } ,
160
+ ) ;
161
+ }
162
+ } ,
163
+ VariantData :: Unit ( ..) => { } ,
164
+ }
165
+ }
166
+ }
167
+
168
+ fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
169
+ if let Some ( def_id) = check_expr_for_enum_as_function ( expr) {
170
+ if let Some ( parentheses_span) = call_parentheses_span ( cx. tcx , expr) {
171
+ // Do not count expressions from macro expansion as a redundant use site.
172
+ if expr. span . from_expansion ( ) {
173
+ return ;
174
+ }
175
+ match self . empty_tuple_enum_variants . get_mut ( & def_id) {
176
+ Some (
177
+ & mut ( Usage :: Unused {
178
+ ref mut redundant_use_sites,
179
+ }
180
+ | Usage :: NoDefinition {
181
+ ref mut redundant_use_sites,
182
+ } ) ,
183
+ ) => {
184
+ redundant_use_sites. push ( parentheses_span) ;
185
+ } ,
186
+ None => {
187
+ // The variant isn't in the IndexMap which means its definition wasn't encountered yet.
188
+ self . empty_tuple_enum_variants . insert (
189
+ def_id,
190
+ Usage :: NoDefinition {
191
+ redundant_use_sites : vec ! [ parentheses_span] ,
192
+ } ,
193
+ ) ;
194
+ } ,
195
+ _ => { } ,
196
+ }
197
+ } else {
198
+ // The parentheses are not redundant.
199
+ self . empty_tuple_enum_variants . insert ( def_id, Usage :: Used ) ;
200
+ }
201
+ }
202
+ }
203
+
204
+ fn check_crate_post ( & mut self , cx : & LateContext < ' _ > ) {
205
+ for ( local_def_id, usage) in & self . empty_tuple_enum_variants {
206
+ // Ignore all variants with Usage::Used or Usage::NoDefinition
207
+ let Usage :: Unused { redundant_use_sites } = usage else {
208
+ continue ;
209
+ } ;
210
+ // Attempt to fetch the Variant from LocalDefId.
211
+ let Node :: Variant ( variant) = cx. tcx . hir_node (
212
+ cx. tcx
213
+ . local_def_id_to_hir_id ( cx. tcx . parent ( local_def_id. to_def_id ( ) ) . expect_local ( ) ) ,
214
+ ) else {
215
+ continue ;
216
+ } ;
217
+ // Span of the parentheses in variant definition
218
+ let span = variant. span . with_lo ( variant. ident . span . hi ( ) ) ;
219
+ span_lint_hir_and_then (
104
220
cx,
105
221
EMPTY_ENUM_VARIANTS_WITH_BRACKETS ,
106
- span_after_ident,
222
+ variant. hir_id ,
223
+ span,
107
224
"enum variant has empty brackets" ,
108
225
|diagnostic| {
109
- diagnostic. span_suggestion_hidden (
110
- span_after_ident,
111
- "remove the brackets" ,
112
- "" ,
113
- Applicability :: MaybeIncorrect ,
114
- ) ;
226
+ if redundant_use_sites. is_empty ( ) {
227
+ // If there's no redundant use sites, the definition is the only place to modify.
228
+ diagnostic. span_suggestion_hidden (
229
+ span,
230
+ "remove the brackets" ,
231
+ "" ,
232
+ Applicability :: MaybeIncorrect ,
233
+ ) ;
234
+ } else {
235
+ let mut parentheses_spans: Vec < _ > =
236
+ redundant_use_sites. iter ( ) . map ( |span| ( * span, String :: new ( ) ) ) . collect ( ) ;
237
+ parentheses_spans. push ( ( span, String :: new ( ) ) ) ;
238
+ diagnostic. multipart_suggestion (
239
+ "remove the brackets" ,
240
+ parentheses_spans,
241
+ Applicability :: MaybeIncorrect ,
242
+ ) ;
243
+ }
115
244
} ,
116
245
) ;
117
246
}
118
247
}
119
248
}
120
249
121
- fn has_no_ident_token ( braces_span_str : & str ) -> bool {
122
- !rustc_lexer :: tokenize ( braces_span_str ) . any ( |t| t . kind == TokenKind :: Ident )
250
+ fn has_brackets ( var_data : & VariantData < ' _ > ) -> bool {
251
+ !matches ! ( var_data , VariantData :: Unit ( .. ) )
123
252
}
124
253
125
- fn has_brackets ( var_data : & VariantData ) -> bool {
126
- !matches ! ( var_data, VariantData :: Unit ( _) )
127
- }
128
-
129
- fn has_no_fields ( cx : & EarlyContext < ' _ > , var_data : & VariantData , braces_span : Span ) -> bool {
130
- if !var_data. fields ( ) . is_empty ( ) {
131
- return false ;
132
- }
133
-
254
+ fn has_no_fields ( cx : & LateContext < ' _ > , var_data : & VariantData < ' _ > , braces_span : Span ) -> bool {
255
+ var_data. fields ( ) . is_empty ( ) &&
134
256
// there might still be field declarations hidden from the AST
135
257
// (conditionally compiled code using #[cfg(..)])
136
-
137
- let Some ( braces_span_str) = snippet_opt ( cx, braces_span) else {
138
- return false ;
139
- } ;
140
-
141
- has_no_ident_token ( braces_span_str. as_ref ( ) )
258
+ !span_contains_cfg ( cx, braces_span)
142
259
}
143
260
144
- #[ cfg( test) ]
145
- mod unit_test {
146
- use super :: * ;
147
-
148
- #[ test]
149
- fn test_has_no_ident_token ( ) {
150
- let input = "{ field: u8 }" ;
151
- assert ! ( !has_no_ident_token( input) ) ;
152
-
153
- let input = "(u8, String);" ;
154
- assert ! ( !has_no_ident_token( input) ) ;
155
-
156
- let input = " {
157
- // test = 5
158
- }
159
- " ;
160
- assert ! ( has_no_ident_token( input) ) ;
261
+ // If expression HIR ID and callee HIR ID are same, returns the span of the parentheses, else,
262
+ // returns None.
263
+ fn call_parentheses_span ( tcx : TyCtxt < ' _ > , expr : & Expr < ' _ > ) -> Option < Span > {
264
+ if let Node :: Expr ( parent) = tcx. parent_hir_node ( expr. hir_id )
265
+ && let ExprKind :: Call ( callee, ..) = parent. kind
266
+ && callee. hir_id == expr. hir_id
267
+ {
268
+ Some ( parent. span . with_lo ( expr. span . hi ( ) ) )
269
+ } else {
270
+ None
271
+ }
272
+ }
161
273
162
- let input = " ();" ;
163
- assert ! ( has_no_ident_token( input) ) ;
274
+ // Returns the LocalDefId of the variant being called as a function if it exists.
275
+ fn check_expr_for_enum_as_function ( expr : & Expr < ' _ > ) -> Option < LocalDefId > {
276
+ if let ExprKind :: Path ( QPath :: Resolved (
277
+ _,
278
+ Path {
279
+ res : Def ( Ctor ( CtorOf :: Variant , _) , def_id) ,
280
+ ..
281
+ } ,
282
+ ) ) = expr. kind
283
+ {
284
+ def_id. as_local ( )
285
+ } else {
286
+ None
164
287
}
165
288
}
0 commit comments