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,10 +75,23 @@ 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 if let ItemKind :: Struct ( ident, var_data, _) = & item. kind
7896 && has_brackets ( var_data)
7997 && let span_after_ident = item. span . with_lo ( ident. span . hi ( ) )
@@ -96,70 +114,175 @@ impl EarlyLintPass for EmptyWithBrackets {
96114 }
97115 }
98116
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
100119 let span_after_ident = variant. span . with_lo ( variant. ident . span . hi ( ) ) ;
101120
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 (
104220 cx,
105221 EMPTY_ENUM_VARIANTS_WITH_BRACKETS ,
106- span_after_ident,
222+ variant. hir_id ,
223+ span,
107224 "enum variant has empty brackets" ,
108225 |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+ }
115244 } ,
116245 ) ;
117246 }
118247 }
119248}
120249
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 ( .. ) )
123252}
124253
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 ( ) &&
134256 // there might still be field declarations hidden from the AST
135257 // (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)
142259}
143260
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+ }
161273
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
164287 }
165288}
0 commit comments