Skip to content

Commit e97881c

Browse files
committed
[unconditional_recursion]: catch From -> Into -> From
1 parent 7ee75f8 commit e97881c

File tree

3 files changed

+137
-80
lines changed

3 files changed

+137
-80
lines changed

clippy_lints/src/unconditional_recursion.rs

+45-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2-
use clippy_utils::{expr_or_init, get_trait_def_id, path_def_id};
2+
use clippy_utils::{expr_or_init, fn_def_id, get_trait_def_id, path_def_id};
3+
use itertools::Itertools;
34
use rustc_ast::BinOpKind;
45
use rustc_data_structures::fx::FxHashMap;
56
use rustc_hir as hir;
@@ -19,11 +20,11 @@ use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor;
1920

2021
declare_clippy_lint! {
2122
/// ### What it does
22-
/// Checks that there isn't an infinite recursion in `PartialEq` trait
23-
/// implementation.
23+
/// Checks that there isn't an infinite recursion in trait
24+
/// implementations.
2425
///
2526
/// ### Why is this bad?
26-
/// This is a hard to find infinite recursion which will crashing any code
27+
/// This is a hard to find infinite recursion which will crash any code
2728
/// using it.
2829
///
2930
/// ### Example
@@ -201,7 +202,6 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
201202
}
202203
}
203204

204-
#[allow(clippy::unnecessary_def_path)]
205205
fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) {
206206
let args = cx
207207
.tcx
@@ -224,7 +224,7 @@ fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: Local
224224
&& let Some(trait_) = impl_.of_trait
225225
&& let Some(trait_def_id) = trait_.trait_def_id()
226226
// The trait is `ToString`.
227-
&& Some(trait_def_id) == get_trait_def_id(cx, &["alloc", "string", "ToString"])
227+
&& cx.tcx.is_diagnostic_item(sym::ToString, trait_def_id)
228228
{
229229
let is_bad = match expr.kind {
230230
ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => {
@@ -291,7 +291,6 @@ where
291291
self.map
292292
}
293293

294-
#[allow(clippy::unnecessary_def_path)]
295294
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
296295
if self.found_default_call {
297296
return;
@@ -303,7 +302,7 @@ where
303302
&& is_default_method_on_current_ty(self.cx.tcx, qpath, self.implemented_ty_id)
304303
&& let Some(method_def_id) = path_def_id(self.cx, f)
305304
&& let Some(trait_def_id) = self.cx.tcx.trait_of_item(method_def_id)
306-
&& Some(trait_def_id) == get_trait_def_id(self.cx, &["core", "default", "Default"])
305+
&& self.cx.tcx.is_diagnostic_item(sym::Default, trait_def_id)
307306
{
308307
self.found_default_call = true;
309308
span_error(self.cx, self.method_span, expr);
@@ -394,6 +393,39 @@ impl UnconditionalRecursion {
394393
}
395394
}
396395

396+
fn check_from(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, expr: &Expr<'_>) {
397+
let Some(sig) = cx
398+
.typeck_results()
399+
.liberated_fn_sigs()
400+
.get(cx.tcx.local_def_id_to_hir_id(method_def_id))
401+
else {
402+
return;
403+
};
404+
let typeck = cx.typeck_results();
405+
406+
// Check if we are calling `Into::into` where the node args match with our `From::from` signature:
407+
// From::from signature: fn(S1) -> S2
408+
// <S1 as Into<S2>>::into(s1), node_args=[S1, S2]
409+
// If they do match, then it must mean that it is the blanket impl,
410+
// which calls back into our `From::from` again (`Into` is not specializable).
411+
// rustc's unconditional_recursion already catches calling `From::from` directly
412+
if let Some(fn_def_id) = fn_def_id(cx, expr)
413+
&& let node_args = match expr.kind {
414+
ExprKind::MethodCall(..) => typeck.node_args(expr.hir_id),
415+
ExprKind::Call(callee, _) => typeck.node_args(callee.hir_id),
416+
_ => return,
417+
}
418+
&& let Some((s1, s2)) = node_args.iter().filter_map(ty::GenericArg::as_type).collect_tuple()
419+
&& let Some(trait_def_id) = cx.tcx.trait_of_item(fn_def_id)
420+
&& cx.tcx.is_diagnostic_item(sym::Into, trait_def_id)
421+
&& get_impl_trait_def_id(cx, method_def_id) == cx.tcx.get_diagnostic_item(sym::From)
422+
&& s1 == sig.inputs()[0]
423+
&& s2 == sig.output()
424+
{
425+
span_error(cx, method_span, expr);
426+
}
427+
}
428+
397429
impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
398430
fn check_fn(
399431
&mut self,
@@ -410,10 +442,11 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
410442
// Doesn't have a conditional return.
411443
&& !has_conditional_return(body, expr)
412444
{
413-
if name.name == sym::eq || name.name == sym::ne {
414-
check_partial_eq(cx, method_span, method_def_id, name, expr);
415-
} else if name.name == sym::to_string {
416-
check_to_string(cx, method_span, method_def_id, name, expr);
445+
match name.name {
446+
sym::eq | sym::ne => check_partial_eq(cx, method_span, method_def_id, name, expr),
447+
sym::to_string => check_to_string(cx, method_span, method_def_id, name, expr),
448+
sym::from => check_from(cx, method_span, method_def_id, expr),
449+
_ => {},
417450
}
418451
self.check_default_new(cx, decl, body, method_span, method_def_id);
419452
}

tests/ui/unconditional_recursion.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
//@no-rustfix
22

33
#![warn(clippy::unconditional_recursion)]
4-
#![allow(clippy::partialeq_ne_impl, clippy::default_constructed_unit_structs)]
4+
#![allow(
5+
clippy::partialeq_ne_impl,
6+
clippy::default_constructed_unit_structs,
7+
clippy::only_used_in_recursion
8+
)]
59

610
enum Foo {
711
A,
@@ -350,4 +354,22 @@ mod issue12154 {
350354
}
351355
}
352356

357+
// From::from -> Into::into -> From::from
358+
struct BadFromTy1<'a>(&'a ());
359+
struct BadIntoTy1<'b>(&'b ());
360+
impl<'a> From<BadFromTy1<'a>> for BadIntoTy1<'static> {
361+
fn from(f: BadFromTy1<'a>) -> Self {
362+
f.into()
363+
}
364+
}
365+
366+
// From::from -> From::from
367+
struct BadFromTy2<'a>(&'a ());
368+
struct BadIntoTy2<'b>(&'b ());
369+
impl<'a> From<BadFromTy2<'a>> for BadIntoTy2<'static> {
370+
fn from(f: BadFromTy2<'a>) -> Self {
371+
Into::into(f)
372+
}
373+
}
374+
353375
fn main() {}

0 commit comments

Comments
 (0)