11use clippy_utils:: diagnostics:: span_lint_and_then;
2- use clippy_utils:: { get_trait_def_id, path_res} ;
2+ use clippy_utils:: { expr_or_init , get_trait_def_id, path_res} ;
33use rustc_ast:: BinOpKind ;
44use rustc_hir:: def:: Res ;
55use rustc_hir:: def_id:: { DefId , LocalDefId } ;
@@ -8,6 +8,7 @@ use rustc_hir::{Body, Expr, ExprKind, FnDecl, Item, ItemKind, Node};
88use rustc_lint:: { LateContext , LateLintPass } ;
99use rustc_middle:: ty:: { self , Ty } ;
1010use rustc_session:: declare_lint_pass;
11+ use rustc_span:: symbol:: Ident ;
1112use rustc_span:: { sym, Span } ;
1213
1314declare_clippy_lint ! {
@@ -55,79 +56,142 @@ fn is_local(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
5556 matches ! ( path_res( cx, expr) , Res :: Local ( _) )
5657}
5758
59+ #[ allow( clippy:: unnecessary_def_path) ]
60+ fn check_partial_eq ( cx : & LateContext < ' _ > , body : & Body < ' _ > , method_span : Span , method_def_id : LocalDefId , name : Ident ) {
61+ let args = cx
62+ . tcx
63+ . instantiate_bound_regions_with_erased ( cx. tcx . fn_sig ( method_def_id) . skip_binder ( ) )
64+ . inputs ( ) ;
65+ // That has two arguments.
66+ if let [ self_arg, other_arg] = args
67+ && let Some ( self_arg) = get_ty_def_id ( * self_arg)
68+ && let Some ( other_arg) = get_ty_def_id ( * other_arg)
69+ // The two arguments are of the same type.
70+ && self_arg == other_arg
71+ && let hir_id = cx. tcx . local_def_id_to_hir_id ( method_def_id)
72+ && let Some ( (
73+ _,
74+ Node :: Item ( Item {
75+ kind : ItemKind :: Impl ( impl_) ,
76+ owner_id,
77+ ..
78+ } ) ,
79+ ) ) = cx. tcx . hir ( ) . parent_iter ( hir_id) . next ( )
80+ // We exclude `impl` blocks generated from rustc's proc macros.
81+ && !cx. tcx . has_attr ( * owner_id, sym:: automatically_derived)
82+ // It is a implementation of a trait.
83+ && let Some ( trait_) = impl_. of_trait
84+ && let Some ( trait_def_id) = trait_. trait_def_id ( )
85+ // The trait is `PartialEq`.
86+ && Some ( trait_def_id) == get_trait_def_id ( cx, & [ "core" , "cmp" , "PartialEq" ] )
87+ {
88+ let to_check_op = if name. name == sym:: eq {
89+ BinOpKind :: Eq
90+ } else {
91+ BinOpKind :: Ne
92+ } ;
93+ let expr = body. value . peel_blocks ( ) ;
94+ let is_bad = match expr. kind {
95+ ExprKind :: Binary ( op, left, right) if op. node == to_check_op => is_local ( cx, left) && is_local ( cx, right) ,
96+ ExprKind :: MethodCall ( segment, receiver, & [ arg] , _) if segment. ident . name == name. name => {
97+ if is_local ( cx, receiver)
98+ && is_local ( cx, & arg)
99+ && let Some ( fn_id) = cx. typeck_results ( ) . type_dependent_def_id ( expr. hir_id )
100+ && let Some ( trait_id) = cx. tcx . trait_of_item ( fn_id)
101+ && trait_id == trait_def_id
102+ {
103+ true
104+ } else {
105+ false
106+ }
107+ } ,
108+ _ => false ,
109+ } ;
110+ if is_bad {
111+ span_lint_and_then (
112+ cx,
113+ UNCONDITIONAL_RECURSION ,
114+ method_span,
115+ "function cannot return without recursing" ,
116+ |diag| {
117+ diag. span_note ( expr. span , "recursive call site" ) ;
118+ } ,
119+ ) ;
120+ }
121+ }
122+ }
123+
124+ #[ allow( clippy:: unnecessary_def_path) ]
125+ fn check_to_string ( cx : & LateContext < ' _ > , body : & Body < ' _ > , method_span : Span , method_def_id : LocalDefId , name : Ident ) {
126+ let args = cx
127+ . tcx
128+ . instantiate_bound_regions_with_erased ( cx. tcx . fn_sig ( method_def_id) . skip_binder ( ) )
129+ . inputs ( ) ;
130+ // That has one argument.
131+ if let [ _self_arg] = args
132+ && let hir_id = cx. tcx . local_def_id_to_hir_id ( method_def_id)
133+ && let Some ( (
134+ _,
135+ Node :: Item ( Item {
136+ kind : ItemKind :: Impl ( impl_) ,
137+ owner_id,
138+ ..
139+ } ) ,
140+ ) ) = cx. tcx . hir ( ) . parent_iter ( hir_id) . next ( )
141+ // We exclude `impl` blocks generated from rustc's proc macros.
142+ && !cx. tcx . has_attr ( * owner_id, sym:: automatically_derived)
143+ // It is a implementation of a trait.
144+ && let Some ( trait_) = impl_. of_trait
145+ && let Some ( trait_def_id) = trait_. trait_def_id ( )
146+ // The trait is `ToString`.
147+ && Some ( trait_def_id) == get_trait_def_id ( cx, & [ "alloc" , "string" , "ToString" ] )
148+ {
149+ let expr = expr_or_init ( cx, body. value . peel_blocks ( ) ) ;
150+ let is_bad = match expr. kind {
151+ ExprKind :: MethodCall ( segment, receiver, & [ arg] , _) if segment. ident . name == name. name => {
152+ if is_local ( cx, receiver)
153+ && is_local ( cx, & arg)
154+ && let Some ( fn_id) = cx. typeck_results ( ) . type_dependent_def_id ( expr. hir_id )
155+ && let Some ( trait_id) = cx. tcx . trait_of_item ( fn_id)
156+ && trait_id == trait_def_id
157+ {
158+ true
159+ } else {
160+ false
161+ }
162+ } ,
163+ _ => false ,
164+ } ;
165+ if is_bad {
166+ span_lint_and_then (
167+ cx,
168+ UNCONDITIONAL_RECURSION ,
169+ method_span,
170+ "function cannot return without recursing" ,
171+ |diag| {
172+ diag. span_note ( expr. span , "recursive call site" ) ;
173+ } ,
174+ ) ;
175+ }
176+ }
177+ }
178+
58179impl < ' tcx > LateLintPass < ' tcx > for UnconditionalRecursion {
59- #[ allow( clippy:: unnecessary_def_path) ]
60180 fn check_fn (
61181 & mut self ,
62182 cx : & LateContext < ' tcx > ,
63183 kind : FnKind < ' tcx > ,
64184 _decl : & ' tcx FnDecl < ' tcx > ,
65185 body : & ' tcx Body < ' tcx > ,
66186 method_span : Span ,
67- def_id : LocalDefId ,
187+ method_def_id : LocalDefId ,
68188 ) {
69189 // If the function is a method...
70- if let FnKind :: Method ( name, _) = kind
71- // That has two arguments.
72- && let [ self_arg, other_arg] = cx
73- . tcx
74- . instantiate_bound_regions_with_erased ( cx. tcx . fn_sig ( def_id) . skip_binder ( ) )
75- . inputs ( )
76- && let Some ( self_arg) = get_ty_def_id ( * self_arg)
77- && let Some ( other_arg) = get_ty_def_id ( * other_arg)
78- // The two arguments are of the same type.
79- && self_arg == other_arg
80- && let hir_id = cx. tcx . local_def_id_to_hir_id ( def_id)
81- && let Some ( (
82- _,
83- Node :: Item ( Item {
84- kind : ItemKind :: Impl ( impl_) ,
85- owner_id,
86- ..
87- } ) ,
88- ) ) = cx. tcx . hir ( ) . parent_iter ( hir_id) . next ( )
89- // We exclude `impl` blocks generated from rustc's proc macros.
90- && !cx. tcx . has_attr ( * owner_id, sym:: automatically_derived)
91- // It is a implementation of a trait.
92- && let Some ( trait_) = impl_. of_trait
93- && let Some ( trait_def_id) = trait_. trait_def_id ( )
94- // The trait is `PartialEq`.
95- && Some ( trait_def_id) == get_trait_def_id ( cx, & [ "core" , "cmp" , "PartialEq" ] )
96- {
97- let to_check_op = if name. name == sym:: eq {
98- BinOpKind :: Eq
99- } else {
100- BinOpKind :: Ne
101- } ;
102- let expr = body. value . peel_blocks ( ) ;
103- let is_bad = match expr. kind {
104- ExprKind :: Binary ( op, left, right) if op. node == to_check_op => {
105- is_local ( cx, left) && is_local ( cx, right)
106- } ,
107- ExprKind :: MethodCall ( segment, receiver, & [ arg] , _) if segment. ident . name == name. name => {
108- if is_local ( cx, receiver)
109- && is_local ( cx, & arg)
110- && let Some ( fn_id) = cx. typeck_results ( ) . type_dependent_def_id ( expr. hir_id )
111- && let Some ( trait_id) = cx. tcx . trait_of_item ( fn_id)
112- && trait_id == trait_def_id
113- {
114- true
115- } else {
116- false
117- }
118- } ,
119- _ => false ,
120- } ;
121- if is_bad {
122- span_lint_and_then (
123- cx,
124- UNCONDITIONAL_RECURSION ,
125- method_span,
126- "function cannot return without recursing" ,
127- |diag| {
128- diag. span_note ( expr. span , "recursive call site" ) ;
129- } ,
130- ) ;
190+ if let FnKind :: Method ( name, _) = kind {
191+ if name. name == sym:: eq || name. name == sym:: ne {
192+ check_partial_eq ( cx, body, method_span, method_def_id, name) ;
193+ } else if name. name == sym:: to_string {
194+ check_to_string ( cx, body, method_span, method_def_id, name) ;
131195 }
132196 }
133197 }
0 commit comments