about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_hir_analysis/src/check/intrinsicck.rs2
-rw-r--r--compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs5
-rw-r--r--compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs6
-rw-r--r--compiler/rustc_hir_typeck/src/upvar.rs114
-rw-r--r--compiler/rustc_middle/src/ty/closure.rs67
-rw-r--r--compiler/rustc_middle/src/ty/mod.rs7
-rw-r--r--compiler/rustc_mir_transform/src/coroutine/by_move_body.rs78
-rw-r--r--config.example.toml2
-rwxr-xr-xsrc/bootstrap/configure.py4
-rw-r--r--tests/ui/async-await/async-borrowck-escaping-closure-error.rs1
-rw-r--r--tests/ui/async-await/async-borrowck-escaping-closure-error.stderr11
-rw-r--r--tests/ui/async-await/async-closures/moro-example.rs43
-rw-r--r--tests/ui/async-await/async-closures/no-borrow-from-env.rs44
-rw-r--r--tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.rs47
-rw-r--r--tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.stderr152
15 files changed, 470 insertions, 113 deletions
diff --git a/compiler/rustc_hir_analysis/src/check/intrinsicck.rs b/compiler/rustc_hir_analysis/src/check/intrinsicck.rs
index 1958a80d47c..45ccd0fa2e0 100644
--- a/compiler/rustc_hir_analysis/src/check/intrinsicck.rs
+++ b/compiler/rustc_hir_analysis/src/check/intrinsicck.rs
@@ -143,7 +143,7 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> {
                 };
                 assert!(
                     ty.is_manually_drop(),
-                    "expected first field of `MaybeUnit` to be `ManuallyDrop`"
+                    "expected first field of `MaybeUninit` to be `ManuallyDrop`"
                 );
                 let fields = &ty.non_enum_variant().fields;
                 let ty = fields[FieldIdx::ZERO].ty(self.tcx, args);
diff --git a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
index affd678fc6c..3d16f1420d9 100644
--- a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
+++ b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
@@ -718,6 +718,11 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> {
     }
 
     #[instrument(level = "debug", skip(self))]
+    fn visit_pattern_type_pattern(&mut self, p: &'tcx hir::Pat<'tcx>) {
+        intravisit::walk_pat(self, p)
+    }
+
+    #[instrument(level = "debug", skip(self))]
     fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
         use self::hir::TraitItemKind::*;
         match trait_item.kind {
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
index 9fb8b4ac40e..63aeb165a48 100644
--- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
@@ -2234,11 +2234,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                                         .type_of(def_id)
                                         .no_bound_vars()
                                         .expect("const parameter types cannot be generic");
-                                    let item_def_id = tcx.parent(def_id);
-                                    let generics = tcx.generics_of(item_def_id);
-                                    let index = generics.param_def_id_to_index[&def_id];
-                                    let name = tcx.item_name(def_id);
-                                    ty::Const::new_param(tcx, ty::ParamConst::new(index, name), ty)
+                                    self.lower_const_param(expr.hir_id, ty)
                                 }
 
                                 _ => {
diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs
index c987bfb9a0e..ef569b4bef3 100644
--- a/compiler/rustc_hir_typeck/src/upvar.rs
+++ b/compiler/rustc_hir_typeck/src/upvar.rs
@@ -367,37 +367,48 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 ty::INNERMOST,
                 ty::BoundRegion { var: ty::BoundVar::ZERO, kind: ty::BoundRegionKind::BrEnv },
             );
+
+            let num_args = args
+                .as_coroutine_closure()
+                .coroutine_closure_sig()
+                .skip_binder()
+                .tupled_inputs_ty
+                .tuple_fields()
+                .len();
+            let typeck_results = self.typeck_results.borrow();
+
             let tupled_upvars_ty_for_borrow = Ty::new_tup_from_iter(
                 self.tcx,
-                self.typeck_results
-                    .borrow()
-                    .closure_min_captures_flattened(
-                        self.tcx.coroutine_for_closure(closure_def_id).expect_local(),
-                    )
-                    // Skip the captures that are just moving the closure's args
-                    // into the coroutine. These are always by move, and we append
-                    // those later in the `CoroutineClosureSignature` helper functions.
-                    .skip(
-                        args.as_coroutine_closure()
-                            .coroutine_closure_sig()
-                            .skip_binder()
-                            .tupled_inputs_ty
-                            .tuple_fields()
-                            .len(),
-                    )
-                    .map(|captured_place| {
-                        let upvar_ty = captured_place.place.ty();
-                        let capture = captured_place.info.capture_kind;
+                ty::analyze_coroutine_closure_captures(
+                    typeck_results.closure_min_captures_flattened(closure_def_id),
+                    typeck_results
+                        .closure_min_captures_flattened(
+                            self.tcx.coroutine_for_closure(closure_def_id).expect_local(),
+                        )
+                        // Skip the captures that are just moving the closure's args
+                        // into the coroutine. These are always by move, and we append
+                        // those later in the `CoroutineClosureSignature` helper functions.
+                        .skip(num_args),
+                    |(_, parent_capture), (_, child_capture)| {
+                        // This is subtle. See documentation on function.
+                        let needs_ref = should_reborrow_from_env_of_parent_coroutine_closure(
+                            parent_capture,
+                            child_capture,
+                        );
+
+                        let upvar_ty = child_capture.place.ty();
+                        let capture = child_capture.info.capture_kind;
                         // Not all upvars are captured by ref, so use
                         // `apply_capture_kind_on_capture_ty` to ensure that we
                         // compute the right captured type.
-                        apply_capture_kind_on_capture_ty(
+                        return apply_capture_kind_on_capture_ty(
                             self.tcx,
                             upvar_ty,
                             capture,
-                            Some(closure_env_region),
-                        )
-                    }),
+                            if needs_ref { Some(closure_env_region) } else { child_capture.region },
+                        );
+                    },
+                ),
             );
             let coroutine_captures_by_ref_ty = Ty::new_fn_ptr(
                 self.tcx,
@@ -1761,6 +1772,63 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     }
 }
 
+/// Determines whether a child capture that is derived from a parent capture
+/// should be borrowed with the lifetime of the parent coroutine-closure's env.
+///
+/// There are two cases when this needs to happen:
+///
+/// (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.
+///
+/// ```rust
+/// #![feature(async_closure)]
+/// let x = &1i32; // Let's call this lifetime `'1`.
+/// let c = async move || {
+///     println!("{:?}", *x);
+///     // Even though the inner coroutine borrows by ref, we're only capturing `*x`,
+///     // not `x`, so the inner closure is allowed to reborrow the data for `'1`.
+/// };
+/// ```
+///
+/// (2.) If a coroutine is mutably borrowing from a parent capture, then that
+/// mutable borrow cannot live for longer than either the parent *or* the borrow
+/// that we have on the original upvar. Therefore we always need to borrow the
+/// child capture with the lifetime of the parent coroutine-closure's env.
+///
+/// ```rust
+/// #![feature(async_closure)]
+/// let mut x = 1i32;
+/// let c = async || {
+///     x = 1;
+///     // The parent borrows `x` for some `&'1 mut i32`.
+///     // However, when we call `c()`, we implicitly autoref for the signature of
+///     // `AsyncFnMut::async_call_mut`. Let's call that lifetime `'call`. Since
+///     // the maximum that `&'call mut &'1 mut i32` can be reborrowed is `&'call mut i32`,
+///     // the inner coroutine should capture w/ the lifetime of the coroutine-closure.
+/// };
+/// ```
+///
+/// If either of these cases apply, then we should capture the borrow with the
+/// lifetime of the parent coroutine-closure's env. Luckily, if this function is
+/// not correct, then the program is not unsound, since we still borrowck and validate
+/// the choices made from this function -- the only side-effect is that the user
+/// may receive unnecessary borrowck errors.
+fn should_reborrow_from_env_of_parent_coroutine_closure<'tcx>(
+    parent_capture: &ty::CapturedPlace<'tcx>,
+    child_capture: &ty::CapturedPlace<'tcx>,
+) -> bool {
+    // (1.)
+    (!parent_capture.is_by_ref()
+        && !matches!(
+            child_capture.place.projections.get(parent_capture.place.projections.len()),
+            Some(Projection { kind: ProjectionKind::Deref, .. })
+        ))
+        // (2.)
+        || matches!(child_capture.info.capture_kind, UpvarCapture::ByRef(ty::BorrowKind::MutBorrow))
+}
+
 /// Truncate the capture so that the place being borrowed is in accordance with RFC 1240,
 /// which states that it's unsafe to take a reference into a struct marked `repr(packed)`.
 fn restrict_repr_packed_field_ref_capture<'tcx>(
diff --git a/compiler/rustc_middle/src/ty/closure.rs b/compiler/rustc_middle/src/ty/closure.rs
index 95d1e08b58b..211d403998f 100644
--- a/compiler/rustc_middle/src/ty/closure.rs
+++ b/compiler/rustc_middle/src/ty/closure.rs
@@ -6,6 +6,7 @@ use crate::{mir, ty};
 use std::fmt::Write;
 
 use crate::query::Providers;
+use rustc_data_structures::captures::Captures;
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_hir as hir;
 use rustc_hir::def_id::LocalDefId;
@@ -415,6 +416,72 @@ impl BorrowKind {
     }
 }
 
+pub fn analyze_coroutine_closure_captures<'a, 'tcx: 'a, T>(
+    parent_captures: impl IntoIterator<Item = &'a CapturedPlace<'tcx>>,
+    child_captures: impl IntoIterator<Item = &'a CapturedPlace<'tcx>>,
+    mut for_each: impl FnMut((usize, &'a CapturedPlace<'tcx>), (usize, &'a CapturedPlace<'tcx>)) -> T,
+) -> impl Iterator<Item = T> + Captures<'a> + Captures<'tcx> {
+    std::iter::from_coroutine(move || {
+        let mut child_captures = child_captures.into_iter().enumerate().peekable();
+
+        // One parent capture may correspond to several child captures if we end up
+        // refining the set of captures via edition-2021 precise captures. We want to
+        // match up any number of child captures with one parent capture, so we keep
+        // peeking off this `Peekable` until the child doesn't match anymore.
+        for (parent_field_idx, parent_capture) in parent_captures.into_iter().enumerate() {
+            // Make sure we use every field at least once, b/c why are we capturing something
+            // if it's not used in the inner coroutine.
+            let mut field_used_at_least_once = false;
+
+            // A parent matches a child if they share the same prefix of projections.
+            // The child may have more, if it is capturing sub-fields out of
+            // something that is captured by-move in the parent closure.
+            while child_captures.peek().map_or(false, |(_, child_capture)| {
+                child_prefix_matches_parent_projections(parent_capture, child_capture)
+            }) {
+                let (child_field_idx, child_capture) = child_captures.next().unwrap();
+                // This analysis only makes sense if the parent capture is a
+                // prefix of the child capture.
+                assert!(
+                    child_capture.place.projections.len() >= parent_capture.place.projections.len(),
+                    "parent capture ({parent_capture:#?}) expected to be prefix of \
+                    child capture ({child_capture:#?})"
+                );
+
+                yield for_each(
+                    (parent_field_idx, parent_capture),
+                    (child_field_idx, child_capture),
+                );
+
+                field_used_at_least_once = true;
+            }
+
+            // Make sure the field was used at least once.
+            assert!(
+                field_used_at_least_once,
+                "we captured {parent_capture:#?} but it was not used in the child coroutine?"
+            );
+        }
+        assert_eq!(child_captures.next(), None, "leftover child captures?");
+    })
+}
+
+fn child_prefix_matches_parent_projections(
+    parent_capture: &ty::CapturedPlace<'_>,
+    child_capture: &ty::CapturedPlace<'_>,
+) -> bool {
+    let HirPlaceBase::Upvar(parent_base) = parent_capture.place.base else {
+        bug!("expected capture to be an upvar");
+    };
+    let HirPlaceBase::Upvar(child_base) = child_capture.place.base else {
+        bug!("expected capture to be an upvar");
+    };
+
+    parent_base.var_path.hir_id == child_base.var_path.hir_id
+        && std::iter::zip(&child_capture.place.projections, &parent_capture.place.projections)
+            .all(|(child, parent)| child.kind == parent.kind)
+}
+
 pub fn provide(providers: &mut Providers) {
     *providers = Providers { closure_typeinfo, ..*providers }
 }
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index ee4dc9744ac..e6b773ae512 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -77,9 +77,10 @@ pub use rustc_type_ir::ConstKind::{
 pub use rustc_type_ir::*;
 
 pub use self::closure::{
-    is_ancestor_or_same_capture, place_to_string_for_capture, BorrowKind, CaptureInfo,
-    CapturedPlace, ClosureTypeInfo, MinCaptureInformationMap, MinCaptureList,
-    RootVariableMinCaptureList, UpvarCapture, UpvarId, UpvarPath, CAPTURE_STRUCT_LOCAL,
+    analyze_coroutine_closure_captures, is_ancestor_or_same_capture, place_to_string_for_capture,
+    BorrowKind, CaptureInfo, CapturedPlace, ClosureTypeInfo, MinCaptureInformationMap,
+    MinCaptureList, RootVariableMinCaptureList, UpvarCapture, UpvarId, UpvarPath,
+    CAPTURE_STRUCT_LOCAL,
 };
 pub use self::consts::{
     Const, ConstData, ConstInt, ConstKind, Expr, ScalarInt, UnevaluatedConst, ValTree,
diff --git a/compiler/rustc_mir_transform/src/coroutine/by_move_body.rs b/compiler/rustc_mir_transform/src/coroutine/by_move_body.rs
index b26f968bf5e..3d6c1a95204 100644
--- a/compiler/rustc_mir_transform/src/coroutine/by_move_body.rs
+++ b/compiler/rustc_mir_transform/src/coroutine/by_move_body.rs
@@ -71,7 +71,7 @@
 
 use rustc_data_structures::unord::UnordMap;
 use rustc_hir as hir;
-use rustc_middle::hir::place::{PlaceBase, Projection, ProjectionKind};
+use rustc_middle::hir::place::{Projection, ProjectionKind};
 use rustc_middle::mir::visit::MutVisitor;
 use rustc_middle::mir::{self, dump_mir, MirPass};
 use rustc_middle::ty::{self, InstanceDef, Ty, TyCtxt, TypeVisitableExt};
@@ -124,44 +124,10 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
             .tuple_fields()
             .len();
 
-        let mut field_remapping = UnordMap::default();
-
-        let mut child_captures = tcx
-            .closure_captures(coroutine_def_id)
-            .iter()
-            .copied()
-            // By construction we capture all the args first.
-            .skip(num_args)
-            .enumerate()
-            .peekable();
-
-        // One parent capture may correspond to several child captures if we end up
-        // refining the set of captures via edition-2021 precise captures. We want to
-        // match up any number of child captures with one parent capture, so we keep
-        // peeking off this `Peekable` until the child doesn't match anymore.
-        for (parent_field_idx, parent_capture) in
-            tcx.closure_captures(parent_def_id).iter().copied().enumerate()
-        {
-            // Make sure we use every field at least once, b/c why are we capturing something
-            // if it's not used in the inner coroutine.
-            let mut field_used_at_least_once = false;
-
-            // A parent matches a child if they share the same prefix of projections.
-            // The child may have more, if it is capturing sub-fields out of
-            // something that is captured by-move in the parent closure.
-            while child_captures.peek().map_or(false, |(_, child_capture)| {
-                child_prefix_matches_parent_projections(parent_capture, child_capture)
-            }) {
-                let (child_field_idx, child_capture) = child_captures.next().unwrap();
-
-                // This analysis only makes sense if the parent capture is a
-                // prefix of the child capture.
-                assert!(
-                    child_capture.place.projections.len() >= parent_capture.place.projections.len(),
-                    "parent capture ({parent_capture:#?}) expected to be prefix of \
-                    child capture ({child_capture:#?})"
-                );
-
+        let field_remapping: UnordMap<_, _> = ty::analyze_coroutine_closure_captures(
+            tcx.closure_captures(parent_def_id).iter().copied(),
+            tcx.closure_captures(coroutine_def_id).iter().skip(num_args).copied(),
+            |(parent_field_idx, parent_capture), (child_field_idx, child_capture)| {
                 // Store this set of additional projections (fields and derefs).
                 // We need to re-apply them later.
                 let child_precise_captures =
@@ -192,7 +158,7 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
                     ),
                 };
 
-                field_remapping.insert(
+                (
                     FieldIdx::from_usize(child_field_idx + num_args),
                     (
                         FieldIdx::from_usize(parent_field_idx + num_args),
@@ -200,18 +166,10 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
                         needs_deref,
                         child_precise_captures,
                     ),
-                );
-
-                field_used_at_least_once = true;
-            }
-
-            // Make sure the field was used at least once.
-            assert!(
-                field_used_at_least_once,
-                "we captured {parent_capture:#?} but it was not used in the child coroutine?"
-            );
-        }
-        assert_eq!(child_captures.next(), None, "leftover child captures?");
+                )
+            },
+        )
+        .collect();
 
         if coroutine_kind == ty::ClosureKind::FnOnce {
             assert_eq!(field_remapping.len(), tcx.closure_captures(parent_def_id).len());
@@ -241,22 +199,6 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
     }
 }
 
-fn child_prefix_matches_parent_projections(
-    parent_capture: &ty::CapturedPlace<'_>,
-    child_capture: &ty::CapturedPlace<'_>,
-) -> bool {
-    let PlaceBase::Upvar(parent_base) = parent_capture.place.base else {
-        bug!("expected capture to be an upvar");
-    };
-    let PlaceBase::Upvar(child_base) = child_capture.place.base else {
-        bug!("expected capture to be an upvar");
-    };
-
-    parent_base.var_path.hir_id == child_base.var_path.hir_id
-        && std::iter::zip(&child_capture.place.projections, &parent_capture.place.projections)
-            .all(|(child, parent)| child.kind == parent.kind)
-}
-
 struct MakeByMoveBody<'tcx> {
     tcx: TyCtxt<'tcx>,
     field_remapping: UnordMap<FieldIdx, (FieldIdx, Ty<'tcx>, bool, &'tcx [Projection<'tcx>])>,
diff --git a/config.example.toml b/config.example.toml
index b8cdc2ec848..0e8fda9a69c 100644
--- a/config.example.toml
+++ b/config.example.toml
@@ -302,7 +302,7 @@
 
 # Set the bootstrap/download cache path. It is useful when building rust
 # repeatedly in a CI invironment.
-# bootstrap-cache-path = /shared/cache
+#bootstrap-cache-path = /path/to/shared/cache
 
 # Enable a build of the extended Rust tool set which is not only the compiler
 # but also tools such as Cargo. This will also produce "combined installers"
diff --git a/src/bootstrap/configure.py b/src/bootstrap/configure.py
index 818a7daadca..768aac912ce 100755
--- a/src/bootstrap/configure.py
+++ b/src/bootstrap/configure.py
@@ -152,9 +152,9 @@ v("default-linker", "rust.default-linker", "the default linker")
 # (others are conditionally saved).
 o("manage-submodules", "build.submodules", "let the build manage the git submodules")
 o("full-bootstrap", "build.full-bootstrap", "build three compilers instead of two (not recommended except for testing reproducible builds)")
-o("bootstrap-cache-path", "build.bootstrap-cache-path", "use provided path for the bootstrap cache")
 o("extended", "build.extended", "build an extended rust tool set")
 
+v("bootstrap-cache-path", None, "use provided path for the bootstrap cache")
 v("tools", None, "List of extended tools will be installed")
 v("codegen-backends", None, "List of codegen backends to build")
 v("build", "build.build", "GNUs ./configure syntax LLVM build triple")
@@ -359,6 +359,8 @@ def apply_args(known_args, option_checking, config):
             set('target.{}.llvm-filecheck'.format(build_triple), value, config)
         elif option.name == 'tools':
             set('build.tools', value.split(','), config)
+        elif option.name == 'bootstrap-cache-path':
+            set('build.bootstrap-cache-path', value, config)
         elif option.name == 'codegen-backends':
             set('rust.codegen-backends', value.split(','), config)
         elif option.name == 'host':
diff --git a/tests/ui/async-await/async-borrowck-escaping-closure-error.rs b/tests/ui/async-await/async-borrowck-escaping-closure-error.rs
index 1990d0ffe2a..ffb97ca04ac 100644
--- a/tests/ui/async-await/async-borrowck-escaping-closure-error.rs
+++ b/tests/ui/async-await/async-borrowck-escaping-closure-error.rs
@@ -5,7 +5,6 @@ fn foo() -> Box<dyn std::future::Future<Output = u32>> {
     let x = 0u32;
     Box::new((async || x)())
     //~^ ERROR cannot return value referencing local variable `x`
-    //~| ERROR cannot return value referencing temporary value
 }
 
 fn main() {
diff --git a/tests/ui/async-await/async-borrowck-escaping-closure-error.stderr b/tests/ui/async-await/async-borrowck-escaping-closure-error.stderr
index be67c78221a..4b1ce300b56 100644
--- a/tests/ui/async-await/async-borrowck-escaping-closure-error.stderr
+++ b/tests/ui/async-await/async-borrowck-escaping-closure-error.stderr
@@ -7,15 +7,6 @@ LL |     Box::new((async || x)())
    |     |        `x` is borrowed here
    |     returns a value referencing data owned by the current function
 
-error[E0515]: cannot return value referencing temporary value
-  --> $DIR/async-borrowck-escaping-closure-error.rs:6:5
-   |
-LL |     Box::new((async || x)())
-   |     ^^^^^^^^^------------^^^
-   |     |        |
-   |     |        temporary value created here
-   |     returns a value referencing data owned by the current function
-
-error: aborting due to 2 previous errors
+error: aborting due to 1 previous error
 
 For more information about this error, try `rustc --explain E0515`.
diff --git a/tests/ui/async-await/async-closures/moro-example.rs b/tests/ui/async-await/async-closures/moro-example.rs
new file mode 100644
index 00000000000..5a8f42c7ca5
--- /dev/null
+++ b/tests/ui/async-await/async-closures/moro-example.rs
@@ -0,0 +1,43 @@
+//@ check-pass
+//@ edition: 2021
+
+#![feature(async_closure)]
+
+use std::future::Future;
+use std::pin::Pin;
+use std::{marker::PhantomData, sync::Mutex};
+
+type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
+
+pub struct Scope<'scope, 'env: 'scope> {
+    enqueued: Mutex<Vec<BoxFuture<'scope, ()>>>,
+    phantom: PhantomData<&'env ()>,
+}
+
+impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
+    pub fn spawn(&'scope self, future: impl Future<Output = ()> + Send + 'scope) {
+        self.enqueued.lock().unwrap().push(Box::pin(future));
+    }
+}
+
+fn scope_with_closure<'env, B>(_body: B) -> BoxFuture<'env, ()>
+where
+    for<'scope> B: async FnOnce(&'scope Scope<'scope, 'env>),
+{
+    todo!()
+}
+
+type ScopeRef<'scope, 'env> = &'scope Scope<'scope, 'env>;
+
+async fn go<'a>(value: &'a i32) {
+    let closure = async |scope: ScopeRef<'_, 'a>| {
+        let _future1 = scope.spawn(async {
+            // Make sure that `*value` is immutably borrowed with lifetime of
+            // `'a` and not with the lifetime of the containing coroutine-closure.
+            let _v = *value;
+        });
+    };
+    scope_with_closure(closure).await;
+}
+
+fn main() {}
diff --git a/tests/ui/async-await/async-closures/no-borrow-from-env.rs b/tests/ui/async-await/async-closures/no-borrow-from-env.rs
new file mode 100644
index 00000000000..fe84aeeb32f
--- /dev/null
+++ b/tests/ui/async-await/async-closures/no-borrow-from-env.rs
@@ -0,0 +1,44 @@
+//@ edition: 2021
+//@ check-pass
+
+#![feature(async_closure)]
+
+fn outlives<'a>(_: impl Sized + 'a) {}
+
+async fn call_once(f: impl async FnOnce()) {
+    f().await;
+}
+
+fn simple<'a>(x: &'a i32) {
+    let c = async || { println!("{}", *x); };
+    outlives::<'a>(c());
+    outlives::<'a>(call_once(c));
+
+    let c = async move || { println!("{}", *x); };
+    outlives::<'a>(c());
+    outlives::<'a>(call_once(c));
+}
+
+struct S<'a>(&'a i32);
+
+fn through_field<'a>(x: S<'a>) {
+    let c = async || { println!("{}", *x.0); };
+    outlives::<'a>(c());
+    outlives::<'a>(call_once(c));
+
+    let c = async move || { println!("{}", *x.0); };
+    outlives::<'a>(c());
+    outlives::<'a>(call_once(c));
+}
+
+fn through_field_and_ref<'a>(x: &S<'a>) {
+    let c = async || { println!("{}", *x.0); };
+    outlives::<'a>(c());
+    outlives::<'a>(call_once(c));
+
+    let c = async move || { println!("{}", *x.0); };
+    outlives::<'a>(c());
+    // outlives::<'a>(call_once(c)); // FIXME(async_closures): Figure out why this fails
+}
+
+fn main() {}
diff --git a/tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.rs b/tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.rs
new file mode 100644
index 00000000000..17681161e20
--- /dev/null
+++ b/tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.rs
@@ -0,0 +1,47 @@
+//@ edition: 2018
+
+// This is `no-borrow-from-env.rs`, but under edition 2018 we still want to make
+// sure that we don't ICE or anything, even if precise closure captures means
+// that we can't actually borrowck successfully.
+
+#![feature(async_closure)]
+
+fn outlives<'a>(_: impl Sized + 'a) {}
+
+async fn call_once(f: impl async FnOnce()) {
+    f().await;
+}
+
+fn simple<'a>(x: &'a i32) {
+    let c = async || { println!("{}", *x); }; //~ ERROR `x` does not live long enough
+    outlives::<'a>(c());
+    outlives::<'a>(call_once(c));
+
+    let c = async move || { println!("{}", *x); };
+    outlives::<'a>(c()); //~ ERROR `c` does not live long enough
+    outlives::<'a>(call_once(c)); //~ ERROR cannot move out of `c`
+}
+
+struct S<'a>(&'a i32);
+
+fn through_field<'a>(x: S<'a>) {
+    let c = async || { println!("{}", *x.0); }; //~ ERROR `x` does not live long enough
+    outlives::<'a>(c());
+    outlives::<'a>(call_once(c));
+
+    let c = async move || { println!("{}", *x.0); }; //~ ERROR cannot move out of `x`
+    outlives::<'a>(c()); //~ ERROR `c` does not live long enough
+    outlives::<'a>(call_once(c)); //~ ERROR cannot move out of `c`
+}
+
+fn through_field_and_ref<'a>(x: &S<'a>) {
+    let c = async || { println!("{}", *x.0); }; //~ ERROR `x` does not live long enough
+    outlives::<'a>(c());
+    outlives::<'a>(call_once(c)); //~ ERROR explicit lifetime required in the type of `x`
+
+    let c = async move || { println!("{}", *x.0); };
+    outlives::<'a>(c()); //~ ERROR `c` does not live long enough
+    // outlives::<'a>(call_once(c)); // FIXME(async_closures): Figure out why this fails
+}
+
+fn main() {}
diff --git a/tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.stderr b/tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.stderr
new file mode 100644
index 00000000000..569028934cb
--- /dev/null
+++ b/tests/ui/async-await/async-closures/without-precise-captures-we-are-powerless.stderr
@@ -0,0 +1,152 @@
+error[E0597]: `x` does not live long enough
+  --> $DIR/without-precise-captures-we-are-powerless.rs:16:13
+   |
+LL | fn simple<'a>(x: &'a i32) {
+   |           -- lifetime `'a` defined here
+LL |     let c = async || { println!("{}", *x); };
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
+LL |     outlives::<'a>(c());
+LL |     outlives::<'a>(call_once(c));
+   |                    ------------ argument requires that `x` is borrowed for `'a`
+...
+LL | }
+   |  - `x` dropped here while still borrowed
+
+error[E0597]: `c` does not live long enough
+  --> $DIR/without-precise-captures-we-are-powerless.rs:21:20
+   |
+LL | fn simple<'a>(x: &'a i32) {
+   |           -- lifetime `'a` defined here
+...
+LL |     let c = async move || { println!("{}", *x); };
+   |         - binding `c` declared here
+LL |     outlives::<'a>(c());
+   |                    ^--
+   |                    |
+   |                    borrowed value does not live long enough
+   |                    argument requires that `c` is borrowed for `'a`
+LL |     outlives::<'a>(call_once(c));
+LL | }
+   | - `c` dropped here while still borrowed
+
+error[E0505]: cannot move out of `c` because it is borrowed
+  --> $DIR/without-precise-captures-we-are-powerless.rs:22:30
+   |
+LL | fn simple<'a>(x: &'a i32) {
+   |           -- lifetime `'a` defined here
+...
+LL |     let c = async move || { println!("{}", *x); };
+   |         - binding `c` declared here
+LL |     outlives::<'a>(c());
+   |                    ---
+   |                    |
+   |                    borrow of `c` occurs here
+   |                    argument requires that `c` is borrowed for `'a`
+LL |     outlives::<'a>(call_once(c));
+   |                              ^ move out of `c` occurs here
+
+error[E0597]: `x` does not live long enough
+  --> $DIR/without-precise-captures-we-are-powerless.rs:28:13
+   |
+LL | fn through_field<'a>(x: S<'a>) {
+   |                  -- lifetime `'a` defined here
+LL |     let c = async || { println!("{}", *x.0); };
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
+LL |     outlives::<'a>(c());
+LL |     outlives::<'a>(call_once(c));
+   |                    ------------ argument requires that `x` is borrowed for `'a`
+...
+LL | }
+   |  - `x` dropped here while still borrowed
+
+error[E0505]: cannot move out of `x` because it is borrowed
+  --> $DIR/without-precise-captures-we-are-powerless.rs:32:13
+   |
+LL | fn through_field<'a>(x: S<'a>) {
+   |                  -- lifetime `'a` defined here
+LL |     let c = async || { println!("{}", *x.0); };
+   |             ---------------------------------- borrow of `x` occurs here
+LL |     outlives::<'a>(c());
+LL |     outlives::<'a>(call_once(c));
+   |                    ------------ argument requires that `x` is borrowed for `'a`
+LL |
+LL |     let c = async move || { println!("{}", *x.0); };
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ move out of `x` occurs here
+
+error[E0597]: `c` does not live long enough
+  --> $DIR/without-precise-captures-we-are-powerless.rs:33:20
+   |
+LL | fn through_field<'a>(x: S<'a>) {
+   |                  -- lifetime `'a` defined here
+...
+LL |     let c = async move || { println!("{}", *x.0); };
+   |         - binding `c` declared here
+LL |     outlives::<'a>(c());
+   |                    ^--
+   |                    |
+   |                    borrowed value does not live long enough
+   |                    argument requires that `c` is borrowed for `'a`
+LL |     outlives::<'a>(call_once(c));
+LL | }
+   | - `c` dropped here while still borrowed
+
+error[E0505]: cannot move out of `c` because it is borrowed
+  --> $DIR/without-precise-captures-we-are-powerless.rs:34:30
+   |
+LL | fn through_field<'a>(x: S<'a>) {
+   |                  -- lifetime `'a` defined here
+...
+LL |     let c = async move || { println!("{}", *x.0); };
+   |         - binding `c` declared here
+LL |     outlives::<'a>(c());
+   |                    ---
+   |                    |
+   |                    borrow of `c` occurs here
+   |                    argument requires that `c` is borrowed for `'a`
+LL |     outlives::<'a>(call_once(c));
+   |                              ^ move out of `c` occurs here
+
+error[E0597]: `x` does not live long enough
+  --> $DIR/without-precise-captures-we-are-powerless.rs:38:13
+   |
+LL | fn through_field_and_ref<'a>(x: &S<'a>) {
+   |                          -- lifetime `'a` defined here
+LL |     let c = async || { println!("{}", *x.0); };
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
+LL |     outlives::<'a>(c());
+LL |     outlives::<'a>(call_once(c));
+   |                    ------------ argument requires that `x` is borrowed for `'a`
+...
+LL | }
+   |  - `x` dropped here while still borrowed
+
+error[E0621]: explicit lifetime required in the type of `x`
+  --> $DIR/without-precise-captures-we-are-powerless.rs:40:20
+   |
+LL | fn through_field_and_ref<'a>(x: &S<'a>) {
+   |                                 ------ help: add explicit lifetime `'a` to the type of `x`: `&'a S<'a>`
+...
+LL |     outlives::<'a>(call_once(c));
+   |                    ^^^^^^^^^^^^ lifetime `'a` required
+
+error[E0597]: `c` does not live long enough
+  --> $DIR/without-precise-captures-we-are-powerless.rs:43:20
+   |
+LL | fn through_field_and_ref<'a>(x: &S<'a>) {
+   |                          -- lifetime `'a` defined here
+...
+LL |     let c = async move || { println!("{}", *x.0); };
+   |         - binding `c` declared here
+LL |     outlives::<'a>(c());
+   |                    ^--
+   |                    |
+   |                    borrowed value does not live long enough
+   |                    argument requires that `c` is borrowed for `'a`
+LL |     // outlives::<'a>(call_once(c)); // FIXME(async_closures): Figure out why this fails
+LL | }
+   | - `c` dropped here while still borrowed
+
+error: aborting due to 10 previous errors
+
+Some errors have detailed explanations: E0505, E0597, E0621.
+For more information about an error, try `rustc --explain E0505`.