Skip to content

Commit

Permalink
Also check code generated by macros
Browse files Browse the repository at this point in the history
  • Loading branch information
GuillaumeGomez committed Dec 16, 2023
1 parent 91fd01c commit 6b444f3
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 110 deletions.
24 changes: 17 additions & 7 deletions clippy_lints/src/unconditional_recursion.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{get_parent_as_impl, get_trait_def_id, path_res};
use clippy_utils::{get_trait_def_id, path_res};
use rustc_ast::BinOpKind;
use rustc_hir::def::Res;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, Expr, ExprKind, FnDecl};
use rustc_hir::{Body, Expr, ExprKind, FnDecl, Item, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty};
use rustc_session::declare_lint_pass;
Expand Down Expand Up @@ -66,22 +66,32 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
method_span: Span,
def_id: LocalDefId,
) {
// We don't check code generated from (proc) macro.
if method_span.from_expansion() {
return;
}
// If the function is a method...
if let FnKind::Method(name, _) = kind
// That has two arguments.
&& let [self_arg, other_arg] = cx
.tcx
.instantiate_bound_regions_with_erased(cx.tcx.fn_sig(def_id).skip_binder())
.inputs()
&& let Some(self_arg) = get_ty_def_id(*self_arg)
&& let Some(other_arg) = get_ty_def_id(*other_arg)
// The two arguments are of the same type.
&& self_arg == other_arg
&& let hir_id = cx.tcx.local_def_id_to_hir_id(def_id)
&& let Some(impl_) = get_parent_as_impl(cx.tcx, hir_id)
&& let Some((
_,
Node::Item(Item {
kind: ItemKind::Impl(impl_),
owner_id,
..
}),
)) = cx.tcx.hir().parent_iter(hir_id).next()
// We exclude `impl` blocks generated from rustc's proc macros.
&& !cx.tcx.has_attr(*owner_id, sym::automatically_derived)
// It is a implementation of a trait.
&& let Some(trait_) = impl_.of_trait
&& let Some(trait_def_id) = trait_.trait_def_id()
// The trait is `PartialEq`.
&& Some(trait_def_id) == get_trait_def_id(cx, &["core", "cmp", "PartialEq"])
{
let to_check_op = if name.name == sym::eq {
Expand Down
35 changes: 35 additions & 0 deletions tests/ui/unconditional_recursion.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//@no-rustfix

#![warn(clippy::unconditional_recursion)]
#![allow(clippy::partialeq_ne_impl)]

enum Foo {
A,
Expand Down Expand Up @@ -123,6 +124,40 @@ impl PartialEq for S3 {
}
}

// There should be no warning here!
#[derive(PartialEq)]
enum E {
A,
B,
}

#[derive(PartialEq)]
struct Bar<T: PartialEq>(T);

struct S4;

impl PartialEq for S4 {
fn eq(&self, other: &Self) -> bool {
// No warning here.
Bar(self) == Bar(other)
}
}

macro_rules! impl_partial_eq {
($ty:ident) => {
impl PartialEq for $ty {
fn eq(&self, other: &Self) -> bool {
self == other
}
}
};
}

struct S5;

impl_partial_eq!(S5);
//~^ ERROR: function cannot return without recursing

fn main() {
// test code goes here
}
Loading

0 comments on commit 6b444f3

Please sign in to comment.