1
1
use clippy_utils:: diagnostics:: span_lint_and_then;
2
- use clippy_utils:: { expr_or_init, get_trait_def_id} ;
2
+ use clippy_utils:: { expr_or_init, get_trait_def_id, path_def_id } ;
3
3
use rustc_ast:: BinOpKind ;
4
+ use rustc_data_structures:: fx:: FxHashMap ;
5
+ use rustc_hir as hir;
6
+ use rustc_hir:: def:: { DefKind , Res } ;
4
7
use rustc_hir:: def_id:: { DefId , LocalDefId } ;
5
- use rustc_hir:: intravisit:: { walk_body, FnKind } ;
6
- use rustc_hir:: { Body , Expr , ExprKind , FnDecl , Item , ItemKind , Node } ;
8
+ use rustc_hir:: intravisit:: { walk_body, walk_expr, FnKind , Visitor } ;
9
+ use rustc_hir:: { Body , Expr , ExprKind , FnDecl , HirId , Item , ItemKind , Node , QPath , TyKind } ;
10
+ use rustc_hir_analysis:: hir_ty_to_ty;
7
11
use rustc_lint:: { LateContext , LateLintPass } ;
8
- use rustc_middle:: ty:: { self , Ty } ;
9
- use rustc_session:: declare_lint_pass;
10
- use rustc_span:: symbol:: Ident ;
12
+ use rustc_middle:: hir:: map:: Map ;
13
+ use rustc_middle:: hir:: nested_filter;
14
+ use rustc_middle:: ty:: { self , AssocKind , Ty , TyCtxt } ;
15
+ use rustc_session:: impl_lint_pass;
16
+ use rustc_span:: symbol:: { kw, Ident } ;
11
17
use rustc_span:: { sym, Span } ;
12
18
use rustc_trait_selection:: traits:: error_reporting:: suggestions:: ReturnsVisitor ;
13
19
@@ -42,7 +48,26 @@ declare_clippy_lint! {
42
48
"detect unconditional recursion in some traits implementation"
43
49
}
44
50
45
- declare_lint_pass ! ( UnconditionalRecursion => [ UNCONDITIONAL_RECURSION ] ) ;
51
+ #[ derive( Default ) ]
52
+ pub struct UnconditionalRecursion {
53
+ /// The key is the `DefId` of the type implementing the `Default` trait and the value is the
54
+ /// `DefId` of the return call.
55
+ default_impl_for_type : FxHashMap < DefId , DefId > ,
56
+ }
57
+
58
+ impl_lint_pass ! ( UnconditionalRecursion => [ UNCONDITIONAL_RECURSION ] ) ;
59
+
60
+ fn span_error ( cx : & LateContext < ' _ > , method_span : Span , expr : & Expr < ' _ > ) {
61
+ span_lint_and_then (
62
+ cx,
63
+ UNCONDITIONAL_RECURSION ,
64
+ method_span,
65
+ "function cannot return without recursing" ,
66
+ |diag| {
67
+ diag. span_note ( expr. span , "recursive call site" ) ;
68
+ } ,
69
+ ) ;
70
+ }
46
71
47
72
fn get_ty_def_id ( ty : Ty < ' _ > ) -> Option < DefId > {
48
73
match ty. peel_refs ( ) . kind ( ) {
@@ -52,17 +77,60 @@ fn get_ty_def_id(ty: Ty<'_>) -> Option<DefId> {
52
77
}
53
78
}
54
79
55
- fn has_conditional_return ( body : & Body < ' _ > , expr : & Expr < ' _ > ) -> bool {
80
+ fn get_hir_ty_def_id ( tcx : TyCtxt < ' _ > , hir_ty : rustc_hir:: Ty < ' _ > ) -> Option < DefId > {
81
+ let TyKind :: Path ( qpath) = hir_ty. kind else { return None } ;
82
+ match qpath {
83
+ QPath :: Resolved ( _, path) => path. res . opt_def_id ( ) ,
84
+ QPath :: TypeRelative ( _, _) => {
85
+ let ty = hir_ty_to_ty ( tcx, & hir_ty) ;
86
+
87
+ match ty. kind ( ) {
88
+ ty:: Alias ( ty:: Projection , proj) => {
89
+ Res :: < HirId > :: Def ( DefKind :: Trait , proj. trait_ref ( tcx) . def_id ) . opt_def_id ( )
90
+ } ,
91
+ _ => None ,
92
+ }
93
+ } ,
94
+ QPath :: LangItem ( ..) => None ,
95
+ }
96
+ }
97
+
98
+ fn get_return_calls_in_body < ' tcx > ( body : & ' tcx Body < ' tcx > ) -> Vec < & ' tcx Expr < ' tcx > > {
56
99
let mut visitor = ReturnsVisitor :: default ( ) ;
57
100
58
- walk_body ( & mut visitor, body) ;
59
- match visitor. returns . as_slice ( ) {
101
+ visitor. visit_body ( body) ;
102
+ visitor. returns
103
+ }
104
+
105
+ fn has_conditional_return ( body : & Body < ' _ > , expr : & Expr < ' _ > ) -> bool {
106
+ match get_return_calls_in_body ( body) . as_slice ( ) {
60
107
[ ] => false ,
61
108
[ return_expr] => return_expr. hir_id != expr. hir_id ,
62
109
_ => true ,
63
110
}
64
111
}
65
112
113
+ fn get_impl_trait_def_id ( cx : & LateContext < ' _ > , method_def_id : LocalDefId ) -> Option < DefId > {
114
+ let hir_id = cx. tcx . local_def_id_to_hir_id ( method_def_id) ;
115
+ if let Some ( (
116
+ _,
117
+ Node :: Item ( Item {
118
+ kind : ItemKind :: Impl ( impl_) ,
119
+ owner_id,
120
+ ..
121
+ } ) ,
122
+ ) ) = cx. tcx . hir ( ) . parent_iter ( hir_id) . next ( )
123
+ // We exclude `impl` blocks generated from rustc's proc macros.
124
+ && !cx. tcx . has_attr ( * owner_id, sym:: automatically_derived)
125
+ // It is a implementation of a trait.
126
+ && let Some ( trait_) = impl_. of_trait
127
+ {
128
+ trait_. trait_def_id ( )
129
+ } else {
130
+ None
131
+ }
132
+ }
133
+
66
134
#[ allow( clippy:: unnecessary_def_path) ]
67
135
fn check_partial_eq ( cx : & LateContext < ' _ > , method_span : Span , method_def_id : LocalDefId , name : Ident , expr : & Expr < ' _ > ) {
68
136
let args = cx
@@ -75,20 +143,7 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
75
143
&& let Some ( other_arg) = get_ty_def_id ( * other_arg)
76
144
// The two arguments are of the same type.
77
145
&& self_arg == other_arg
78
- && let hir_id = cx. tcx . local_def_id_to_hir_id ( method_def_id)
79
- && let Some ( (
80
- _,
81
- Node :: Item ( Item {
82
- kind : ItemKind :: Impl ( impl_) ,
83
- owner_id,
84
- ..
85
- } ) ,
86
- ) ) = cx. tcx . hir ( ) . parent_iter ( hir_id) . next ( )
87
- // We exclude `impl` blocks generated from rustc's proc macros.
88
- && !cx. tcx . has_attr ( * owner_id, sym:: automatically_derived)
89
- // It is a implementation of a trait.
90
- && let Some ( trait_) = impl_. of_trait
91
- && let Some ( trait_def_id) = trait_. trait_def_id ( )
146
+ && let Some ( trait_def_id) = get_impl_trait_def_id ( cx, method_def_id)
92
147
// The trait is `PartialEq`.
93
148
&& Some ( trait_def_id) == get_trait_def_id ( cx, & [ "core" , "cmp" , "PartialEq" ] )
94
149
{
@@ -125,15 +180,7 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
125
180
_ => false ,
126
181
} ;
127
182
if is_bad {
128
- span_lint_and_then (
129
- cx,
130
- UNCONDITIONAL_RECURSION ,
131
- method_span,
132
- "function cannot return without recursing" ,
133
- |diag| {
134
- diag. span_note ( expr. span , "recursive call site" ) ;
135
- } ,
136
- ) ;
183
+ span_error ( cx, method_span, expr) ;
137
184
}
138
185
}
139
186
}
@@ -177,15 +224,156 @@ fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: Local
177
224
_ => false ,
178
225
} ;
179
226
if is_bad {
180
- span_lint_and_then (
227
+ span_error ( cx, method_span, expr) ;
228
+ }
229
+ }
230
+ }
231
+
232
+ fn is_default_method_on_current_ty ( tcx : TyCtxt < ' _ > , qpath : QPath < ' _ > , implemented_ty_id : DefId ) -> bool {
233
+ match qpath {
234
+ QPath :: Resolved ( _, path) => match path. segments {
235
+ [ first, .., last] => last. ident . name == kw:: Default && first. res . opt_def_id ( ) == Some ( implemented_ty_id) ,
236
+ _ => false ,
237
+ } ,
238
+ QPath :: TypeRelative ( ty, segment) => {
239
+ if segment. ident . name != kw:: Default {
240
+ return false ;
241
+ }
242
+ if matches ! (
243
+ ty. kind,
244
+ TyKind :: Path ( QPath :: Resolved (
245
+ _,
246
+ hir:: Path {
247
+ res: Res :: SelfTyAlias { .. } ,
248
+ ..
249
+ } ,
250
+ ) )
251
+ ) {
252
+ return true ;
253
+ }
254
+ get_hir_ty_def_id ( tcx, * ty) == Some ( implemented_ty_id)
255
+ } ,
256
+ QPath :: LangItem ( ..) => false ,
257
+ }
258
+ }
259
+
260
+ struct CheckCalls < ' a , ' tcx > {
261
+ cx : & ' a LateContext < ' tcx > ,
262
+ map : Map < ' tcx > ,
263
+ implemented_ty_id : DefId ,
264
+ found_default_call : bool ,
265
+ method_span : Span ,
266
+ }
267
+
268
+ impl < ' a , ' tcx > Visitor < ' tcx > for CheckCalls < ' a , ' tcx >
269
+ where
270
+ ' tcx : ' a ,
271
+ {
272
+ type NestedFilter = nested_filter:: OnlyBodies ;
273
+
274
+ fn nested_visit_map ( & mut self ) -> Self :: Map {
275
+ self . map
276
+ }
277
+
278
+ #[ allow( clippy:: unnecessary_def_path) ]
279
+ fn visit_expr ( & mut self , expr : & ' tcx Expr < ' tcx > ) {
280
+ if self . found_default_call {
281
+ return ;
282
+ }
283
+ walk_expr ( self , expr) ;
284
+
285
+ if let ExprKind :: Call ( f, _) = expr. kind
286
+ && let ExprKind :: Path ( qpath) = f. kind
287
+ && is_default_method_on_current_ty ( self . cx . tcx , qpath, self . implemented_ty_id )
288
+ && let Some ( method_def_id) = path_def_id ( self . cx , f)
289
+ && let Some ( trait_def_id) = self . cx . tcx . trait_of_item ( method_def_id)
290
+ && Some ( trait_def_id) == get_trait_def_id ( self . cx , & [ "core" , "default" , "Default" ] )
291
+ {
292
+ self . found_default_call = true ;
293
+ span_error ( self . cx , self . method_span , expr) ;
294
+ }
295
+ }
296
+ }
297
+
298
+ impl UnconditionalRecursion {
299
+ #[ allow( clippy:: unnecessary_def_path) ]
300
+ fn init_default_impl_for_type_if_needed ( & mut self , cx : & LateContext < ' _ > ) {
301
+ if self . default_impl_for_type . is_empty ( )
302
+ && let Some ( default_trait_id) = get_trait_def_id ( cx, & [ "core" , "default" , "Default" ] )
303
+ {
304
+ let impls = cx. tcx . trait_impls_of ( default_trait_id) ;
305
+ for ( ty, impl_def_ids) in impls. non_blanket_impls ( ) {
306
+ let Some ( self_def_id) = ty. def ( ) else { continue } ;
307
+ for impl_def_id in impl_def_ids {
308
+ if !cx. tcx . has_attr ( * impl_def_id, sym:: automatically_derived) &&
309
+ let Some ( assoc_item) = cx
310
+ . tcx
311
+ . associated_items ( impl_def_id)
312
+ . in_definition_order ( )
313
+ // We're not interested in foreign implementations of the `Default` trait.
314
+ . find ( |item| {
315
+ item. kind == AssocKind :: Fn && item. def_id . is_local ( ) && item. name == kw:: Default
316
+ } )
317
+ && let Some ( body_node) = cx. tcx . hir ( ) . get_if_local ( assoc_item. def_id )
318
+ && let Some ( body_id) = body_node. body_id ( )
319
+ && let body = cx. tcx . hir ( ) . body ( body_id)
320
+ // We don't want to keep it if it has conditional return.
321
+ && let [ return_expr] = get_return_calls_in_body ( body) . as_slice ( )
322
+ && let ExprKind :: Call ( call_expr, _) = return_expr. kind
323
+ // We need to use typeck here to infer the actual function being called.
324
+ && let body_def_id = cx. tcx . hir ( ) . enclosing_body_owner ( call_expr. hir_id )
325
+ && let Some ( body_owner) = cx. tcx . hir ( ) . maybe_body_owned_by ( body_def_id)
326
+ && let typeck = cx. tcx . typeck_body ( body_owner)
327
+ && let Some ( call_def_id) = typeck. type_dependent_def_id ( call_expr. hir_id )
328
+ {
329
+ self . default_impl_for_type . insert ( self_def_id, call_def_id) ;
330
+ }
331
+ }
332
+ }
333
+ }
334
+ }
335
+
336
+ fn check_default_new < ' tcx > (
337
+ & mut self ,
338
+ cx : & LateContext < ' tcx > ,
339
+ decl : & FnDecl < ' tcx > ,
340
+ body : & ' tcx Body < ' tcx > ,
341
+ method_span : Span ,
342
+ method_def_id : LocalDefId ,
343
+ ) {
344
+ // We're only interested into static methods.
345
+ if decl. implicit_self . has_implicit_self ( ) {
346
+ return ;
347
+ }
348
+ // We don't check trait implementations.
349
+ if get_impl_trait_def_id ( cx, method_def_id) . is_some ( ) {
350
+ return ;
351
+ }
352
+
353
+ let hir_id = cx. tcx . local_def_id_to_hir_id ( method_def_id) ;
354
+ if let Some ( (
355
+ _,
356
+ Node :: Item ( Item {
357
+ kind : ItemKind :: Impl ( impl_) ,
358
+ ..
359
+ } ) ,
360
+ ) ) = cx. tcx . hir ( ) . parent_iter ( hir_id) . next ( )
361
+ && let Some ( implemented_ty_id) = get_hir_ty_def_id ( cx. tcx , * impl_. self_ty )
362
+ && {
363
+ self . init_default_impl_for_type_if_needed ( cx) ;
364
+ true
365
+ }
366
+ && let Some ( return_def_id) = self . default_impl_for_type . get ( & implemented_ty_id)
367
+ && method_def_id. to_def_id ( ) == * return_def_id
368
+ {
369
+ let mut c = CheckCalls {
181
370
cx,
182
- UNCONDITIONAL_RECURSION ,
371
+ map : cx. tcx . hir ( ) ,
372
+ implemented_ty_id,
373
+ found_default_call : false ,
183
374
method_span,
184
- "function cannot return without recursing" ,
185
- |diag| {
186
- diag. span_note ( expr. span , "recursive call site" ) ;
187
- } ,
188
- ) ;
375
+ } ;
376
+ walk_body ( & mut c, body) ;
189
377
}
190
378
}
191
379
}
@@ -195,7 +383,7 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
195
383
& mut self ,
196
384
cx : & LateContext < ' tcx > ,
197
385
kind : FnKind < ' tcx > ,
198
- _decl : & ' tcx FnDecl < ' tcx > ,
386
+ decl : & ' tcx FnDecl < ' tcx > ,
199
387
body : & ' tcx Body < ' tcx > ,
200
388
method_span : Span ,
201
389
method_def_id : LocalDefId ,
@@ -211,6 +399,7 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
211
399
} else if name. name == sym:: to_string {
212
400
check_to_string ( cx, method_span, method_def_id, name, expr) ;
213
401
}
402
+ self . check_default_new ( cx, decl, body, method_span, method_def_id) ;
214
403
}
215
404
}
216
405
}
0 commit comments