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 ;
44use 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;
813use rustc_span:: Span ;
914
1015declare_clippy_lint ! {
@@ -70,15 +75,28 @@ declare_clippy_lint! {
7075 "finds enum variants with empty brackets"
7176}
7277
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 ] ) ;
7492
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 < ' _ > ) {
7795 let span_after_ident = item. span . with_lo ( item. ident . span . hi ( ) ) ;
7896
7997 if let ItemKind :: Struct ( var_data, _) = & item. kind
8098 && 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)
82100 {
83101 span_lint_and_then (
84102 cx,
@@ -97,70 +115,166 @@ impl EarlyLintPass for EmptyWithBrackets {
97115 }
98116 }
99117
100- fn check_variant ( & mut self , cx : & EarlyContext < ' _ > , variant : & Variant ) {
118+ fn check_variant ( & mut self , cx : & LateContext < ' _ > , variant : & Variant < ' _ > ) {
101119 let span_after_ident = variant. span . with_lo ( variant. ident . span . hi ( ) ) ;
102120
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 (
105211 cx,
106212 EMPTY_ENUM_VARIANTS_WITH_BRACKETS ,
107- span_after_ident,
213+ variant. hir_id ,
214+ span,
108215 "enum variant has empty brackets" ,
109216 |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+ }
116234 } ,
117235 ) ;
118236 }
119237 }
120238}
121239
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 ( .. ) )
124242}
125243
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+ {
132248 return false ;
133249 }
134250
135251 // there might still be field declarations hidden from the AST
136252 // (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)
143254}
144255
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+ }
162266
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
165279 }
166280}
0 commit comments