Skip to content

Commit 3784660

Browse files
Make the computation of coroutine_captures_by_ref_ty more sophisticated
1 parent 12ae3f2 commit 3784660

7 files changed

+376
-34
lines changed

compiler/rustc_hir_typeck/src/upvar.rs

+89-23
Original file line numberDiff line numberDiff line change
@@ -367,37 +367,48 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
367367
ty::INNERMOST,
368368
ty::BoundRegion { var: ty::BoundVar::ZERO, kind: ty::BoundRegionKind::BrEnv },
369369
);
370+
371+
let num_args = args
372+
.as_coroutine_closure()
373+
.coroutine_closure_sig()
374+
.skip_binder()
375+
.tupled_inputs_ty
376+
.tuple_fields()
377+
.len();
378+
let typeck_results = self.typeck_results.borrow();
379+
370380
let tupled_upvars_ty_for_borrow = Ty::new_tup_from_iter(
371381
self.tcx,
372-
self.typeck_results
373-
.borrow()
374-
.closure_min_captures_flattened(
375-
self.tcx.coroutine_for_closure(closure_def_id).expect_local(),
376-
)
377-
// Skip the captures that are just moving the closure's args
378-
// into the coroutine. These are always by move, and we append
379-
// those later in the `CoroutineClosureSignature` helper functions.
380-
.skip(
381-
args.as_coroutine_closure()
382-
.coroutine_closure_sig()
383-
.skip_binder()
384-
.tupled_inputs_ty
385-
.tuple_fields()
386-
.len(),
387-
)
388-
.map(|captured_place| {
389-
let upvar_ty = captured_place.place.ty();
390-
let capture = captured_place.info.capture_kind;
382+
ty::analyze_coroutine_closure_captures(
383+
typeck_results.closure_min_captures_flattened(closure_def_id),
384+
typeck_results
385+
.closure_min_captures_flattened(
386+
self.tcx.coroutine_for_closure(closure_def_id).expect_local(),
387+
)
388+
// Skip the captures that are just moving the closure's args
389+
// into the coroutine. These are always by move, and we append
390+
// those later in the `CoroutineClosureSignature` helper functions.
391+
.skip(num_args),
392+
|(_, parent_capture), (_, child_capture)| {
393+
// This is subtle. See documentation on function.
394+
let needs_ref = should_reborrow_from_env_of_parent_coroutine_closure(
395+
parent_capture,
396+
child_capture,
397+
);
398+
399+
let upvar_ty = child_capture.place.ty();
400+
let capture = child_capture.info.capture_kind;
391401
// Not all upvars are captured by ref, so use
392402
// `apply_capture_kind_on_capture_ty` to ensure that we
393403
// compute the right captured type.
394-
apply_capture_kind_on_capture_ty(
404+
return apply_capture_kind_on_capture_ty(
395405
self.tcx,
396406
upvar_ty,
397407
capture,
398-
Some(closure_env_region),
399-
)
400-
}),
408+
if needs_ref { Some(closure_env_region) } else { child_capture.region },
409+
);
410+
},
411+
),
401412
);
402413
let coroutine_captures_by_ref_ty = Ty::new_fn_ptr(
403414
self.tcx,
@@ -1761,6 +1772,61 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
17611772
}
17621773
}
17631774

1775+
/// Determines whether a child capture that is derived from a parent capture
1776+
/// should be borrowed with the lifetime of the parent coroutine-closure's env.
1777+
///
1778+
/// There are two cases when this needs to happen:
1779+
///
1780+
/// (1.) Are we borrowing data owned by the parent closure? We can determine if
1781+
/// that is the case by checking if the parent capture is by move, EXCEPT if we
1782+
/// apply a deref projection, which means we're reborrowing a reference that we
1783+
/// captured by move.
1784+
///
1785+
/// ```rust
1786+
/// #![feature(async_closure)]
1787+
/// let x = &i32; // Let's call this lifetime `'1`.
1788+
/// let c = async move || {
1789+
/// println!("{:?}", *x);
1790+
/// // Even though the inner coroutine borrows by ref, we're only capturing `*x`,
1791+
/// // not `x`, so the inner closure is allowed to reborrow the data for `'1`.
1792+
/// };
1793+
/// ```
1794+
///
1795+
/// (2.) If a coroutine is mutably borrowing from a parent capture, then that
1796+
/// mutable borrow cannot live for longer than either the parent *or* the borrow
1797+
/// that we have on the original upvar. Therefore we always need to borrow the
1798+
/// child capture with the lifetime of the parent coroutine-closure's env.
1799+
///
1800+
/// ```rust
1801+
/// #![feature(async_closure)]
1802+
/// let mut x = 1i32;
1803+
/// let c = async || {
1804+
/// x = 1;
1805+
/// // The parent borrows `x` for some `&'1 mut i32`.
1806+
/// // However, when we call `c()`, we implicitly autoref for the signature of
1807+
/// // `AsyncFnMut::async_call_mut`. Let's call that lifetime `'call`. Since
1808+
/// // the maximum that `&'call mut &'1 mut i32` can be reborrowed is `&'call mut i32`,
1809+
/// // the inner coroutine should capture w/ the lifetime of the coroutine-closure.
1810+
/// };
1811+
/// ```
1812+
///
1813+
/// If either of these cases apply, then we should capture the borrow with the
1814+
/// lifetime of the parent coroutine-closure's env. Luckily, if this function is
1815+
/// not correct, then the program is not unsound, since we still borrowck and validate
1816+
/// the choices made from this function -- the only side-effect is that the user
1817+
/// may receive unnecessary borrowck errors.
1818+
fn should_reborrow_from_env_of_parent_coroutine_closure<'tcx>(
1819+
parent_capture: &ty::CapturedPlace<'tcx>,
1820+
child_capture: &ty::CapturedPlace<'tcx>,
1821+
) -> bool {
1822+
(!parent_capture.is_by_ref()
1823+
&& !matches!(
1824+
child_capture.place.projections.get(parent_capture.place.projections.len()),
1825+
Some(Projection { kind: ProjectionKind::Deref, .. })
1826+
))
1827+
|| matches!(child_capture.info.capture_kind, UpvarCapture::ByRef(ty::BorrowKind::MutBorrow))
1828+
}
1829+
17641830
/// Truncate the capture so that the place being borrowed is in accordance with RFC 1240,
17651831
/// which states that it's unsafe to take a reference into a struct marked `repr(packed)`.
17661832
fn restrict_repr_packed_field_ref_capture<'tcx>(

tests/ui/async-await/async-borrowck-escaping-closure-error.rs

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ fn foo() -> Box<dyn std::future::Future<Output = u32>> {
55
let x = 0u32;
66
Box::new((async || x)())
77
//~^ ERROR cannot return value referencing local variable `x`
8-
//~| ERROR cannot return value referencing temporary value
98
}
109

1110
fn main() {

tests/ui/async-await/async-borrowck-escaping-closure-error.stderr

+1-10
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,6 @@ LL | Box::new((async || x)())
77
| | `x` is borrowed here
88
| returns a value referencing data owned by the current function
99

10-
error[E0515]: cannot return value referencing temporary value
11-
--> $DIR/async-borrowck-escaping-closure-error.rs:6:5
12-
|
13-
LL | Box::new((async || x)())
14-
| ^^^^^^^^^------------^^^
15-
| | |
16-
| | temporary value created here
17-
| returns a value referencing data owned by the current function
18-
19-
error: aborting due to 2 previous errors
10+
error: aborting due to 1 previous error
2011

2112
For more information about this error, try `rustc --explain E0515`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//@ check-pass
2+
//@ edition: 2021
3+
4+
#![feature(async_closure)]
5+
6+
use std::future::Future;
7+
use std::pin::Pin;
8+
use std::{marker::PhantomData, sync::Mutex};
9+
10+
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
11+
12+
pub struct Scope<'scope, 'env: 'scope> {
13+
enqueued: Mutex<Vec<BoxFuture<'scope, ()>>>,
14+
phantom: PhantomData<&'env ()>,
15+
}
16+
17+
impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
18+
pub fn spawn(&'scope self, future: impl Future<Output = ()> + Send + 'scope) {
19+
self.enqueued.lock().unwrap().push(Box::pin(future));
20+
}
21+
}
22+
23+
fn scope_with_closure<'env, B>(_body: B) -> BoxFuture<'env, ()>
24+
where
25+
for<'scope> B: async FnOnce(&'scope Scope<'scope, 'env>),
26+
{
27+
todo!()
28+
}
29+
30+
type ScopeRef<'scope, 'env> = &'scope Scope<'scope, 'env>;
31+
32+
async fn go<'a>(value: &'a i32) {
33+
let closure = async |scope: ScopeRef<'_, 'a>| {
34+
let _future1 = scope.spawn(async {
35+
// Make sure that `*value` is immutably borrowed with lifetime of
36+
// `'a` and not with the lifetime of the containing coroutine-closure.
37+
let _v = *value;
38+
});
39+
};
40+
scope_with_closure(closure).await;
41+
}
42+
43+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//@ edition: 2021
2+
//@ check-pass
3+
4+
#![feature(async_closure)]
5+
6+
fn outlives<'a>(_: impl Sized + 'a) {}
7+
8+
async fn call_once(f: impl async FnOnce()) {
9+
f().await;
10+
}
11+
12+
fn simple<'a>(x: &'a i32) {
13+
let c = async || { println!("{}", *x); };
14+
outlives::<'a>(c());
15+
outlives::<'a>(call_once(c));
16+
17+
let c = async move || { println!("{}", *x); };
18+
outlives::<'a>(c());
19+
outlives::<'a>(call_once(c));
20+
}
21+
22+
struct S<'a>(&'a i32);
23+
24+
fn through_field<'a>(x: S<'a>) {
25+
let c = async || { println!("{}", *x.0); };
26+
outlives::<'a>(c());
27+
outlives::<'a>(call_once(c));
28+
29+
let c = async move || { println!("{}", *x.0); };
30+
outlives::<'a>(c());
31+
outlives::<'a>(call_once(c));
32+
}
33+
34+
fn through_field_and_ref<'a>(x: &S<'a>) {
35+
let c = async || { println!("{}", *x.0); };
36+
outlives::<'a>(c());
37+
outlives::<'a>(call_once(c));
38+
39+
let c = async move || { println!("{}", *x.0); };
40+
outlives::<'a>(c());
41+
// outlives::<'a>(call_once(c)); // FIXME(async_closures): Figure out why this fails
42+
}
43+
44+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//@ edition: 2018
2+
3+
// This is `no-borrow-from-env.rs`, but under edition 2018 we still want to make
4+
// sure that we don't ICE or anything, even if precise closure captures means
5+
// that we can't actually borrowck successfully.
6+
7+
#![feature(async_closure)]
8+
9+
fn outlives<'a>(_: impl Sized + 'a) {}
10+
11+
async fn call_once(f: impl async FnOnce()) {
12+
f().await;
13+
}
14+
15+
fn simple<'a>(x: &'a i32) {
16+
let c = async || { println!("{}", *x); }; //~ ERROR `x` does not live long enough
17+
outlives::<'a>(c());
18+
outlives::<'a>(call_once(c));
19+
20+
let c = async move || { println!("{}", *x); };
21+
outlives::<'a>(c()); //~ ERROR `c` does not live long enough
22+
outlives::<'a>(call_once(c)); //~ ERROR cannot move out of `c`
23+
}
24+
25+
struct S<'a>(&'a i32);
26+
27+
fn through_field<'a>(x: S<'a>) {
28+
let c = async || { println!("{}", *x.0); }; //~ ERROR `x` does not live long enough
29+
outlives::<'a>(c());
30+
outlives::<'a>(call_once(c));
31+
32+
let c = async move || { println!("{}", *x.0); }; //~ ERROR cannot move out of `x`
33+
outlives::<'a>(c()); //~ ERROR `c` does not live long enough
34+
outlives::<'a>(call_once(c)); //~ ERROR cannot move out of `c`
35+
}
36+
37+
fn through_field_and_ref<'a>(x: &S<'a>) {
38+
let c = async || { println!("{}", *x.0); }; //~ ERROR `x` does not live long enough
39+
outlives::<'a>(c());
40+
outlives::<'a>(call_once(c)); //~ ERROR explicit lifetime required in the type of `x`
41+
42+
let c = async move || { println!("{}", *x.0); };
43+
outlives::<'a>(c()); //~ ERROR `c` does not live long enough
44+
// outlives::<'a>(call_once(c)); // FIXME(async_closures): Figure out why this fails
45+
}
46+
47+
fn main() {}

0 commit comments

Comments
 (0)