Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve upvar analysis for deref of child capture #138517

Merged
merged 1 commit into from
Mar 17, 2025
Merged
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
25 changes: 19 additions & 6 deletions compiler/rustc_hir_typeck/src/upvar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1862,8 +1862,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
///
/// (1.) Are we borrowing data owned by the parent closure? We can determine if
/// that is the case by checking if the parent capture is by move, EXCEPT if we
/// apply a deref projection, which means we're reborrowing a reference that we
/// captured by move.
/// apply a deref projection of an immutable reference, reborrows of immutable
/// references which aren't restricted to the LUB of the lifetimes of the deref
/// chain. This is why `&'short mut &'long T` can be reborrowed as `&'long T`.
///
/// ```rust
/// let x = &1i32; // Let's call this lifetime `'1`.
Expand Down Expand Up @@ -1902,10 +1903,22 @@ fn should_reborrow_from_env_of_parent_coroutine_closure<'tcx>(
) -> bool {
// (1.)
(!parent_capture.is_by_ref()
&& !matches!(
child_capture.place.projections.get(parent_capture.place.projections.len()),
Some(Projection { kind: ProjectionKind::Deref, .. })
))
// This is just inlined `place.deref_tys()` but truncated to just
// the child projections. Namely, look for a `&T` deref, since we
// can always extend `&'short mut &'long T` to `&'long T`.
&& !child_capture
.place
.projections
.iter()
.enumerate()
.skip(parent_capture.place.projections.len())
.any(|(idx, proj)| {
matches!(proj.kind, ProjectionKind::Deref)
&& matches!(
child_capture.place.ty_before_projection(idx).kind(),
ty::Ref(.., ty::Mutability::Not)
)
}))
// (2.)
|| matches!(child_capture.info.capture_kind, UpvarCapture::ByRef(ty::BorrowKind::Mutable))
}
Expand Down
46 changes: 46 additions & 0 deletions tests/ui/async-await/async-closures/imm-deref-lending.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//@ edition: 2021
//@ check-pass

#![feature(impl_trait_in_bindings)]

struct FooS {
precise: i32,
}

fn ref_inside_mut(f: &mut &FooS) {
let x: impl AsyncFn() = async move || {
let y = &f.precise;
};
}

fn mut_inside_ref(f: &&mut FooS) {
let x: impl AsyncFn() = async move || {
let y = &f.precise;
};
}

fn mut_ref_inside_mut(f: &mut &mut FooS) {
let x: impl AsyncFn() = async move || {
let y = &f.precise;
};
}

fn ref_inside_box(f: Box<&FooS>) {
let x: impl AsyncFn() = async move || {
let y = &f.precise;
};
}

fn box_inside_ref(f: &Box<FooS>) {
let x: impl AsyncFn() = async move || {
let y = &f.precise;
};
}

fn box_inside_box(f: Box<Box<FooS>>) {
let x: impl AsyncFn() = async move || {
let y = &f.precise;
};
}

fn main() {}
49 changes: 49 additions & 0 deletions tests/ui/async-await/async-closures/imm-deref-not-lending.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//@ edition: 2021

#![feature(impl_trait_in_bindings)]

struct FooS {
precise: i32,
}

fn ref_inside_mut(f: &mut &FooS) {
let x: impl Fn() -> _ = async move || {
let y = &f.precise;
};
}

fn mut_inside_ref(f: &&mut FooS) {
let x: impl Fn() -> _ = async move || {
let y = &f.precise;
};
}

// Expected to fail, no immutable reference here.
fn mut_ref_inside_mut(f: &mut &mut FooS) {
let x: impl Fn() -> _ = async move || {
//~^ ERROR async closure does not implement `Fn`
let y = &f.precise;
};
}

fn ref_inside_box(f: Box<&FooS>) {
let x: impl Fn() -> _ = async move || {
let y = &f.precise;
};
}

fn box_inside_ref(f: &Box<FooS>) {
let x: impl Fn() -> _ = async move || {
let y = &f.precise;
};
}

// Expected to fail, no immutable reference here.
fn box_inside_box(f: Box<Box<FooS>>) {
let x: impl Fn() -> _ = async move || {
//~^ ERROR async closure does not implement `Fn`
let y = &f.precise;
};
}

fn main() {}
14 changes: 14 additions & 0 deletions tests/ui/async-await/async-closures/imm-deref-not-lending.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: async closure does not implement `Fn` because it captures state from its environment
--> $DIR/imm-deref-not-lending.rs:23:29
|
LL | let x: impl Fn() -> _ = async move || {
| ^^^^^^^^^^^^^

error: async closure does not implement `Fn` because it captures state from its environment
--> $DIR/imm-deref-not-lending.rs:43:29
|
LL | let x: impl Fn() -> _ = async move || {
| ^^^^^^^^^^^^^

error: aborting due to 2 previous errors

Loading