1- use clippy_utils:: diagnostics:: span_lint_and_then;
1+ use clippy_utils:: diagnostics:: { span_lint_and_then, span_lint_hir_and_then } ;
22use clippy_utils:: source:: snippet_opt;
3- use rustc_ast :: ast :: { Item , ItemKind , Variant , VariantData } ;
3+ use rustc_data_structures :: fx :: FxIndexMap ;
44use 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 } ;
510use 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;
814use rustc_span:: Span ;
915
1016declare_clippy_lint ! {
@@ -70,15 +76,27 @@ declare_clippy_lint! {
7076 "finds enum variants with empty brackets"
7177}
7278
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 `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,38 +115,122 @@ 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+ self . empty_tuple_enum_variants
145+ . entry ( local_def_id)
146+ . or_insert ( Usage :: Unused {
147+ redundant_use_sites : vec ! [ ] ,
148+ } ) ;
149+ } ,
150+ VariantData :: Unit ( ..) => { } ,
151+ }
152+ }
153+ }
154+
155+ fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
156+ if let Some ( def_id) = check_expr_for_enum_as_function ( expr) {
157+ if let Some ( parentheses_span) = call_parentheses_span ( cx. tcx , expr) {
158+ match self . empty_tuple_enum_variants . get_mut ( & def_id) {
159+ Some ( Usage :: Unused {
160+ ref mut redundant_use_sites,
161+ } ) => {
162+ redundant_use_sites. push ( parentheses_span) ;
163+ } ,
164+ None if has_no_fields ( cx, None , parentheses_span) => {
165+ self . empty_tuple_enum_variants . insert (
166+ def_id,
167+ Usage :: Unused {
168+ redundant_use_sites : vec ! [ parentheses_span] ,
169+ } ,
170+ ) ;
171+ } ,
172+ _ => { } ,
173+ }
174+ } else {
175+ self . empty_tuple_enum_variants . insert ( def_id, Usage :: Used ) ;
176+ }
177+ }
178+ }
179+
180+ fn check_crate_post ( & mut self , cx : & LateContext < ' _ > ) {
181+ for ( local_def_id, usage) in & self . empty_tuple_enum_variants {
182+ let Usage :: Unused { redundant_use_sites } = usage else {
183+ continue ;
184+ } ;
185+ let Node :: Variant ( variant) = cx. tcx . hir_node (
186+ cx. tcx
187+ . local_def_id_to_hir_id ( cx. tcx . parent ( local_def_id. to_def_id ( ) ) . expect_local ( ) ) ,
188+ ) else {
189+ continue ;
190+ } ;
191+ let span = variant. span . with_lo ( variant. ident . span . hi ( ) ) ;
192+ span_lint_hir_and_then (
105193 cx,
106194 EMPTY_ENUM_VARIANTS_WITH_BRACKETS ,
107- span_after_ident,
195+ variant. hir_id ,
196+ span,
108197 "enum variant has empty brackets" ,
109198 |diagnostic| {
110- diagnostic. span_suggestion_hidden (
111- span_after_ident,
112- "remove the brackets" ,
113- "" ,
114- Applicability :: MaybeIncorrect ,
115- ) ;
199+ if redundant_use_sites. is_empty ( ) {
200+ diagnostic. span_suggestion_hidden (
201+ span,
202+ "remove the brackets" ,
203+ "" ,
204+ Applicability :: MaybeIncorrect ,
205+ ) ;
206+ } else {
207+ let mut parentheses_spans: Vec < _ > =
208+ redundant_use_sites. iter ( ) . map ( |span| ( * span, String :: new ( ) ) ) . collect ( ) ;
209+ parentheses_spans. push ( ( span, String :: new ( ) ) ) ;
210+ diagnostic. multipart_suggestion (
211+ "remove the brackets" ,
212+ parentheses_spans,
213+ Applicability :: MaybeIncorrect ,
214+ ) ;
215+ }
116216 } ,
117217 ) ;
118218 }
119219 }
120220}
121221
122222fn has_no_ident_token ( braces_span_str : & str ) -> bool {
123- !rustc_lexer:: tokenize ( braces_span_str) . any ( |t| t. kind == TokenKind :: Ident )
223+ !rustc_lexer:: tokenize ( braces_span_str) . any ( |t| matches ! ( t. kind, TokenKind :: Ident | TokenKind :: Literal { .. } ) )
124224}
125225
126- fn has_brackets ( var_data : & VariantData ) -> bool {
127- !matches ! ( var_data, VariantData :: Unit ( _ ) )
226+ fn has_brackets ( var_data : & VariantData < ' _ > ) -> bool {
227+ !matches ! ( var_data, VariantData :: Unit ( .. ) )
128228}
129229
130- fn has_no_fields ( cx : & EarlyContext < ' _ > , var_data : & VariantData , braces_span : Span ) -> bool {
131- if !var_data. fields ( ) . is_empty ( ) {
230+ fn has_no_fields ( cx : & LateContext < ' _ > , var_data_opt : Option < & VariantData < ' _ > > , braces_span : Span ) -> bool {
231+ if let Some ( var_data) = var_data_opt
232+ && !var_data. fields ( ) . is_empty ( )
233+ {
132234 return false ;
133235 }
134236
@@ -142,6 +244,32 @@ fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Spa
142244 has_no_ident_token ( braces_span_str. as_ref ( ) )
143245}
144246
247+ fn call_parentheses_span ( tcx : TyCtxt < ' _ > , expr : & Expr < ' _ > ) -> Option < Span > {
248+ if let Node :: Expr ( parent) = tcx. parent_hir_node ( expr. hir_id )
249+ && let ExprKind :: Call ( callee, ..) = parent. kind
250+ && callee. hir_id == expr. hir_id
251+ {
252+ Some ( parent. span . with_lo ( expr. span . hi ( ) ) )
253+ } else {
254+ None
255+ }
256+ }
257+
258+ fn check_expr_for_enum_as_function ( expr : & Expr < ' _ > ) -> Option < LocalDefId > {
259+ if let ExprKind :: Path ( QPath :: Resolved (
260+ _,
261+ Path {
262+ res : Def ( Ctor ( CtorOf :: Variant , _) , def_id) ,
263+ ..
264+ } ,
265+ ) ) = expr. kind
266+ {
267+ def_id. as_local ( )
268+ } else {
269+ None
270+ }
271+ }
272+
145273#[ cfg( test) ]
146274mod unit_test {
147275 use super :: * ;
0 commit comments