about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSamuel Tardieu <sam@rfc1149.net>2025-04-14 08:50:44 +0200
committerSamuel Tardieu <sam@rfc1149.net>2025-04-14 09:55:38 +0200
commit77b3ac3d5731b34441cb4ef0badcf9cc261b2013 (patch)
treec74e29afecf7f8d42e09e5d2de0880cbd770445f
parent69ade776fa8a05371c0921124c5a4bcba343b3e1 (diff)
downloadrust-77b3ac3d5731b34441cb4ef0badcf9cc261b2013.tar.gz
rust-77b3ac3d5731b34441cb4ef0badcf9cc261b2013.zip
Check for lifetime uses in closures as well
The `BodyLifetimeChecker` which checks for the use of any non-anonymous
non-static lifetime did not recurse into closures, missing lifetime
uses. This would lead to a bogus elision suggestion.

The `BodyLifetimeChecker` is not refined enough to avoid false
positives, as any conforming lifetime, including one coming from the outer
context, would be considered a hit. The number of false positives might
increase now that we check closures as well, in case those closures
define and use lifetimes themselves.
-rw-r--r--clippy_lints/src/lifetimes.rs19
-rw-r--r--tests/ui/needless_lifetimes.fixed7
-rw-r--r--tests/ui/needless_lifetimes.rs7
3 files changed, 30 insertions, 3 deletions
diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs
index 9b13dfbd0e9..fd6208f6b5e 100644
--- a/clippy_lints/src/lifetimes.rs
+++ b/clippy_lints/src/lifetimes.rs
@@ -314,7 +314,7 @@ fn could_use_elision<'tcx>(
             return None;
         }
 
-        let mut checker = BodyLifetimeChecker;
+        let mut checker = BodyLifetimeChecker::new(cx);
         if checker.visit_expr(body.value).is_break() {
             return None;
         }
@@ -911,10 +911,23 @@ fn elision_suggestions(
     Some(suggestions)
 }
 
-struct BodyLifetimeChecker;
+struct BodyLifetimeChecker<'tcx> {
+    tcx: TyCtxt<'tcx>,
+}
+
+impl<'tcx> BodyLifetimeChecker<'tcx> {
+    fn new(cx: &LateContext<'tcx>) -> Self {
+        Self { tcx: cx.tcx }
+    }
+}
 
-impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker {
+impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker<'tcx> {
     type Result = ControlFlow<()>;
+    type NestedFilter = middle_nested_filter::OnlyBodies;
+
+    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
+        self.tcx
+    }
     // for lifetimes as parameters of generics
     fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) -> ControlFlow<()> {
         if !lifetime.is_anonymous() && lifetime.ident.name != kw::StaticLifetime {
diff --git a/tests/ui/needless_lifetimes.fixed b/tests/ui/needless_lifetimes.fixed
index d59393fb3f3..e9d811986aa 100644
--- a/tests/ui/needless_lifetimes.fixed
+++ b/tests/ui/needless_lifetimes.fixed
@@ -534,4 +534,11 @@ mod issue13749bis {
     impl<'a, T: 'a> Generic<T> {}
 }
 
+pub fn issue14607<'s>(x: &'s u8) {
+    #[expect(clippy::redundant_closure_call)]
+    (|| {
+        let _: &'s u8 = x;
+    })();
+}
+
 fn main() {}
diff --git a/tests/ui/needless_lifetimes.rs b/tests/ui/needless_lifetimes.rs
index e24907ab5fc..0b6eb9755b9 100644
--- a/tests/ui/needless_lifetimes.rs
+++ b/tests/ui/needless_lifetimes.rs
@@ -534,4 +534,11 @@ mod issue13749bis {
     impl<'a, T: 'a> Generic<T> {}
 }
 
+pub fn issue14607<'s>(x: &'s u8) {
+    #[expect(clippy::redundant_closure_call)]
+    (|| {
+        let _: &'s u8 = x;
+    })();
+}
+
 fn main() {}