From 7be157a60ef84e892f437fb2dcd45faed9a42b11 Mon Sep 17 00:00:00 2001 From: Embers-of-the-Fire Date: Sun, 22 Mar 2026 19:14:07 +0800 Subject: [PATCH 1/4] test: cover current if let closure capture behavior --- .../if-let-patterns-capture-analysis.rs | 54 +++++++++++ .../if-let-patterns-capture-analysis.stderr | 90 +++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.rs create mode 100644 tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.stderr diff --git a/tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.rs b/tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.rs new file mode 100644 index 0000000000000..10b01d456d7bd --- /dev/null +++ b/tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.rs @@ -0,0 +1,54 @@ +//@ edition:2021 + +#![feature(rustc_attrs)] +#![feature(stmt_expr_attributes)] +#![allow(irrefutable_let_patterns)] + +enum SingleVariant { + Pair(i32, String), +} + +fn if_let_closure() { + let variant = SingleVariant::Pair(1, "hello".into()); + + let c = #[rustc_capture_analysis] + || { + //~^ ERROR First Pass analysis includes: + //~| ERROR Min Capture analysis includes: + if let SingleVariant::Pair(ref n, s) = variant { + //~^ NOTE: Capturing variant[] -> Immutable + //~| NOTE: Capturing variant[(0, 0)] -> Immutable + //~| NOTE: Capturing variant[(1, 0)] -> ByValue + //~| NOTE: Min Capture variant[] -> ByValue + let _ = (n, s); + } + }; + + c(); +} + +fn match_closure() { + let variant = SingleVariant::Pair(1, "hello".into()); + + let c = #[rustc_capture_analysis] + || { + //~^ ERROR First Pass analysis includes: + //~| ERROR Min Capture analysis includes: + match variant { + //~^ NOTE: Capturing variant[(0, 0)] -> Immutable + //~| NOTE: Capturing variant[(1, 0)] -> ByValue + //~| NOTE: Min Capture variant[(0, 0)] -> Immutable + //~| NOTE: Min Capture variant[(1, 0)] -> ByValue + SingleVariant::Pair(ref n, s) => { + let _ = (n, s); + } + } + }; + + c(); +} + +fn main() { + if_let_closure(); + match_closure(); +} diff --git a/tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.stderr b/tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.stderr new file mode 100644 index 0000000000000..d93f333a5ef0c --- /dev/null +++ b/tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.stderr @@ -0,0 +1,90 @@ +error: First Pass analysis includes: + --> $DIR/if-let-patterns-capture-analysis.rs:15:5 + | +LL | / || { +LL | | +LL | | +LL | | if let SingleVariant::Pair(ref n, s) = variant { +... | +LL | | }; + | |_____^ + | +note: Capturing variant[] -> Immutable + --> $DIR/if-let-patterns-capture-analysis.rs:18:48 + | +LL | if let SingleVariant::Pair(ref n, s) = variant { + | ^^^^^^^ +note: Capturing variant[(0, 0)] -> Immutable + --> $DIR/if-let-patterns-capture-analysis.rs:18:48 + | +LL | if let SingleVariant::Pair(ref n, s) = variant { + | ^^^^^^^ +note: Capturing variant[(1, 0)] -> ByValue + --> $DIR/if-let-patterns-capture-analysis.rs:18:48 + | +LL | if let SingleVariant::Pair(ref n, s) = variant { + | ^^^^^^^ + +error: Min Capture analysis includes: + --> $DIR/if-let-patterns-capture-analysis.rs:15:5 + | +LL | / || { +LL | | +LL | | +LL | | if let SingleVariant::Pair(ref n, s) = variant { +... | +LL | | }; + | |_____^ + | +note: Min Capture variant[] -> ByValue + --> $DIR/if-let-patterns-capture-analysis.rs:18:48 + | +LL | if let SingleVariant::Pair(ref n, s) = variant { + | ^^^^^^^ + +error: First Pass analysis includes: + --> $DIR/if-let-patterns-capture-analysis.rs:34:5 + | +LL | / || { +LL | | +LL | | +LL | | match variant { +... | +LL | | }; + | |_____^ + | +note: Capturing variant[(0, 0)] -> Immutable + --> $DIR/if-let-patterns-capture-analysis.rs:37:15 + | +LL | match variant { + | ^^^^^^^ +note: Capturing variant[(1, 0)] -> ByValue + --> $DIR/if-let-patterns-capture-analysis.rs:37:15 + | +LL | match variant { + | ^^^^^^^ + +error: Min Capture analysis includes: + --> $DIR/if-let-patterns-capture-analysis.rs:34:5 + | +LL | / || { +LL | | +LL | | +LL | | match variant { +... | +LL | | }; + | |_____^ + | +note: Min Capture variant[(0, 0)] -> Immutable + --> $DIR/if-let-patterns-capture-analysis.rs:37:15 + | +LL | match variant { + | ^^^^^^^ +note: Min Capture variant[(1, 0)] -> ByValue + --> $DIR/if-let-patterns-capture-analysis.rs:37:15 + | +LL | match variant { + | ^^^^^^^ + +error: aborting due to 4 previous errors + From 66777ad3aaf66f494bf2d0b81c4b5086987910a0 Mon Sep 17 00:00:00 2001 From: Embers-of-the-Fire Date: Sun, 22 Mar 2026 19:20:24 +0800 Subject: [PATCH 2/4] fix: drop eager if let scrutinee borrow during capture analysis --- .../rustc_hir_typeck/src/expr_use_visitor.rs | 2 +- .../2229_closure_analysis/capture-enums.rs | 2 -- .../capture-enums.stderr | 24 ++++++------------- .../if-let-patterns-capture-analysis.rs | 6 ++--- .../if-let-patterns-capture-analysis.stderr | 12 +++++----- 5 files changed, 17 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index b6dfda33142c5..a37018cf74031 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -451,7 +451,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx } hir::ExprKind::Let(hir::LetExpr { pat, init, .. }) => { - self.walk_local(init, pat, None, || self.borrow_expr(init, BorrowKind::Immutable))?; + self.walk_local(init, pat, None, || Ok(()))?; } hir::ExprKind::Match(discr, arms, _) => { diff --git a/tests/ui/closures/2229_closure_analysis/capture-enums.rs b/tests/ui/closures/2229_closure_analysis/capture-enums.rs index 36b98351854bf..b0f0b4fcfb5f0 100644 --- a/tests/ui/closures/2229_closure_analysis/capture-enums.rs +++ b/tests/ui/closures/2229_closure_analysis/capture-enums.rs @@ -20,7 +20,6 @@ fn multi_variant_enum() { //~| ERROR Min Capture analysis includes: if let Info::Point(_, _, str) = point { //~^ NOTE: Capturing point[] -> Immutable - //~| NOTE: Capturing point[] -> Immutable //~| NOTE: Capturing point[(2, 0)] -> ByValue //~| NOTE: Min Capture point[] -> ByValue println!("{}", str); @@ -28,7 +27,6 @@ fn multi_variant_enum() { if let Info::Meta(_, v) = meta { //~^ NOTE: Capturing meta[] -> Immutable - //~| NOTE: Capturing meta[] -> Immutable //~| NOTE: Capturing meta[(1, 1)] -> ByValue //~| NOTE: Min Capture meta[] -> ByValue println!("{:?}", v); diff --git a/tests/ui/closures/2229_closure_analysis/capture-enums.stderr b/tests/ui/closures/2229_closure_analysis/capture-enums.stderr index 2f49c8668f85c..2165c3bd828f1 100644 --- a/tests/ui/closures/2229_closure_analysis/capture-enums.stderr +++ b/tests/ui/closures/2229_closure_analysis/capture-enums.stderr @@ -14,28 +14,18 @@ note: Capturing point[] -> Immutable | LL | if let Info::Point(_, _, str) = point { | ^^^^^ -note: Capturing point[] -> Immutable - --> $DIR/capture-enums.rs:21:41 - | -LL | if let Info::Point(_, _, str) = point { - | ^^^^^ note: Capturing point[(2, 0)] -> ByValue --> $DIR/capture-enums.rs:21:41 | LL | if let Info::Point(_, _, str) = point { | ^^^^^ note: Capturing meta[] -> Immutable - --> $DIR/capture-enums.rs:29:35 - | -LL | if let Info::Meta(_, v) = meta { - | ^^^^ -note: Capturing meta[] -> Immutable - --> $DIR/capture-enums.rs:29:35 + --> $DIR/capture-enums.rs:28:35 | LL | if let Info::Meta(_, v) = meta { | ^^^^ note: Capturing meta[(1, 1)] -> ByValue - --> $DIR/capture-enums.rs:29:35 + --> $DIR/capture-enums.rs:28:35 | LL | if let Info::Meta(_, v) = meta { | ^^^^ @@ -57,13 +47,13 @@ note: Min Capture point[] -> ByValue LL | if let Info::Point(_, _, str) = point { | ^^^^^ note: Min Capture meta[] -> ByValue - --> $DIR/capture-enums.rs:29:35 + --> $DIR/capture-enums.rs:28:35 | LL | if let Info::Meta(_, v) = meta { | ^^^^ error: First Pass analysis includes: - --> $DIR/capture-enums.rs:49:5 + --> $DIR/capture-enums.rs:47:5 | LL | / || { LL | | @@ -75,13 +65,13 @@ LL | | }; | |_____^ | note: Capturing point[(2, 0)] -> ByValue - --> $DIR/capture-enums.rs:52:47 + --> $DIR/capture-enums.rs:50:47 | LL | let SingleVariant::Point(_, _, str) = point; | ^^^^^ error: Min Capture analysis includes: - --> $DIR/capture-enums.rs:49:5 + --> $DIR/capture-enums.rs:47:5 | LL | / || { LL | | @@ -93,7 +83,7 @@ LL | | }; | |_____^ | note: Min Capture point[(2, 0)] -> ByValue - --> $DIR/capture-enums.rs:52:47 + --> $DIR/capture-enums.rs:50:47 | LL | let SingleVariant::Point(_, _, str) = point; | ^^^^^ diff --git a/tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.rs b/tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.rs index 10b01d456d7bd..e5f6e940c1c42 100644 --- a/tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.rs +++ b/tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.rs @@ -16,10 +16,10 @@ fn if_let_closure() { //~^ ERROR First Pass analysis includes: //~| ERROR Min Capture analysis includes: if let SingleVariant::Pair(ref n, s) = variant { - //~^ NOTE: Capturing variant[] -> Immutable - //~| NOTE: Capturing variant[(0, 0)] -> Immutable + //~^ NOTE: Capturing variant[(0, 0)] -> Immutable //~| NOTE: Capturing variant[(1, 0)] -> ByValue - //~| NOTE: Min Capture variant[] -> ByValue + //~| NOTE: Min Capture variant[(0, 0)] -> Immutable + //~| NOTE: Min Capture variant[(1, 0)] -> ByValue let _ = (n, s); } }; diff --git a/tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.stderr b/tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.stderr index d93f333a5ef0c..eee008aace344 100644 --- a/tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.stderr +++ b/tests/ui/closures/2229_closure_analysis/if-let-patterns-capture-analysis.stderr @@ -9,11 +9,6 @@ LL | | if let SingleVariant::Pair(ref n, s) = variant { LL | | }; | |_____^ | -note: Capturing variant[] -> Immutable - --> $DIR/if-let-patterns-capture-analysis.rs:18:48 - | -LL | if let SingleVariant::Pair(ref n, s) = variant { - | ^^^^^^^ note: Capturing variant[(0, 0)] -> Immutable --> $DIR/if-let-patterns-capture-analysis.rs:18:48 | @@ -36,7 +31,12 @@ LL | | if let SingleVariant::Pair(ref n, s) = variant { LL | | }; | |_____^ | -note: Min Capture variant[] -> ByValue +note: Min Capture variant[(0, 0)] -> Immutable + --> $DIR/if-let-patterns-capture-analysis.rs:18:48 + | +LL | if let SingleVariant::Pair(ref n, s) = variant { + | ^^^^^^^ +note: Min Capture variant[(1, 0)] -> ByValue --> $DIR/if-let-patterns-capture-analysis.rs:18:48 | LL | if let SingleVariant::Pair(ref n, s) = variant { From e38954ca45bb096f059c0bd7d488d6b74782a8fe Mon Sep 17 00:00:00 2001 From: Embers-of-the-Fire Date: Sun, 22 Mar 2026 20:56:07 +0800 Subject: [PATCH 3/4] test(ui): add regression for if-let closure capture size --- .../run_pass/if-let-capture.rs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/ui/closures/2229_closure_analysis/run_pass/if-let-capture.rs diff --git a/tests/ui/closures/2229_closure_analysis/run_pass/if-let-capture.rs b/tests/ui/closures/2229_closure_analysis/run_pass/if-let-capture.rs new file mode 100644 index 0000000000000..2e78e856cef57 --- /dev/null +++ b/tests/ui/closures/2229_closure_analysis/run_pass/if-let-capture.rs @@ -0,0 +1,51 @@ +//@ edition:2021 +//@ run-pass + +// Regression test for #153982: `if let` in a closure should capture only the +// moved field, matching `match` and plain `let` destructuring. + +#![allow(dead_code, irrefutable_let_patterns)] + +use std::mem::{size_of, size_of_val}; + +struct Thing(String, String); + +fn if_let_capture_size(x: Thing) -> usize { + let closure = || { + if let Thing(_a, _) = x {} + }; + + size_of_val(&closure) +} + +fn match_capture_size(x: Thing) -> usize { + let closure = || { + match x { + Thing(_a, _) => {} + } + }; + + size_of_val(&closure) +} + +fn let_capture_size(x: Thing) -> usize { + let closure = || { + let Thing(_a, _) = x; + }; + + size_of_val(&closure) +} + +fn main() { + let if_let_size = if_let_capture_size(Thing(String::from("a"), String::from("b"))); + let match_size = match_capture_size(Thing(String::from("a"), String::from("b"))); + let let_size = let_capture_size(Thing(String::from("a"), String::from("b"))); + + assert_eq!(if_let_size, size_of::()); + assert_eq!(match_size, size_of::()); + assert_eq!(let_size, size_of::()); + + assert_eq!(if_let_size, match_size); + assert_eq!(if_let_size, let_size); + assert_ne!(if_let_size, size_of::()); +} From 4d78fd1dd04a6976bc8f39bb80c043364858efe3 Mon Sep 17 00:00:00 2001 From: Embers-of-the-Fire Date: Sun, 22 Mar 2026 21:07:12 +0800 Subject: [PATCH 4/4] feat: remove callback param for walk_local method --- compiler/rustc_hir_typeck/src/expr_use_visitor.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index a37018cf74031..f5c457b7d35b4 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -451,7 +451,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx } hir::ExprKind::Let(hir::LetExpr { pat, init, .. }) => { - self.walk_local(init, pat, None, || Ok(()))?; + self.walk_local(init, pat, None)?; } hir::ExprKind::Match(discr, arms, _) => { @@ -577,7 +577,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx fn walk_stmt(&self, stmt: &hir::Stmt<'_>) -> Result<(), Cx::Error> { match stmt.kind { hir::StmtKind::Let(hir::LetStmt { pat, init: Some(expr), els, .. }) => { - self.walk_local(expr, pat, *els, || Ok(()))?; + self.walk_local(expr, pat, *els)?; } hir::StmtKind::Let(_) => {} @@ -617,19 +617,14 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx Ok(()) } - fn walk_local( + fn walk_local( &self, expr: &hir::Expr<'_>, pat: &hir::Pat<'_>, els: Option<&hir::Block<'_>>, - mut f: F, - ) -> Result<(), Cx::Error> - where - F: FnMut() -> Result<(), Cx::Error>, - { + ) -> Result<(), Cx::Error> { self.walk_expr(expr)?; let expr_place = self.cat_expr(expr)?; - f()?; self.fake_read_scrutinee(&expr_place, els.is_some())?; self.walk_pat(&expr_place, pat, false)?; if let Some(els) = els {