1
- use clippy_utils:: diagnostics:: span_lint_and_then;
1
+ use clippy_utils:: diagnostics:: { span_lint_and_then, span_lint_hir_and_then } ;
2
2
use clippy_utils:: source:: snippet_opt;
3
- use rustc_ast :: ast :: { Item , ItemKind , Variant , VariantData } ;
3
+ use rustc_data_structures :: fx :: FxIndexMap ;
4
4
use rustc_errors:: Applicability ;
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 } ;
5
10
use rustc_lexer:: TokenKind ;
6
- use rustc_lint:: { EarlyContext , EarlyLintPass } ;
7
- use rustc_session:: declare_lint_pass;
11
+ use rustc_lint:: { LateContext , LateLintPass } ;
12
+ use rustc_middle:: ty:: TyCtxt ;
13
+ use rustc_session:: impl_lint_pass;
8
14
use rustc_span:: Span ;
9
15
10
16
declare_clippy_lint ! {
@@ -70,10 +76,22 @@ declare_clippy_lint! {
70
76
"finds enum variants with empty brackets"
71
77
}
72
78
73
- declare_lint_pass ! ( EmptyWithBrackets => [ EMPTY_STRUCTS_WITH_BRACKETS , EMPTY_ENUM_VARIANTS_WITH_BRACKETS ] ) ;
79
+ #[ derive( Debug ) ]
80
+ enum Usage {
81
+ Unused { redundant_use_sites : Vec < Span > } ,
82
+ Used ,
83
+ }
84
+
85
+ #[ derive( Default ) ]
86
+ pub struct EmptyWithBrackets {
87
+ // Value holds `true` 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
@@ -97,22 +115,91 @@ 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 < ' _ > ) {
119
+ // Don't lint pub enums
120
+ if cx. effective_visibilities . is_reachable ( variant. def_id ) {
121
+ return ;
122
+ }
123
+
101
124
let span_after_ident = variant. span . with_lo ( variant. ident . span . hi ( ) ) ;
102
125
103
- if has_brackets ( & variant. data ) && has_no_fields ( cx, & variant. data , span_after_ident) {
104
- span_lint_and_then (
126
+ if has_no_fields ( cx, & variant. data , span_after_ident) {
127
+ match variant. data {
128
+ VariantData :: Struct { .. } => {
129
+ span_lint_and_then (
130
+ cx,
131
+ EMPTY_ENUM_VARIANTS_WITH_BRACKETS ,
132
+ span_after_ident,
133
+ "enum variant has empty brackets" ,
134
+ |diagnostic| {
135
+ diagnostic. span_suggestion_hidden (
136
+ span_after_ident,
137
+ "remove the brackets" ,
138
+ "" ,
139
+ Applicability :: MaybeIncorrect ,
140
+ ) ;
141
+ } ,
142
+ ) ;
143
+ } ,
144
+ VariantData :: Tuple ( .., local_def_id) => {
145
+ self . empty_tuple_enum_variants
146
+ . entry ( local_def_id)
147
+ . or_insert ( Usage :: Unused {
148
+ redundant_use_sites : vec ! [ ] ,
149
+ } ) ;
150
+ } ,
151
+ VariantData :: Unit ( ..) => { } ,
152
+ }
153
+ }
154
+ }
155
+
156
+ fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
157
+ if let Some ( def_id) = check_expr_for_enum_as_function ( expr) {
158
+ if let Some ( parentheses_span) = call_parentheses_span ( cx. tcx , expr)
159
+ && let Some ( Usage :: Unused {
160
+ ref mut redundant_use_sites,
161
+ } ) = self . empty_tuple_enum_variants . get_mut ( & def_id)
162
+ {
163
+ redundant_use_sites. push ( parentheses_span) ;
164
+ } else {
165
+ self . empty_tuple_enum_variants . insert ( def_id, Usage :: Used ) ;
166
+ }
167
+ }
168
+ }
169
+
170
+ fn check_crate_post ( & mut self , cx : & LateContext < ' _ > ) {
171
+ for ( local_def_id, usage) in & self . empty_tuple_enum_variants {
172
+ let Usage :: Unused { redundant_use_sites } = usage else {
173
+ continue ;
174
+ } ;
175
+ let Node :: Variant ( variant) = cx. tcx . hir_node (
176
+ cx. tcx
177
+ . local_def_id_to_hir_id ( cx. tcx . parent ( local_def_id. to_def_id ( ) ) . expect_local ( ) ) ,
178
+ ) else {
179
+ continue ;
180
+ } ;
181
+ let span = variant. span . with_lo ( variant. ident . span . hi ( ) ) ;
182
+ span_lint_hir_and_then (
105
183
cx,
106
184
EMPTY_ENUM_VARIANTS_WITH_BRACKETS ,
107
- span_after_ident,
185
+ variant. hir_id ,
186
+ span,
108
187
"enum variant has empty brackets" ,
109
188
|diagnostic| {
110
- diagnostic. span_suggestion_hidden (
111
- span_after_ident,
112
- "remove the brackets" ,
113
- "" ,
114
- Applicability :: MaybeIncorrect ,
115
- ) ;
189
+ diagnostic. span_suggestion_hidden (
190
+ span,
191
+ "remove the brackets" ,
192
+ "" ,
193
+ Applicability :: MaybeIncorrect ,
194
+ ) ;
195
+ if !redundant_use_sites. is_empty ( ) {
196
+ let parentheses_spans = redundant_use_sites. iter ( ) . map ( |span| ( * span, String :: new ( ) ) ) . collect ( ) ;
197
+ diagnostic. multipart_suggestion (
198
+ "remove the brackets" ,
199
+ parentheses_spans,
200
+ Applicability :: MaybeIncorrect ,
201
+ ) ;
202
+ }
116
203
} ,
117
204
) ;
118
205
}
@@ -123,11 +210,11 @@ fn has_no_ident_token(braces_span_str: &str) -> bool {
123
210
!rustc_lexer:: tokenize ( braces_span_str) . any ( |t| t. kind == TokenKind :: Ident )
124
211
}
125
212
126
- fn has_brackets ( var_data : & VariantData ) -> bool {
127
- !matches ! ( var_data, VariantData :: Unit ( _ ) )
213
+ fn has_brackets ( var_data : & VariantData < ' _ > ) -> bool {
214
+ !matches ! ( var_data, VariantData :: Unit ( .. ) )
128
215
}
129
216
130
- fn has_no_fields ( cx : & EarlyContext < ' _ > , var_data : & VariantData , braces_span : Span ) -> bool {
217
+ fn has_no_fields ( cx : & LateContext < ' _ > , var_data : & VariantData < ' _ > , braces_span : Span ) -> bool {
131
218
if !var_data. fields ( ) . is_empty ( ) {
132
219
return false ;
133
220
}
@@ -142,6 +229,32 @@ fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Spa
142
229
has_no_ident_token ( braces_span_str. as_ref ( ) )
143
230
}
144
231
232
+ fn call_parentheses_span ( tcx : TyCtxt < ' _ > , expr : & Expr < ' _ > ) -> Option < Span > {
233
+ if let Node :: Expr ( parent) = tcx. parent_hir_node ( expr. hir_id )
234
+ && let ExprKind :: Call ( callee, ..) = parent. kind
235
+ && callee. hir_id == expr. hir_id
236
+ {
237
+ Some ( parent. span . with_lo ( expr. span . hi ( ) ) )
238
+ } else {
239
+ None
240
+ }
241
+ }
242
+
243
+ fn check_expr_for_enum_as_function ( expr : & Expr < ' _ > ) -> Option < LocalDefId > {
244
+ if let ExprKind :: Path ( QPath :: Resolved (
245
+ _,
246
+ Path {
247
+ res : Def ( Ctor ( CtorOf :: Variant , _) , def_id) ,
248
+ ..
249
+ } ,
250
+ ) ) = expr. kind
251
+ {
252
+ def_id. as_local ( )
253
+ } else {
254
+ None
255
+ }
256
+ }
257
+
145
258
#[ cfg( test) ]
146
259
mod unit_test {
147
260
use super :: * ;
0 commit comments