about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-01-03 09:14:58 +0000
committerbors <bors@rust-lang.org>2024-01-03 09:14:58 +0000
commit0153ca95ae588f0423f919e199370b6ff02b02c1 (patch)
tree18a825c143060ff799b368fc906b2ef0341a7e1b
parent2eb13d3a7cb19372c40d3db1b45274e126a8dbf2 (diff)
parent7107aa22b2219ff11b645178e32d3905ad99da7d (diff)
downloadrust-0153ca95ae588f0423f919e199370b6ff02b02c1.tar.gz
rust-0153ca95ae588f0423f919e199370b6ff02b02c1.zip
Auto merge of #12062 - GuillaumeGomez:fix-false-positive-unconditional_recursion, r=xFrednet
Fix false positive `unconditional_recursion`

Fixes #12052.

Only checking if both variables are `local` was not enough, we also need to confirm they have the same type as `Self`.

changelog: Fix false positive for `unconditional_recursion` lint
-rw-r--r--clippy_lints/src/unconditional_recursion.rs60
-rw-r--r--tests/ui/unconditional_recursion.rs58
-rw-r--r--tests/ui/unconditional_recursion.stderr53
3 files changed, 140 insertions, 31 deletions
diff --git a/clippy_lints/src/unconditional_recursion.rs b/clippy_lints/src/unconditional_recursion.rs
index 4036ad78a56..5366b5513d3 100644
--- a/clippy_lints/src/unconditional_recursion.rs
+++ b/clippy_lints/src/unconditional_recursion.rs
@@ -1,15 +1,15 @@
 use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::{expr_or_init, get_trait_def_id, path_res};
+use clippy_utils::{expr_or_init, get_trait_def_id};
 use rustc_ast::BinOpKind;
-use rustc_hir::def::Res;
 use rustc_hir::def_id::{DefId, LocalDefId};
-use rustc_hir::intravisit::FnKind;
+use rustc_hir::intravisit::{walk_body, FnKind};
 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;
 use rustc_span::symbol::Ident;
 use rustc_span::{sym, Span};
+use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor;
 
 declare_clippy_lint! {
     /// ### What it does
@@ -52,12 +52,19 @@ fn get_ty_def_id(ty: Ty<'_>) -> Option<DefId> {
     }
 }
 
-fn is_local(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
-    matches!(path_res(cx, expr), Res::Local(_))
+fn has_conditional_return(body: &Body<'_>, expr: &Expr<'_>) -> bool {
+    let mut visitor = ReturnsVisitor::default();
+
+    walk_body(&mut visitor, body);
+    match visitor.returns.as_slice() {
+        [] => false,
+        [return_expr] => return_expr.hir_id != expr.hir_id,
+        _ => true,
+    }
 }
 
 #[allow(clippy::unnecessary_def_path)]
-fn check_partial_eq(cx: &LateContext<'_>, body: &Body<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident) {
+fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) {
     let args = cx
         .tcx
         .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder())
@@ -90,13 +97,23 @@ fn check_partial_eq(cx: &LateContext<'_>, body: &Body<'_>, method_span: Span, me
         } else {
             BinOpKind::Ne
         };
-        let expr = body.value.peel_blocks();
         let is_bad = match expr.kind {
-            ExprKind::Binary(op, left, right) if op.node == to_check_op => is_local(cx, left) && is_local(cx, right),
-            ExprKind::MethodCall(segment, receiver, &[arg], _) if segment.ident.name == name.name => {
-                if is_local(cx, receiver)
-                    && is_local(cx, &arg)
-                    && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
+            ExprKind::Binary(op, left, right) if op.node == to_check_op => {
+                // Then we check if the left-hand element is of the same type as `self`.
+                if let Some(left_ty) = cx.typeck_results().expr_ty_opt(left)
+                    && let Some(left_id) = get_ty_def_id(left_ty)
+                    && self_arg == left_id
+                    && let Some(right_ty) = cx.typeck_results().expr_ty_opt(right)
+                    && let Some(right_id) = get_ty_def_id(right_ty)
+                    && other_arg == right_id
+                {
+                    true
+                } else {
+                    false
+                }
+            },
+            ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => {
+                if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
                     && let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
                     && trait_id == trait_def_id
                 {
@@ -122,7 +139,7 @@ fn check_partial_eq(cx: &LateContext<'_>, body: &Body<'_>, method_span: Span, me
 }
 
 #[allow(clippy::unnecessary_def_path)]
-fn check_to_string(cx: &LateContext<'_>, body: &Body<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident) {
+fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) {
     let args = cx
         .tcx
         .instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder())
@@ -146,12 +163,9 @@ fn check_to_string(cx: &LateContext<'_>, body: &Body<'_>, method_span: Span, met
         // The trait is `ToString`.
         && Some(trait_def_id) == get_trait_def_id(cx, &["alloc", "string", "ToString"])
     {
-        let expr = expr_or_init(cx, body.value.peel_blocks());
         let is_bad = match expr.kind {
-            ExprKind::MethodCall(segment, receiver, &[arg], _) if segment.ident.name == name.name => {
-                if is_local(cx, receiver)
-                    && is_local(cx, &arg)
-                    && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
+            ExprKind::MethodCall(segment, _receiver, &[_arg], _) if segment.ident.name == name.name => {
+                if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
                     && let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
                     && trait_id == trait_def_id
                 {
@@ -187,11 +201,15 @@ impl<'tcx> LateLintPass<'tcx> for UnconditionalRecursion {
         method_def_id: LocalDefId,
     ) {
         // If the function is a method...
-        if let FnKind::Method(name, _) = kind {
+        if let FnKind::Method(name, _) = kind
+            && let expr = expr_or_init(cx, body.value).peel_blocks()
+            // Doesn't have a conditional return.
+            && !has_conditional_return(body, expr)
+        {
             if name.name == sym::eq || name.name == sym::ne {
-                check_partial_eq(cx, body, method_span, method_def_id, name);
+                check_partial_eq(cx, method_span, method_def_id, name, expr);
             } else if name.name == sym::to_string {
-                check_to_string(cx, body, method_span, method_def_id, name);
+                check_to_string(cx, method_span, method_def_id, name, expr);
             }
         }
     }
diff --git a/tests/ui/unconditional_recursion.rs b/tests/ui/unconditional_recursion.rs
index 36fb7e08d64..19cd553b375 100644
--- a/tests/ui/unconditional_recursion.rs
+++ b/tests/ui/unconditional_recursion.rs
@@ -158,18 +158,64 @@ struct S5;
 impl_partial_eq!(S5);
 //~^ ERROR: function cannot return without recursing
 
-struct S6;
+struct S6 {
+    field: String,
+}
+
+impl PartialEq for S6 {
+    fn eq(&self, other: &Self) -> bool {
+        let mine = &self.field;
+        let theirs = &other.field;
+        mine == theirs // Should not warn!
+    }
+}
+
+struct S7<'a> {
+    field: &'a S7<'a>,
+}
+
+impl<'a> PartialEq for S7<'a> {
+    fn eq(&self, other: &Self) -> bool {
+        //~^ ERROR: function cannot return without recursing
+        let mine = &self.field;
+        let theirs = &other.field;
+        mine == theirs
+    }
+}
+
+struct S8 {
+    num: i32,
+    field: Option<Box<S8>>,
+}
+
+impl PartialEq for S8 {
+    fn eq(&self, other: &Self) -> bool {
+        if self.num != other.num {
+            return false;
+        }
+
+        let (this, other) = match (self.field.as_deref(), other.field.as_deref()) {
+            (Some(x1), Some(x2)) => (x1, x2),
+            (None, None) => return true,
+            _ => return false,
+        };
+
+        this == other
+    }
+}
+
+struct S9;
 
-impl std::string::ToString for S6 {
+impl std::string::ToString for S9 {
     fn to_string(&self) -> String {
         //~^ ERROR: function cannot return without recursing
         self.to_string()
     }
 }
 
-struct S7;
+struct S10;
 
-impl std::string::ToString for S7 {
+impl std::string::ToString for S10 {
     fn to_string(&self) -> String {
         //~^ ERROR: function cannot return without recursing
         let x = self;
@@ -177,9 +223,9 @@ impl std::string::ToString for S7 {
     }
 }
 
-struct S8;
+struct S11;
 
-impl std::string::ToString for S8 {
+impl std::string::ToString for S11 {
     fn to_string(&self) -> String {
         //~^ ERROR: function cannot return without recursing
         (self as &Self).to_string()
diff --git a/tests/ui/unconditional_recursion.stderr b/tests/ui/unconditional_recursion.stderr
index 040cc4a85a4..364dd572819 100644
--- a/tests/ui/unconditional_recursion.stderr
+++ b/tests/ui/unconditional_recursion.stderr
@@ -23,7 +23,7 @@ LL |         self.eq(other)
    = help: a `loop` may express intention better if this is on purpose
 
 error: function cannot return without recursing
-  --> $DIR/unconditional_recursion.rs:164:5
+  --> $DIR/unconditional_recursion.rs:210:5
    |
 LL |     fn to_string(&self) -> String {
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing
@@ -34,7 +34,7 @@ LL |         self.to_string()
    = help: a `loop` may express intention better if this is on purpose
 
 error: function cannot return without recursing
-  --> $DIR/unconditional_recursion.rs:173:5
+  --> $DIR/unconditional_recursion.rs:219:5
    |
 LL |     fn to_string(&self) -> String {
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing
@@ -45,7 +45,7 @@ LL |         x.to_string()
    = help: a `loop` may express intention better if this is on purpose
 
 error: function cannot return without recursing
-  --> $DIR/unconditional_recursion.rs:183:5
+  --> $DIR/unconditional_recursion.rs:229:5
    |
 LL |     fn to_string(&self) -> String {
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing
@@ -88,6 +88,34 @@ LL |         self == other
    |         ^^^^^^^^^^^^^
 
 error: function cannot return without recursing
+  --> $DIR/unconditional_recursion.rs:28:5
+   |
+LL | /     fn ne(&self, other: &Self) -> bool {
+LL | |         self != &Foo2::B // no error here
+LL | |     }
+   | |_____^
+   |
+note: recursive call site
+  --> $DIR/unconditional_recursion.rs:29:9
+   |
+LL |         self != &Foo2::B // no error here
+   |         ^^^^^^^^^^^^^^^^
+
+error: function cannot return without recursing
+  --> $DIR/unconditional_recursion.rs:31:5
+   |
+LL | /     fn eq(&self, other: &Self) -> bool {
+LL | |         self == &Foo2::B // no error here
+LL | |     }
+   | |_____^
+   |
+note: recursive call site
+  --> $DIR/unconditional_recursion.rs:32:9
+   |
+LL |         self == &Foo2::B // no error here
+   |         ^^^^^^^^^^^^^^^^
+
+error: function cannot return without recursing
   --> $DIR/unconditional_recursion.rs:42:5
    |
 LL | /     fn ne(&self, other: &Self) -> bool {
@@ -280,5 +308,22 @@ LL | impl_partial_eq!(S5);
    | -------------------- in this macro invocation
    = note: this error originates in the macro `impl_partial_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error: aborting due to 22 previous errors
+error: function cannot return without recursing
+  --> $DIR/unconditional_recursion.rs:178:5
+   |
+LL | /     fn eq(&self, other: &Self) -> bool {
+LL | |
+LL | |         let mine = &self.field;
+LL | |         let theirs = &other.field;
+LL | |         mine == theirs
+LL | |     }
+   | |_____^
+   |
+note: recursive call site
+  --> $DIR/unconditional_recursion.rs:182:9
+   |
+LL |         mine == theirs
+   |         ^^^^^^^^^^^^^^
+
+error: aborting due to 25 previous errors