Skip to content

Commit 65defdb

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

File tree

4 files changed

+174
-85
lines changed

4 files changed

+174
-85
lines changed

clippy_lints/src/unconditional_recursion.rs

+40-14
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::{expr_or_init, get_trait_def_id, path_def_id};
2+
use clippy_utils::{expr_or_init, fn_def_id_with_node_args, path_def_id};
33
use rustc_ast::BinOpKind;
44
use rustc_data_structures::fx::FxHashMap;
55
use rustc_hir as hir;
@@ -19,11 +19,11 @@ use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor;
1919

2020
declare_clippy_lint! {
2121
/// ### What it does
22-
/// Checks that there isn't an infinite recursion in `PartialEq` trait
23-
/// implementation.
22+
/// Checks that there isn't an infinite recursion in trait
23+
/// implementations.
2424
///
2525
/// ### Why is this bad?
26-
/// This is a hard to find infinite recursion which will crashing any code
26+
/// This is a hard to find infinite recursion that will crash any code.
2727
/// using it.
2828
///
2929
/// ### Example
@@ -201,7 +201,6 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
201201
}
202202
}
203203

204-
#[allow(clippy::unnecessary_def_path)]
205204
fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) {
206205
let args = cx
207206
.tcx
@@ -224,7 +223,7 @@ fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: Local
224223
&& let Some(trait_) = impl_.of_trait
225224
&& let Some(trait_def_id) = trait_.trait_def_id()
226225
// The trait is `ToString`.
227-
&& Some(trait_def_id) == get_trait_def_id(cx, &["alloc", "string", "ToString"])
226+
&& cx.tcx.is_diagnostic_item(sym::ToString, trait_def_id)
228227
{
229228
let is_bad = match expr.kind {
230229
ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => {
@@ -291,7 +290,6 @@ where
291290
self.map
292291
}
293292

294-
#[allow(clippy::unnecessary_def_path)]
295293
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
296294
if self.found_default_call {
297295
return;
@@ -303,7 +301,7 @@ where
303301
&& is_default_method_on_current_ty(self.cx.tcx, qpath, self.implemented_ty_id)
304302
&& let Some(method_def_id) = path_def_id(self.cx, f)
305303
&& 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"])
304+
&& self.cx.tcx.is_diagnostic_item(sym::Default, trait_def_id)
307305
{
308306
self.found_default_call = true;
309307
span_error(self.cx, self.method_span, expr);
@@ -312,10 +310,9 @@ where
312310
}
313311

314312
impl UnconditionalRecursion {
315-
#[allow(clippy::unnecessary_def_path)]
316313
fn init_default_impl_for_type_if_needed(&mut self, cx: &LateContext<'_>) {
317314
if self.default_impl_for_type.is_empty()
318-
&& let Some(default_trait_id) = get_trait_def_id(cx, &["core", "default", "Default"])
315+
&& let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default)
319316
{
320317
let impls = cx.tcx.trait_impls_of(default_trait_id);
321318
for (ty, impl_def_ids) in impls.non_blanket_impls() {
@@ -394,6 +391,34 @@ impl UnconditionalRecursion {
394391
}
395392
}
396393

394+
fn check_from(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, expr: &Expr<'_>) {
395+
let Some(sig) = cx
396+
.typeck_results()
397+
.liberated_fn_sigs()
398+
.get(cx.tcx.local_def_id_to_hir_id(method_def_id))
399+
else {
400+
return;
401+
};
402+
403+
// Check if we are calling `Into::into` where the node args match with our `From::from` signature:
404+
// From::from signature: fn(S1) -> S2
405+
// <S1 as Into<S2>>::into(s1), node_args=[S1, S2]
406+
// If they do match, then it must mean that it is the blanket impl,
407+
// which calls back into our `From::from` again (`Into` is not specializable).
408+
// rustc's unconditional_recursion already catches calling `From::from` directly
409+
if let Some((fn_def_id, node_args)) = fn_def_id_with_node_args(cx, expr)
410+
&& let [s1, s2] = **node_args
411+
&& let (Some(s1), Some(s2)) = (s1.as_type(), s2.as_type())
412+
&& let Some(trait_def_id) = cx.tcx.trait_of_item(fn_def_id)
413+
&& cx.tcx.is_diagnostic_item(sym::Into, trait_def_id)
414+
&& get_impl_trait_def_id(cx, method_def_id) == cx.tcx.get_diagnostic_item(sym::From)
415+
&& s1 == sig.inputs()[0]
416+
&& s2 == sig.output()
417+
{
418+
span_error(cx, method_span, expr);
419+
}
420+
}
421+
397422
impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
398423
fn check_fn(
399424
&mut self,
@@ -410,10 +435,11 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
410435
// Doesn't have a conditional return.
411436
&& !has_conditional_return(body, expr)
412437
{
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);
438+
match name.name {
439+
sym::eq | sym::ne => check_partial_eq(cx, method_span, method_def_id, name, expr),
440+
sym::to_string => check_to_string(cx, method_span, method_def_id, name, expr),
441+
sym::from => check_from(cx, method_span, method_def_id, expr),
442+
_ => {},
417443
}
418444
self.check_default_new(cx, decl, body, method_span, method_def_id);
419445
}

clippy_utils/src/lib.rs

+16-3
Original file line numberDiff line numberDiff line change
@@ -2249,8 +2249,21 @@ pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool {
22492249

22502250
/// Returns the `DefId` of the callee if the given expression is a function or method call.
22512251
pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> {
2252+
fn_def_id_with_node_args(cx, expr).map(|(did, _)| did)
2253+
}
2254+
2255+
/// Returns the `DefId` of the callee if the given expression is a function or method call,
2256+
/// as well as its node args.
2257+
pub fn fn_def_id_with_node_args<'tcx>(
2258+
cx: &LateContext<'tcx>,
2259+
expr: &Expr<'_>,
2260+
) -> Option<(DefId, rustc_ty::GenericArgsRef<'tcx>)> {
2261+
let typeck = cx.typeck_results();
22522262
match &expr.kind {
2253-
ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
2263+
ExprKind::MethodCall(..) => Some((
2264+
typeck.type_dependent_def_id(expr.hir_id)?,
2265+
typeck.node_args(expr.hir_id),
2266+
)),
22542267
ExprKind::Call(
22552268
Expr {
22562269
kind: ExprKind::Path(qpath),
@@ -2262,9 +2275,9 @@ pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> {
22622275
// Only return Fn-like DefIds, not the DefIds of statics/consts/etc that contain or
22632276
// deref to fn pointers, dyn Fn, impl Fn - #8850
22642277
if let Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) =
2265-
cx.typeck_results().qpath_res(qpath, *path_hir_id)
2278+
typeck.qpath_res(qpath, *path_hir_id)
22662279
{
2267-
Some(id)
2280+
Some((id, typeck.node_args(*path_hir_id)))
22682281
} else {
22692282
None
22702283
}

tests/ui/unconditional_recursion.rs

+49-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,48 @@ 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+
// Using UFCS syntax
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+
375+
// Different Into impl (<i16 as Into<i32>>), so no infinite recursion
376+
struct BadFromTy3;
377+
impl From<BadFromTy3> for i32 {
378+
fn from(f: BadFromTy3) -> Self {
379+
Into::into(1i16)
380+
}
381+
}
382+
383+
// A conditional return that ends the recursion
384+
struct BadFromTy4;
385+
impl From<BadFromTy4> for i32 {
386+
fn from(f: BadFromTy4) -> Self {
387+
if true {
388+
return 42;
389+
}
390+
f.into()
391+
}
392+
}
393+
394+
// Types differ in refs, don't lint
395+
impl From<&BadFromTy4> for i32 {
396+
fn from(f: &BadFromTy4) -> Self {
397+
BadFromTy4.into()
398+
}
399+
}
400+
353401
fn main() {}

0 commit comments

Comments
 (0)