11use 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 } ;
33use rustc_ast:: BinOpKind ;
4+ use rustc_data_structures:: fx:: FxHashMap ;
5+ use rustc_hir as hir;
6+ use rustc_hir:: def:: { DefKind , Res } ;
47use 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;
711use 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 } ;
1117use rustc_span:: { sym, Span } ;
1218use rustc_trait_selection:: traits:: error_reporting:: suggestions:: ReturnsVisitor ;
1319
@@ -42,7 +48,26 @@ declare_clippy_lint! {
4248 "detect unconditional recursion in some traits implementation"
4349}
4450
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+ }
4671
4772fn get_ty_def_id ( ty : Ty < ' _ > ) -> Option < DefId > {
4873 match ty. peel_refs ( ) . kind ( ) {
@@ -52,17 +77,60 @@ fn get_ty_def_id(ty: Ty<'_>) -> Option<DefId> {
5277 }
5378}
5479
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 > > {
5699 let mut visitor = ReturnsVisitor :: default ( ) ;
57100
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 ( ) {
60107 [ ] => false ,
61108 [ return_expr] => return_expr. hir_id != expr. hir_id ,
62109 _ => true ,
63110 }
64111}
65112
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+
66134#[ allow( clippy:: unnecessary_def_path) ]
67135fn check_partial_eq ( cx : & LateContext < ' _ > , method_span : Span , method_def_id : LocalDefId , name : Ident , expr : & Expr < ' _ > ) {
68136 let args = cx
@@ -75,20 +143,7 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
75143 && let Some ( other_arg) = get_ty_def_id ( * other_arg)
76144 // The two arguments are of the same type.
77145 && 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)
92147 // The trait is `PartialEq`.
93148 && Some ( trait_def_id) == get_trait_def_id ( cx, & [ "core" , "cmp" , "PartialEq" ] )
94149 {
@@ -125,15 +180,7 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
125180 _ => false ,
126181 } ;
127182 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) ;
137184 }
138185 }
139186}
@@ -177,15 +224,156 @@ fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: Local
177224 _ => false ,
178225 } ;
179226 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 {
181370 cx,
182- UNCONDITIONAL_RECURSION ,
371+ map : cx. tcx . hir ( ) ,
372+ implemented_ty_id,
373+ found_default_call : false ,
183374 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) ;
189377 }
190378 }
191379}
@@ -195,7 +383,7 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
195383 & mut self ,
196384 cx : & LateContext < ' tcx > ,
197385 kind : FnKind < ' tcx > ,
198- _decl : & ' tcx FnDecl < ' tcx > ,
386+ decl : & ' tcx FnDecl < ' tcx > ,
199387 body : & ' tcx Body < ' tcx > ,
200388 method_span : Span ,
201389 method_def_id : LocalDefId ,
@@ -211,6 +399,7 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
211399 } else if name. name == sym:: to_string {
212400 check_to_string ( cx, method_span, method_def_id, name, expr) ;
213401 }
402+ self . check_default_new ( cx, decl, body, method_span, method_def_id) ;
214403 }
215404 }
216405}
0 commit comments