Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 23 additions & 18 deletions compiler/rustc_borrowck/src/diagnostics/region_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,12 @@ impl RegionName {
| RegionNameSource::NamedEarlyParamRegion(span) => {
diag.span_label(*span, format!("lifetime `{self}` defined here"));
}
RegionNameSource::SynthesizedFreeEnvRegion(span, note) => {
RegionNameSource::SynthesizedFreeEnvRegion(span, closure_trait) => {
diag.span_label(*span, format!("lifetime `{self}` represents this closure's body"));
diag.note(*note);
diag.note(format!(
"closure implements `{closure_trait}`, so references to captured variables \
can't escape the closure"
));
}
RegionNameSource::AnonRegionFromArgument(RegionNameHighlight::CannotMatchHirTy(
span,
Expand Down Expand Up @@ -326,9 +329,15 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
ty::LateParamRegionKind::ClosureEnv => {
let def_ty = self.regioncx.universal_regions().defining_ty;

let closure_kind = match def_ty {
DefiningTy::Closure(_, args) => args.as_closure().kind(),
DefiningTy::CoroutineClosure(_, args) => args.as_coroutine_closure().kind(),
let (is_lending_coroutine_closure, closure_kind) = match def_ty {
DefiningTy::Closure(_, args) => (false, args.as_closure().kind()),
DefiningTy::CoroutineClosure(_, args) => {
let args = args.as_coroutine_closure();
(
!args.tupled_upvars_ty().is_ty_var() && args.has_self_borrows(),
args.kind(),
)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

so the fix here would be to check whether args.has_self_borrows and if so, talk about AsyncFn(Mut) instead

_ => {
// Can't have BrEnv in functions, constants or coroutines.
bug!("BrEnv outside of closure.");
Expand All @@ -340,23 +349,19 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
bug!("Closure is not defined by a closure expr");
};
let region_name = self.synthesize_region_name();
let note = match closure_kind {
ty::ClosureKind::Fn => {
"closure implements `Fn`, so references to captured variables \
can't escape the closure"
}
ty::ClosureKind::FnMut => {
"closure implements `FnMut`, so references to captured variables \
can't escape the closure"
}
ty::ClosureKind::FnOnce => {
bug!("BrEnv in a `FnOnce` closure");
}
let closure_trait = match (is_lending_coroutine_closure, closure_kind) {
(false, kind) => kind.as_str(),
(true, ty::ClosureKind::Fn) => "AsyncFn",
(true, ty::ClosureKind::FnMut) => "AsyncFnMut",
(true, ty::ClosureKind::FnOnce) => "AsyncFnOnce",
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any way this can look like

let note = format!("closure implements `{}`, so references to captured variables can't escape the closure", closure_kind.as_str());

Copy link
Copy Markdown
Contributor Author

@InvalidPathException InvalidPathException Nov 9, 2025

Choose a reason for hiding this comment

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

I did think about this. Correct me if I'm wrong, RegionNameSource stores these as &'static str but format! gives String, so we cannot really get around it (at least in this way). If we do refactor then it might be a lot of diff for something we do not touch for years.


Some(RegionName {
name: region_name,
source: RegionNameSource::SynthesizedFreeEnvRegion(fn_decl_span, note),
source: RegionNameSource::SynthesizedFreeEnvRegion(
fn_decl_span,
closure_trait,
),
})
}

Expand Down
4 changes: 2 additions & 2 deletions tests/ui/async-await/async-closures/not-lending.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ LL | let x = async move || -> &String { &s };
| | return type of async closure `{async closure body@$DIR/not-lending.rs:12:42: 12:48}` contains a lifetime `'2`
| lifetime `'1` represents this closure's body
|
= note: closure implements `Fn`, so references to captured variables can't escape the closure
= note: closure implements `AsyncFn`, so references to captured variables can't escape the closure

error: lifetime may not live long enough
--> $DIR/not-lending.rs:16:31
Expand All @@ -18,7 +18,7 @@ LL | let x = async move || { &s };
| | return type of async closure `{async closure body@$DIR/not-lending.rs:16:31: 16:37}` contains a lifetime `'2`
| lifetime `'1` represents this closure's body
|
= note: closure implements `Fn`, so references to captured variables can't escape the closure
= note: closure implements `AsyncFn`, so references to captured variables can't escape the closure

error: aborting due to 2 previous errors

Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ LL | | y
LL | | })()
| |_____^ returning this value requires that `'1` must outlive `'2`
|
= note: closure implements `FnMut`, so references to captured variables can't escape the closure
= note: closure implements `AsyncFnMut`, so references to captured variables can't escape the closure

error[E0716]: temporary value dropped while borrowed
--> $DIR/issue-74072-lifetime-name-annotations.rs:13:5
Expand Down Expand Up @@ -88,7 +88,7 @@ LL | | y
LL | | })()
| |_____^ returning this value requires that `'1` must outlive `'2`
|
= note: closure implements `FnMut`, so references to captured variables can't escape the closure
= note: closure implements `AsyncFnMut`, so references to captured variables can't escape the closure

error[E0716]: temporary value dropped while borrowed
--> $DIR/issue-74072-lifetime-name-annotations.rs:23:5
Expand Down
Loading