Skip to content

Commit 44c99a8

Browse files
committed
Auto merge of #11980 - GuillaumeGomez:UNCONDITIONAL_RECURSION-tostring, r=llogiq
Extend UNCONDITIONAL_RECURSION to check for ToString implementations Follow-up of #11938. r? `@llogiq` changelog: Extend `UNCONDITIONAL_RECURSION` to check for `ToString` implementations
2 parents 7f185bd + d161f3b commit 44c99a8

File tree

3 files changed

+190
-65
lines changed

3 files changed

+190
-65
lines changed

clippy_lints/src/unconditional_recursion.rs

Lines changed: 128 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use 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};
33
use rustc_ast::BinOpKind;
44
use rustc_hir::def::Res;
55
use rustc_hir::def_id::{DefId, LocalDefId};
@@ -8,6 +8,7 @@ use rustc_hir::{Body, Expr, ExprKind, FnDecl, Item, ItemKind, Node};
88
use rustc_lint::{LateContext, LateLintPass};
99
use rustc_middle::ty::{self, Ty};
1010
use rustc_session::declare_lint_pass;
11+
use rustc_span::symbol::Ident;
1112
use rustc_span::{sym, Span};
1213

1314
declare_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+
58179
impl<'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
}

tests/ui/unconditional_recursion.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,34 @@ struct S5;
158158
impl_partial_eq!(S5);
159159
//~^ ERROR: function cannot return without recursing
160160

161+
struct S6;
162+
163+
impl std::string::ToString for S6 {
164+
fn to_string(&self) -> String {
165+
//~^ ERROR: function cannot return without recursing
166+
self.to_string()
167+
}
168+
}
169+
170+
struct S7;
171+
172+
impl std::string::ToString for S7 {
173+
fn to_string(&self) -> String {
174+
//~^ ERROR: function cannot return without recursing
175+
let x = self;
176+
x.to_string()
177+
}
178+
}
179+
180+
struct S8;
181+
182+
impl std::string::ToString for S8 {
183+
fn to_string(&self) -> String {
184+
//~^ ERROR: function cannot return without recursing
185+
(self as &Self).to_string()
186+
}
187+
}
188+
161189
fn main() {
162190
// test code goes here
163191
}

tests/ui/unconditional_recursion.stderr

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,39 @@ LL | self.eq(other)
2222
|
2323
= help: a `loop` may express intention better if this is on purpose
2424

25+
error: function cannot return without recursing
26+
--> $DIR/unconditional_recursion.rs:164:5
27+
|
28+
LL | fn to_string(&self) -> String {
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing
30+
LL |
31+
LL | self.to_string()
32+
| ---------------- recursive call site
33+
|
34+
= help: a `loop` may express intention better if this is on purpose
35+
36+
error: function cannot return without recursing
37+
--> $DIR/unconditional_recursion.rs:173:5
38+
|
39+
LL | fn to_string(&self) -> String {
40+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing
41+
...
42+
LL | x.to_string()
43+
| ------------- recursive call site
44+
|
45+
= help: a `loop` may express intention better if this is on purpose
46+
47+
error: function cannot return without recursing
48+
--> $DIR/unconditional_recursion.rs:183:5
49+
|
50+
LL | fn to_string(&self) -> String {
51+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing
52+
LL |
53+
LL | (self as &Self).to_string()
54+
| --------------------------- recursive call site
55+
|
56+
= help: a `loop` may express intention better if this is on purpose
57+
2558
error: function cannot return without recursing
2659
--> $DIR/unconditional_recursion.rs:12:5
2760
|
@@ -247,5 +280,5 @@ LL | impl_partial_eq!(S5);
247280
| -------------------- in this macro invocation
248281
= note: this error originates in the macro `impl_partial_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
249282

250-
error: aborting due to 19 previous errors
283+
error: aborting due to 22 previous errors
251284

0 commit comments

Comments
 (0)