Skip to content

Commit 73be486

Browse files
committed
Auto merge of #12459 - y21:unconditional_recursion_from_into, r=Jarcho
lint when calling the blanket `Into` impl from a `From` impl Closes #11150 ``` warning: function cannot return without recursing --> x.rs:9:9 | 9 | / fn from(value: f32) -> Self { 10 | | value.into() 11 | | } | |_________^ | note: recursive call site --> x.rs:10:13 | 10 | value.into() | ^^^^^^^^^^^^ ``` I'm also thinking that we can probably generalize this lint to #11032 at some point (instead of hardcoding a bunch of impls), like how rustc's `unconditional_recursion` works, at least up to one indirect call, but this still seems useful for now :) I've also noticed that we use `fn_def_id` in a bunch of lints and then try to get the node args of the call in a separate step, so I made a helper function that does both in one. I intend to refactor a bunch of uses of `fn_def_id` to use this later I can add more test cases, but this is already using much of the same logic that exists for the other impls that this lint looks for (e.g. making sure that there are no conditional returns). changelog: [`unconditional_recursion`]: emit a warning inside of `From::from` when unconditionally calling the blanket `.into()` impl
2 parents 99e8000 + 65defdb commit 73be486

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)