about summary refs log tree commit diff
diff options
context:
space:
mode:
authoryanglsh <yanglsh@shanghaitech.edu.cn>2025-06-25 21:27:02 +0800
committeryanglsh <yanglsh@shanghaitech.edu.cn>2025-07-14 22:38:04 +0800
commit222ebd0535bcaeda021702bef63e25cfcaa47e5b (patch)
tree4a4dbc8aab3c4d1983577103a526ca06be60a6e5
parent1c64211aee1daca8b039ebc62bda1737c1ef3531 (diff)
downloadrust-222ebd0535bcaeda021702bef63e25cfcaa47e5b.tar.gz
rust-222ebd0535bcaeda021702bef63e25cfcaa47e5b.zip
fix: `search_is_some` suggests wrongly inside macro
-rw-r--r--clippy_utils/src/sugg.rs24
-rw-r--r--tests/ui/search_is_some.rs15
-rw-r--r--tests/ui/search_is_some.stderr17
-rw-r--r--tests/ui/search_is_some_fixable_some.fixed7
-rw-r--r--tests/ui/search_is_some_fixable_some.rs7
-rw-r--r--tests/ui/search_is_some_fixable_some.stderr8
6 files changed, 73 insertions, 5 deletions
diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs
index 7a24d07fa1d..88f313c5f7e 100644
--- a/clippy_utils/src/sugg.rs
+++ b/clippy_utils/src/sugg.rs
@@ -6,9 +6,9 @@ use crate::ty::expr_sig;
 use crate::{get_parent_expr_for_hir, higher};
 use rustc_ast::ast;
 use rustc_ast::util::parser::AssocOp;
+use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::Applicability;
-use rustc_hir as hir;
-use rustc_hir::{Closure, ExprKind, HirId, MutTy, TyKind};
+use rustc_hir::{self as hir, Closure, ExprKind, HirId, MutTy, Node, TyKind};
 use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
 use rustc_lint::{EarlyContext, LateContext, LintContext};
 use rustc_middle::hir::place::ProjectionKind;
@@ -753,8 +753,10 @@ pub fn deref_closure_args(cx: &LateContext<'_>, closure: &hir::Expr<'_>) -> Opti
         let mut visitor = DerefDelegate {
             cx,
             closure_span: closure.span,
+            closure_arg_id: closure_body.params[0].pat.hir_id,
             closure_arg_is_type_annotated_double_ref,
             next_pos: closure.span.lo(),
+            checked_borrows: FxHashSet::default(),
             suggestion_start: String::new(),
             applicability: Applicability::MachineApplicable,
         };
@@ -780,10 +782,15 @@ struct DerefDelegate<'a, 'tcx> {
     cx: &'a LateContext<'tcx>,
     /// The span of the input closure to adapt
     closure_span: Span,
+    /// The `hir_id` of the closure argument being checked
+    closure_arg_id: HirId,
     /// Indicates if the arg of the closure is a type annotated double reference
     closure_arg_is_type_annotated_double_ref: bool,
     /// last position of the span to gradually build the suggestion
     next_pos: BytePos,
+    /// `hir_id`s that has been checked. This is used to avoid checking the same `hir_id` multiple
+    /// times when inside macro expansions.
+    checked_borrows: FxHashSet<HirId>,
     /// starting part of the gradually built suggestion
     suggestion_start: String,
     /// confidence on the built suggestion
@@ -847,9 +854,15 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
 
     fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
 
+    #[expect(clippy::too_many_lines)]
     fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
         if let PlaceBase::Local(id) = cmt.place.base {
             let span = self.cx.tcx.hir_span(cmt.hir_id);
+            if !self.checked_borrows.insert(cmt.hir_id) {
+                // already checked this span and hir_id, skip
+                return;
+            }
+
             let start_span = Span::new(self.next_pos, span.lo(), span.ctxt(), None);
             let mut start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability);
 
@@ -858,7 +871,12 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
             // full identifier that includes projection (i.e.: `fp.field`)
             let ident_str_with_proj = snippet(self.cx, span, "..").to_string();
 
-            if cmt.place.projections.is_empty() {
+            // Make sure to get in all projections if we're on a `matches!`
+            if let Node::Pat(pat) = self.cx.tcx.hir_node(id)
+                && pat.hir_id != self.closure_arg_id
+            {
+                let _ = write!(self.suggestion_start, "{start_snip}{ident_str_with_proj}");
+            } else if cmt.place.projections.is_empty() {
                 // handle item without any projection, that needs an explicit borrowing
                 // i.e.: suggest `&x` instead of `x`
                 let _: fmt::Result = write!(self.suggestion_start, "{start_snip}&{ident_str}");
diff --git a/tests/ui/search_is_some.rs b/tests/ui/search_is_some.rs
index 4143b8bfba5..802d27449ab 100644
--- a/tests/ui/search_is_some.rs
+++ b/tests/ui/search_is_some.rs
@@ -87,3 +87,18 @@ fn is_none() {
     let _ = (0..1).find(some_closure).is_none();
     //~^ search_is_some
 }
+
+#[allow(clippy::match_like_matches_macro)]
+fn issue15102() {
+    let values = [None, Some(3)];
+    let has_even = values
+        //~^ search_is_some
+        .iter()
+        .find(|v| match v {
+            Some(x) if x % 2 == 0 => true,
+            _ => false,
+        })
+        .is_some();
+
+    println!("{has_even}");
+}
diff --git a/tests/ui/search_is_some.stderr b/tests/ui/search_is_some.stderr
index d9a43c8915e..d5412f90111 100644
--- a/tests/ui/search_is_some.stderr
+++ b/tests/ui/search_is_some.stderr
@@ -90,5 +90,20 @@ error: called `is_none()` after searching an `Iterator` with `find`
 LL |     let _ = (0..1).find(some_closure).is_none();
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `!(0..1).any(some_closure)`
 
-error: aborting due to 8 previous errors
+error: called `is_some()` after searching an `Iterator` with `find`
+  --> tests/ui/search_is_some.rs:94:20
+   |
+LL |       let has_even = values
+   |  ____________________^
+LL | |
+LL | |         .iter()
+LL | |         .find(|v| match v {
+...  |
+LL | |         })
+LL | |         .is_some();
+   | |__________________^
+   |
+   = help: this is more succinctly expressed by calling `any()`
+
+error: aborting due to 9 previous errors
 
diff --git a/tests/ui/search_is_some_fixable_some.fixed b/tests/ui/search_is_some_fixable_some.fixed
index 42b39b33b57..c7a4422f373 100644
--- a/tests/ui/search_is_some_fixable_some.fixed
+++ b/tests/ui/search_is_some_fixable_some.fixed
@@ -289,3 +289,10 @@ mod issue9120 {
         //~^ search_is_some
     }
 }
+
+fn issue15102() {
+    let values = [None, Some(3)];
+    let has_even = values.iter().any(|v| matches!(&v, Some(x) if x % 2 == 0));
+    //~^ search_is_some
+    println!("{has_even}");
+}
diff --git a/tests/ui/search_is_some_fixable_some.rs b/tests/ui/search_is_some_fixable_some.rs
index ca4f4d941cb..d6b1c67c971 100644
--- a/tests/ui/search_is_some_fixable_some.rs
+++ b/tests/ui/search_is_some_fixable_some.rs
@@ -297,3 +297,10 @@ mod issue9120 {
         //~^ search_is_some
     }
 }
+
+fn issue15102() {
+    let values = [None, Some(3)];
+    let has_even = values.iter().find(|v| matches!(v, Some(x) if x % 2 == 0)).is_some();
+    //~^ search_is_some
+    println!("{has_even}");
+}
diff --git a/tests/ui/search_is_some_fixable_some.stderr b/tests/ui/search_is_some_fixable_some.stderr
index 8291f48d43c..551a670d937 100644
--- a/tests/ui/search_is_some_fixable_some.stderr
+++ b/tests/ui/search_is_some_fixable_some.stderr
@@ -289,5 +289,11 @@ error: called `is_some()` after searching an `Iterator` with `find`
 LL |         let _ = v.iter().find(|x: &&u32| (*arg_no_deref_dyn)(x)).is_some();
    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|x: &u32| (*arg_no_deref_dyn)(&x))`
 
-error: aborting due to 46 previous errors
+error: called `is_some()` after searching an `Iterator` with `find`
+  --> tests/ui/search_is_some_fixable_some.rs:303:34
+   |
+LL |     let has_even = values.iter().find(|v| matches!(v, Some(x) if x % 2 == 0)).is_some();
+   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `any(|v| matches!(&v, Some(x) if x % 2 == 0))`
+
+error: aborting due to 47 previous errors