Skip to content

Show named lifetime in closure upvar diagnostics#153724

Merged
rust-bors[bot] merged 1 commit intorust-lang:mainfrom
arferreira:fix-lifetime-naming-in-closures
Mar 16, 2026
Merged

Show named lifetime in closure upvar diagnostics#153724
rust-bors[bot] merged 1 commit intorust-lang:mainfrom
arferreira:fix-lifetime-naming-in-closures

Conversation

@arferreira
Copy link
Contributor

@arferreira arferreira commented Mar 11, 2026

Fixes #153545.

When a closure captures a variable whose type involves a named lifetime from the parent function (e.g., 'a), the borrow checker's region renumbering creates a separate RegionVid for each occurrence of the erased lifetime in the closure's upvar type. These new RegionVids have no external_name, so diagnostics fall back to synthetic names like '1.

This PR recovers the original lifetime name by matching the closure's upvar type against the parent function's parameter type (obtained via liberate_late_bound_regions). Both types have the same structure but different region representations, the parent has ReLateParam/ReEarlyParam with real names, the closure has ReVar from renumbering. Walking them in parallel with for_each_free_region lets us find which named lifetime corresponds to the anonymous RegionVid.

Before:

error[E0716]: temporary value dropped while borrowed
 --> test.rs:11:21
  |
6 | fn apply<'a>(
  |          -- lifetime `'1` defined here

After:

error[E0716]: temporary value dropped while borrowed
 --> test.rs:11:21
  |
6 | fn apply<'a>(
  |          -- lifetime `'a` defined here

The fix gracefully falls back to the existing '1 naming when it can't match, nested closures, local variables (not parameters), destructuring patterns, or partial captures where the type structures diverge.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Mar 11, 2026
@rustbot
Copy link
Collaborator

rustbot commented Mar 11, 2026

r? @jieyouxu

rustbot has assigned @jieyouxu.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 69 candidates
  • Random selection from 16 candidates

@arferreira arferreira force-pushed the fix-lifetime-naming-in-closures branch from d4a45ab to 99e1940 Compare March 11, 2026 15:58
Copy link
Member

@jieyouxu jieyouxu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I think the change looks reasonable. There might be some false-matches, but the current diff is better than previous anyway.

@bors r+ rollup

View changes since this review

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meta suggestion: for future PRs that does diagnostic changes like these, it's particularly helpful for the reviewer to evaluate the diagnostic difference if you can structure the PR into 2 commits:

  1. Commit 1 adds the test with a blessed stderr that reflects the current diagnostics (i.e. before your changes)
  2. Commit 2 contains your change and the diff in diagnostic output (i.e. after your change)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, the diff is

diff --git b/tests/ui/borrowck/closure-upvar-named-lifetime.stderr a/tests/ui/borrowck/closure-upvar-named-lifetime.stderr
index 5c2e17ef177..fc7c59fa97c 100644
--- b/tests/ui/borrowck/closure-upvar-named-lifetime.stderr
+++ a/tests/ui/borrowck/closure-upvar-named-lifetime.stderr
@@ -1,8 +1,8 @@
 error[E0597]: `map` does not live long enough
   --> $DIR/closure-upvar-named-lifetime.rs:19:21
    |
-LL |     f: Arc<dyn Fn(Entry<'a, String, String>) + 'a>,
-   |     - lifetime `'1` appears in the type of `f`
+LL | fn apply<'a>(
+   |          -- lifetime `'a` defined here
 ...
 LL |     move |map| {
    |           --- binding `map` declared here
@@ -11,18 +11,18 @@ LL |         let value = map.borrow_mut().entry("foo".to_string());
    |                     ^^^ borrowed value does not live long enough
 ...
 LL |         f(wrapped_value);
-   |         ---------------- argument requires that `map` is borrowed for `'1`
+   |         ---------------- argument requires that `map` is borrowed for `'a`
 LL |     }
    |     - `map` dropped here while still borrowed
    |
-note: requirement that the value outlives `'1` introduced here
+note: requirement that the value outlives `'a` introduced here
   --> $SRC_DIR/core/src/ops/function.rs:LL:COL
 
 error[E0716]: temporary value dropped while borrowed
   --> $DIR/closure-upvar-named-lifetime.rs:19:21
    |
-LL |     f: Arc<dyn Fn(Entry<'a, String, String>) + 'a>,
-   |     - lifetime `'1` appears in the type of `f`
+LL | fn apply<'a>(
+   |          -- lifetime `'a` defined here
 ...
 LL |         let value = map.borrow_mut().entry("foo".to_string());
    |                     ^^^^^^^^^^^^^^^^                         - temporary value is freed at the end of this statement
@@ -30,9 +30,9 @@ LL |         let value = map.borrow_mut().entry("foo".to_string());
    |                     creates a temporary value which is freed while still in use
 ...
 LL |         f(wrapped_value);
-   |         ---------------- argument requires that borrow lasts for `'1`
+   |         ---------------- argument requires that borrow lasts for `'a`
    |
-note: requirement that the value outlives `'1` introduced here
+note: requirement that the value outlives `'a` introduced here
   --> $SRC_DIR/core/src/ops/function.rs:LL:COL
 
 error[E0700]: hidden type for `impl Fn(RefCell<HashMap<String, String>>)` captures lifetime that does not appear in bounds
@@ -60,8 +60,8 @@ LL | ) -> impl Fn(RefCell<HashMap<String, String>>) + use<'a>
 error[E0597]: `map` does not live long enough
   --> $DIR/closure-upvar-named-lifetime.rs:34:21
    |
-LL |     f: Box<dyn Fn(Entry<'a, String, String>) + 'a>,
-   |     - lifetime `'1` appears in the type of `f`
+LL | fn apply_box<'a>(
+   |              -- lifetime `'a` defined here
 ...
 LL |     move |map| {
    |           --- binding `map` declared here
@@ -70,15 +70,15 @@ LL |         let value = map.borrow_mut().entry("foo".to_string());
    |                     ^^^ borrowed value does not live long enough
 ...
 LL |         f(value);
-   |         -------- argument requires that `map` is borrowed for `'1`
+   |         -------- argument requires that `map` is borrowed for `'a`
 LL |     }
    |     - `map` dropped here while still borrowed
 
 error[E0716]: temporary value dropped while borrowed
   --> $DIR/closure-upvar-named-lifetime.rs:34:21
    |
-LL |     f: Box<dyn Fn(Entry<'a, String, String>) + 'a>,
-   |     - lifetime `'1` appears in the type of `f`
+LL | fn apply_box<'a>(
+   |              -- lifetime `'a` defined here
 ...
 LL |         let value = map.borrow_mut().entry("foo".to_string());
    |                     ^^^^^^^^^^^^^^^^                         - temporary value is freed at the end of this statement
@@ -86,7 +86,7 @@ LL |         let value = map.borrow_mut().entry("foo".to_string());
    |                     creates a temporary value which is freed while still in use
 ...
 LL |         f(value);
-   |         -------- argument requires that borrow lasts for `'1`
+   |         -------- argument requires that borrow lasts for `'a`
 
 error[E0700]: hidden type for `impl Fn(RefCell<HashMap<String, String>>)` captures lifetime that does not appear in bounds
   --> $DIR/closure-upvar-named-lifetime.rs:32:5

@rust-bors
Copy link
Contributor

rust-bors bot commented Mar 16, 2026

📌 Commit 99e1940 has been approved by jieyouxu

It is now in the queue for this repository.

@rust-bors rust-bors bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 16, 2026
rust-bors bot pushed a commit that referenced this pull request Mar 16, 2026
Rollup of 8 pull requests

Successful merges:

 - #153639 (Remove `QueryInfo`.)
 - #153570 (MaybeUninit: mention common mistakes in assume_init docs; link to validity invariant docs)
 - #153724 (Show named lifetime in closure upvar diagnostics)
 - #153793 (Add overview documentation for `std::mem`.)
 - #153922 (rustc_mir_build only depends on rustc_lint_defs, not rustc_lint)
 - #153923 (run_make_support: Print the command and output even if it succeeds)
 - #153925 (Provide better suggestions for inference errors on `.collect()?`)
 - #153928 (remove several redundant tests)
@rust-bors rust-bors bot merged commit 3e1052c into rust-lang:main Mar 16, 2026
11 checks passed
@rustbot rustbot added this to the 1.96.0 milestone Mar 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

E0716 without "let's call this" and spurious "requirement introduced here"

3 participants