about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDylan DPC <dylan.dpc@gmail.com>2021-02-17 23:51:19 +0100
committerGitHub <noreply@github.com>2021-02-17 23:51:19 +0100
commit0b2f2b941332954c5bd80e61eabce3ee2161a7f5 (patch)
tree589fbb4f268b617baf3e4ca1099ad7d5fda1c98a
parentcdd93fd3e2f6fb03717db137196a371f6b5c55f7 (diff)
parentf99e152e5a02d75e5df1a10b33d4e8592cd25fee (diff)
downloadrust-0b2f2b941332954c5bd80e61eabce3ee2161a7f5.tar.gz
rust-0b2f2b941332954c5bd80e61eabce3ee2161a7f5.zip
Rollup merge of #82007 - sexxi-goose:reborrow, r=nikomatsakis
Implement reborrow for closure captures

The strategy for captures is detailed here with examples: https://hackmd.io/PzxYMPY4RF-B9iH9uj9GTA

Key points:
- We only need to reborrow a capture in case of move closures.
  - If we mutate something via a `&mut` we store it as a `MutBorrow`/`UniqueMuBorrow` of the path containing the `&mut`,
  - Similarly, if it's read via `&` ref we just store it as a `ImmBorrow` of the path containing the `&` ref.
  - If a path doesn't deref a `&mut`, `&`, then that path is captured by Move.
  - If the use of a path results in a move when the closure is called, then that path is truncated before any deref and the truncated path is moved into the closure.

- In the case of non-move closure if a use of a path results in a move, then the path is truncated before any deref and the truncated path is moved into the closure.

Note that the implementation differs a bit from the document to allow for truncated path to be used in the ClosureKind analysis that happens as part of the first capture analysis pass.

Closes: https://github.com/rust-lang/project-rfc-2229/issues/31

r? ````@nikomatsakis````
-rw-r--r--compiler/rustc_typeck/src/check/upvar.rs105
-rw-r--r--src/test/ui/closures/2229_closure_analysis/by_value.rs3
-rw-r--r--src/test/ui/closures/2229_closure_analysis/by_value.stderr11
-rw-r--r--src/test/ui/closures/2229_closure_analysis/diagnostics/closure-origin-array-diagnostics.rs16
-rw-r--r--src/test/ui/closures/2229_closure_analysis/diagnostics/closure-origin-array-diagnostics.stderr23
-rw-r--r--src/test/ui/closures/2229_closure_analysis/move_closure.rs101
-rw-r--r--src/test/ui/closures/2229_closure_analysis/move_closure.stderr197
-rw-r--r--src/test/ui/closures/2229_closure_analysis/run_pass/move_closure.rs41
8 files changed, 408 insertions, 89 deletions
diff --git a/compiler/rustc_typeck/src/check/upvar.rs b/compiler/rustc_typeck/src/check/upvar.rs
index d097f863ad2..69c09528662 100644
--- a/compiler/rustc_typeck/src/check/upvar.rs
+++ b/compiler/rustc_typeck/src/check/upvar.rs
@@ -180,7 +180,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     debug!("seed place {:?}", place);
 
                     let upvar_id = ty::UpvarId::new(*var_hir_id, local_def_id);
-                    let capture_kind = self.init_capture_kind(capture_clause, upvar_id, span);
+                    let capture_kind =
+                        self.init_capture_kind_for_place(&place, capture_clause, upvar_id, span);
                     let fake_info = ty::CaptureInfo {
                         capture_kind_expr_id: None,
                         path_expr_id: None,
@@ -205,11 +206,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             // If we have an origin, store it.
             if let Some(origin) = delegate.current_origin.clone() {
                 let origin = if self.tcx.features().capture_disjoint_fields {
-                    origin
+                    (origin.0, restrict_capture_precision(origin.1))
                 } else {
-                    // FIXME(project-rfc-2229#31): Once the changes to support reborrowing are
-                    //                             made, make sure we are selecting and restricting
-                    //                             the origin correctly.
                     (origin.0, Place { projections: vec![], ..origin.1 })
                 };
 
@@ -449,7 +447,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 base => bug!("Expected upvar, found={:?}", base),
             };
 
-            let place = restrict_capture_precision(place, capture_info.capture_kind);
+            let place = restrict_capture_precision(place);
 
             let min_cap_list = match root_var_min_capture_list.get_mut(&var_hir_id) {
                 None => {
@@ -897,15 +895,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         }
     }
 
-    fn init_capture_kind(
+    fn init_capture_kind_for_place(
         &self,
+        place: &Place<'tcx>,
         capture_clause: hir::CaptureBy,
         upvar_id: ty::UpvarId,
         closure_span: Span,
     ) -> ty::UpvarCapture<'tcx> {
         match capture_clause {
-            hir::CaptureBy::Value => ty::UpvarCapture::ByValue(None),
-            hir::CaptureBy::Ref => {
+            // In case of a move closure if the data is accessed through a reference we
+            // want to capture by ref to allow precise capture using reborrows.
+            //
+            // If the data will be moved out of this place, then the place will be truncated
+            // at the first Deref in `adjust_upvar_borrow_kind_for_consume` and then moved into
+            // the closure.
+            hir::CaptureBy::Value if !place.deref_tys().any(ty::TyS::is_ref) => {
+                ty::UpvarCapture::ByValue(None)
+            }
+            hir::CaptureBy::Value | hir::CaptureBy::Ref => {
                 let origin = UpvarRegion(upvar_id, closure_span);
                 let upvar_region = self.next_region_var(origin);
                 let upvar_borrow = ty::UpvarBorrow { kind: ty::ImmBorrow, region: upvar_region };
@@ -1109,12 +1116,25 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
             place_with_id, diag_expr_id, mode
         );
 
-        // we only care about moves
-        match mode {
-            euv::Copy => {
+        match (self.capture_clause, mode) {
+            // In non-move closures, we only care about moves
+            (hir::CaptureBy::Ref, euv::Copy) => return,
+
+            // We want to capture Copy types that read through a ref via a reborrow
+            (hir::CaptureBy::Value, euv::Copy)
+                if place_with_id.place.deref_tys().any(ty::TyS::is_ref) =>
+            {
                 return;
             }
-            euv::Move => {}
+
+            (hir::CaptureBy::Ref, euv::Move) | (hir::CaptureBy::Value, euv::Move | euv::Copy) => {}
+        };
+
+        let place = truncate_capture_for_move(place_with_id.place.clone());
+        let place_with_id = PlaceWithHirId { place: place.clone(), hir_id: place_with_id.hir_id };
+
+        if !self.capture_information.contains_key(&place) {
+            self.init_capture_info_for_place(&place_with_id, diag_expr_id);
         }
 
         let tcx = self.fcx.tcx;
@@ -1128,13 +1148,15 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
 
         let usage_span = tcx.hir().span(diag_expr_id);
 
-        // To move out of an upvar, this must be a FnOnce closure
-        self.adjust_closure_kind(
-            upvar_id.closure_expr_id,
-            ty::ClosureKind::FnOnce,
-            usage_span,
-            place_with_id.place.clone(),
-        );
+        if matches!(mode, euv::Move) {
+            // To move out of an upvar, this must be a FnOnce closure
+            self.adjust_closure_kind(
+                upvar_id.closure_expr_id,
+                ty::ClosureKind::FnOnce,
+                usage_span,
+                place.clone(),
+            );
+        }
 
         let capture_info = ty::CaptureInfo {
             capture_kind_expr_id: Some(diag_expr_id),
@@ -1317,8 +1339,12 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
         if let PlaceBase::Upvar(upvar_id) = place_with_id.place.base {
             assert_eq!(self.closure_def_id.expect_local(), upvar_id.closure_expr_id);
 
-            let capture_kind =
-                self.fcx.init_capture_kind(self.capture_clause, upvar_id, self.closure_span);
+            let capture_kind = self.fcx.init_capture_kind_for_place(
+                &place_with_id.place,
+                self.capture_clause,
+                upvar_id,
+                self.closure_span,
+            );
 
             let expr_id = Some(diag_expr_id);
             let capture_info = ty::CaptureInfo {
@@ -1392,15 +1418,10 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'a, 'tcx> {
 }
 
 /// Truncate projections so that following rules are obeyed by the captured `place`:
-///
-/// - No Derefs in move closure, this will result in value behind a reference getting moved.
 /// - No projections are applied to raw pointers, since these require unsafe blocks. We capture
 ///   them completely.
 /// - No Index projections are captured, since arrays are captured completely.
-fn restrict_capture_precision<'tcx>(
-    mut place: Place<'tcx>,
-    capture_kind: ty::UpvarCapture<'tcx>,
-) -> Place<'tcx> {
+fn restrict_capture_precision<'tcx>(mut place: Place<'tcx>) -> Place<'tcx> {
     if place.projections.is_empty() {
         // Nothing to do here
         return place;
@@ -1412,7 +1433,6 @@ fn restrict_capture_precision<'tcx>(
     }
 
     let mut truncated_length = usize::MAX;
-    let mut first_deref_projection = usize::MAX;
 
     for (i, proj) in place.projections.iter().enumerate() {
         if proj.ty.is_unsafe_ptr() {
@@ -1426,31 +1446,30 @@ fn restrict_capture_precision<'tcx>(
                 truncated_length = truncated_length.min(i);
                 break;
             }
-            ProjectionKind::Deref => {
-                // We only drop Derefs in case of move closures
-                // There might be an index projection or raw ptr ahead, so we don't stop here.
-                first_deref_projection = first_deref_projection.min(i);
-            }
+            ProjectionKind::Deref => {}
             ProjectionKind::Field(..) => {} // ignore
             ProjectionKind::Subslice => {}  // We never capture this
         }
     }
 
-    let length = place
-        .projections
-        .len()
-        .min(truncated_length)
-        // In case of capture `ByValue` we want to not capture derefs
-        .min(match capture_kind {
-            ty::UpvarCapture::ByValue(..) => first_deref_projection,
-            ty::UpvarCapture::ByRef(..) => usize::MAX,
-        });
+    let length = place.projections.len().min(truncated_length);
 
     place.projections.truncate(length);
 
     place
 }
 
+/// Truncates a place so that the resultant capture doesn't move data out of a reference
+fn truncate_capture_for_move(mut place: Place<'tcx>) -> Place<'tcx> {
+    if let Some(i) = place.projections.iter().position(|proj| proj.kind == ProjectionKind::Deref) {
+        // We only drop Derefs in case of move closures
+        // There might be an index projection or raw ptr ahead, so we don't stop here.
+        place.projections.truncate(i);
+    }
+
+    place
+}
+
 fn construct_place_string(tcx: TyCtxt<'_>, place: &Place<'tcx>) -> String {
     let variable_name = match place.base {
         PlaceBase::Upvar(upvar_id) => var_name(tcx, upvar_id.var_path.hir_id).to_string(),
diff --git a/src/test/ui/closures/2229_closure_analysis/by_value.rs b/src/test/ui/closures/2229_closure_analysis/by_value.rs
index 1007fb582e5..27c8fb1363f 100644
--- a/src/test/ui/closures/2229_closure_analysis/by_value.rs
+++ b/src/test/ui/closures/2229_closure_analysis/by_value.rs
@@ -26,7 +26,8 @@ fn big_box() {
     //~^ First Pass analysis includes:
     //~| Min Capture analysis includes:
         let p = t.0.0;
-        //~^ NOTE: Capturing t[(0, 0),Deref,(0, 0)] -> ByValue
+        //~^ NOTE: Capturing t[(0, 0),Deref,(0, 0)] -> ImmBorrow
+        //~| NOTE: Capturing t[(0, 0)] -> ByValue
         //~| NOTE: Min Capture t[(0, 0)] -> ByValue
         println!("{} {:?}", t.1, p);
         //~^ NOTE: Capturing t[(1, 0)] -> ImmBorrow
diff --git a/src/test/ui/closures/2229_closure_analysis/by_value.stderr b/src/test/ui/closures/2229_closure_analysis/by_value.stderr
index fe04dbef6d8..944e4c40a78 100644
--- a/src/test/ui/closures/2229_closure_analysis/by_value.stderr
+++ b/src/test/ui/closures/2229_closure_analysis/by_value.stderr
@@ -28,13 +28,18 @@ LL | |
 LL | |     };
    | |_____^
    |
-note: Capturing t[(0, 0),Deref,(0, 0)] -> ByValue
+note: Capturing t[(0, 0),Deref,(0, 0)] -> ImmBorrow
+  --> $DIR/by_value.rs:28:17
+   |
+LL |         let p = t.0.0;
+   |                 ^^^^^
+note: Capturing t[(0, 0)] -> ByValue
   --> $DIR/by_value.rs:28:17
    |
 LL |         let p = t.0.0;
    |                 ^^^^^
 note: Capturing t[(1, 0)] -> ImmBorrow
-  --> $DIR/by_value.rs:31:29
+  --> $DIR/by_value.rs:32:29
    |
 LL |         println!("{} {:?}", t.1, p);
    |                             ^^^
@@ -57,7 +62,7 @@ note: Min Capture t[(0, 0)] -> ByValue
 LL |         let p = t.0.0;
    |                 ^^^^^
 note: Min Capture t[(1, 0)] -> ImmBorrow
-  --> $DIR/by_value.rs:31:29
+  --> $DIR/by_value.rs:32:29
    |
 LL |         println!("{} {:?}", t.1, p);
    |                             ^^^
diff --git a/src/test/ui/closures/2229_closure_analysis/diagnostics/closure-origin-array-diagnostics.rs b/src/test/ui/closures/2229_closure_analysis/diagnostics/closure-origin-array-diagnostics.rs
new file mode 100644
index 00000000000..cd7c25620a7
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/diagnostics/closure-origin-array-diagnostics.rs
@@ -0,0 +1,16 @@
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| `#[warn(incomplete_features)]` on by default
+//~| see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+
+// Test that array access is not stored as part of closure kind origin
+
+fn expect_fn<F: Fn()>(_f: F) {}
+
+fn main() {
+    let s = [format!("s"), format!("s")];
+    let c = || { //~ ERROR expected a closure that implements the `Fn`
+        let [_, _s] = s;
+    };
+    expect_fn(c);
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/diagnostics/closure-origin-array-diagnostics.stderr b/src/test/ui/closures/2229_closure_analysis/diagnostics/closure-origin-array-diagnostics.stderr
new file mode 100644
index 00000000000..bd9428771f4
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/diagnostics/closure-origin-array-diagnostics.stderr
@@ -0,0 +1,23 @@
+warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
+  --> $DIR/closure-origin-array-diagnostics.rs:1:12
+   |
+LL | #![feature(capture_disjoint_fields)]
+   |            ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(incomplete_features)]` on by default
+   = note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
+
+error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
+  --> $DIR/closure-origin-array-diagnostics.rs:12:13
+   |
+LL |     let c = || {
+   |             ^^ this closure implements `FnOnce`, not `Fn`
+LL |         let [_, _s] = s;
+   |                       - closure is `FnOnce` because it moves the variable `s` out of its environment
+LL |     };
+LL |     expect_fn(c);
+   |     --------- the requirement to implement `Fn` derives from here
+
+error: aborting due to previous error; 1 warning emitted
+
+For more information about this error, try `rustc --explain E0525`.
diff --git a/src/test/ui/closures/2229_closure_analysis/move_closure.rs b/src/test/ui/closures/2229_closure_analysis/move_closure.rs
index 8bdc999ca3c..1c574da5f48 100644
--- a/src/test/ui/closures/2229_closure_analysis/move_closure.rs
+++ b/src/test/ui/closures/2229_closure_analysis/move_closure.rs
@@ -6,7 +6,25 @@
 //~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
 #![feature(rustc_attrs)]
 
-// Test we truncate derefs properly
+fn simple_move_closure() {
+    struct S(String);
+    struct T(S);
+
+    let t = T(S("s".into()));
+    let mut c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    move || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        t.0.0 = "new S".into();
+        //~^ NOTE: Capturing t[(0, 0),(0, 0)] -> ByValue
+        //~| NOTE: Min Capture t[(0, 0),(0, 0)] -> ByValue
+    };
+    c();
+}
+
+// Test move closure use reborrows when using references
 fn simple_ref() {
     let mut s = 10;
     let ref_s = &mut s;
@@ -18,14 +36,14 @@ fn simple_ref() {
     //~^ ERROR: First Pass analysis includes:
     //~| ERROR: Min Capture analysis includes:
         *ref_s += 10;
-        //~^ NOTE: Capturing ref_s[Deref] -> ByValue
-        //~| NOTE: Min Capture ref_s[] -> ByValue
+        //~^ NOTE: Capturing ref_s[Deref] -> UniqueImmBorrow
+        //~| NOTE: Min Capture ref_s[Deref] -> UniqueImmBorrow
     };
     c();
 }
 
-// Test we truncate derefs properly
-fn struct_contains_ref_to_another_struct() {
+// Test move closure use reborrows when using references
+fn struct_contains_ref_to_another_struct_1() {
     struct S(String);
     struct T<'a>(&'a mut S);
 
@@ -39,34 +57,85 @@ fn struct_contains_ref_to_another_struct() {
     //~^ ERROR: First Pass analysis includes:
     //~| ERROR: Min Capture analysis includes:
         t.0.0 = "new s".into();
-        //~^ NOTE: Capturing t[(0, 0),Deref,(0, 0)] -> ByValue
-        //~| NOTE: Min Capture t[(0, 0)] -> ByValue
+        //~^ NOTE: Capturing t[(0, 0),Deref,(0, 0)] -> UniqueImmBorrow
+        //~| NOTE: Min Capture t[(0, 0),Deref,(0, 0)] -> UniqueImmBorrow
     };
 
     c();
 }
 
-// Test that we don't reduce precision when there is nothing deref.
-fn no_ref() {
+// Test that we can use reborrows to read data of Copy types
+// i.e. without truncating derefs
+fn struct_contains_ref_to_another_struct_2() {
+    struct S(i32);
+    struct T<'a>(&'a S);
+
+    let s = S(0);
+    let t = T(&s);
+
+    let mut c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    move || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        let _t = t.0.0;
+        //~^ NOTE: Capturing t[(0, 0),Deref,(0, 0)] -> ImmBorrow
+        //~| NOTE: Min Capture t[(0, 0),Deref,(0, 0)] -> ImmBorrow
+    };
+
+    c();
+}
+
+// Test that we can use truncate to move out of !Copy types
+fn struct_contains_ref_to_another_struct_3() {
     struct S(String);
-    struct T(S);
+    struct T<'a>(&'a S);
+
+    let s = S("s".into());
+    let t = T(&s);
 
-    let t = T(S("s".into()));
     let mut c = #[rustc_capture_analysis]
     //~^ ERROR: attributes on expressions are experimental
     //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
     move || {
     //~^ ERROR: First Pass analysis includes:
     //~| ERROR: Min Capture analysis includes:
-        t.0.0 = "new S".into();
-        //~^ NOTE: Capturing t[(0, 0),(0, 0)] -> ByValue
-        //~| NOTE: Min Capture t[(0, 0),(0, 0)] -> ByValue
+        let _t = t.0.0;
+        //~^ NOTE: Capturing t[(0, 0),Deref,(0, 0)] -> ImmBorrow
+        //~| NOTE: Capturing t[(0, 0)] -> ByValue
+        //~| NOTE: Min Capture t[(0, 0)] -> ByValue
     };
+
+    c();
+}
+
+// Test that derefs of box are truncated in move closures
+fn truncate_box_derefs() {
+    struct S(i32);
+
+    let b = Box::new(S(10));
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    move || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        let _t = b.0;
+        //~^ NOTE: Capturing b[Deref,(0, 0)] -> ByValue
+        //~| NOTE: Capturing b[] -> ByValue
+        //~| NOTE: Min Capture b[] -> ByValue
+    };
+
     c();
 }
 
 fn main() {
+    simple_move_closure();
     simple_ref();
-    struct_contains_ref_to_another_struct();
-    no_ref();
+    struct_contains_ref_to_another_struct_1();
+    struct_contains_ref_to_another_struct_2();
+    struct_contains_ref_to_another_struct_3();
+    truncate_box_derefs();
 }
diff --git a/src/test/ui/closures/2229_closure_analysis/move_closure.stderr b/src/test/ui/closures/2229_closure_analysis/move_closure.stderr
index a745f14598e..b91ef4dd85c 100644
--- a/src/test/ui/closures/2229_closure_analysis/move_closure.stderr
+++ b/src/test/ui/closures/2229_closure_analysis/move_closure.stderr
@@ -8,7 +8,7 @@ LL |     let mut c = #[rustc_capture_analysis]
    = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
 
 error[E0658]: attributes on expressions are experimental
-  --> $DIR/move_closure.rs:35:17
+  --> $DIR/move_closure.rs:32:17
    |
 LL |     let mut c = #[rustc_capture_analysis]
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -17,7 +17,7 @@ LL |     let mut c = #[rustc_capture_analysis]
    = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
 
 error[E0658]: attributes on expressions are experimental
-  --> $DIR/move_closure.rs:55:17
+  --> $DIR/move_closure.rs:53:17
    |
 LL |     let mut c = #[rustc_capture_analysis]
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -25,6 +25,33 @@ LL |     let mut c = #[rustc_capture_analysis]
    = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
    = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
 
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/move_closure.rs:76:17
+   |
+LL |     let mut c = #[rustc_capture_analysis]
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/move_closure.rs:98:17
+   |
+LL |     let mut c = #[rustc_capture_analysis]
+   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/move_closure.rs:119:13
+   |
+LL |     let c = #[rustc_capture_analysis]
+   |             ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #15701 <https://github.com/rust-lang/rust/issues/15701> for more information
+   = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable
+
 warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
   --> $DIR/move_closure.rs:3:12
    |
@@ -40,20 +67,56 @@ error: First Pass analysis includes:
 LL | /     move || {
 LL | |
 LL | |
-LL | |         *ref_s += 10;
+LL | |         t.0.0 = "new S".into();
+LL | |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing t[(0, 0),(0, 0)] -> ByValue
+  --> $DIR/move_closure.rs:20:9
+   |
+LL |         t.0.0 = "new S".into();
+   |         ^^^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/move_closure.rs:17:5
+   |
+LL | /     move || {
+LL | |
+LL | |
+LL | |         t.0.0 = "new S".into();
 LL | |
 LL | |
 LL | |     };
    | |_____^
    |
-note: Capturing ref_s[Deref] -> ByValue
+note: Min Capture t[(0, 0),(0, 0)] -> ByValue
   --> $DIR/move_closure.rs:20:9
    |
+LL |         t.0.0 = "new S".into();
+   |         ^^^^^
+
+error: First Pass analysis includes:
+  --> $DIR/move_closure.rs:35:5
+   |
+LL | /     move || {
+LL | |
+LL | |
+LL | |         *ref_s += 10;
+LL | |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing ref_s[Deref] -> UniqueImmBorrow
+  --> $DIR/move_closure.rs:38:9
+   |
 LL |         *ref_s += 10;
    |         ^^^^^^
 
 error: Min Capture analysis includes:
-  --> $DIR/move_closure.rs:17:5
+  --> $DIR/move_closure.rs:35:5
    |
 LL | /     move || {
 LL | |
@@ -64,14 +127,14 @@ LL | |
 LL | |     };
    | |_____^
    |
-note: Min Capture ref_s[] -> ByValue
-  --> $DIR/move_closure.rs:20:9
+note: Min Capture ref_s[Deref] -> UniqueImmBorrow
+  --> $DIR/move_closure.rs:38:9
    |
 LL |         *ref_s += 10;
    |         ^^^^^^
 
 error: First Pass analysis includes:
-  --> $DIR/move_closure.rs:38:5
+  --> $DIR/move_closure.rs:56:5
    |
 LL | /     move || {
 LL | |
@@ -82,14 +145,14 @@ LL | |
 LL | |     };
    | |_____^
    |
-note: Capturing t[(0, 0),Deref,(0, 0)] -> ByValue
-  --> $DIR/move_closure.rs:41:9
+note: Capturing t[(0, 0),Deref,(0, 0)] -> UniqueImmBorrow
+  --> $DIR/move_closure.rs:59:9
    |
 LL |         t.0.0 = "new s".into();
    |         ^^^^^
 
 error: Min Capture analysis includes:
-  --> $DIR/move_closure.rs:38:5
+  --> $DIR/move_closure.rs:56:5
    |
 LL | /     move || {
 LL | |
@@ -100,48 +163,130 @@ LL | |
 LL | |     };
    | |_____^
    |
-note: Min Capture t[(0, 0)] -> ByValue
-  --> $DIR/move_closure.rs:41:9
+note: Min Capture t[(0, 0),Deref,(0, 0)] -> UniqueImmBorrow
+  --> $DIR/move_closure.rs:59:9
    |
 LL |         t.0.0 = "new s".into();
    |         ^^^^^
 
 error: First Pass analysis includes:
-  --> $DIR/move_closure.rs:58:5
+  --> $DIR/move_closure.rs:79:5
    |
 LL | /     move || {
 LL | |
 LL | |
-LL | |         t.0.0 = "new S".into();
+LL | |         let _t = t.0.0;
 LL | |
 LL | |
 LL | |     };
    | |_____^
    |
-note: Capturing t[(0, 0),(0, 0)] -> ByValue
-  --> $DIR/move_closure.rs:61:9
+note: Capturing t[(0, 0),Deref,(0, 0)] -> ImmBorrow
+  --> $DIR/move_closure.rs:82:18
    |
-LL |         t.0.0 = "new S".into();
-   |         ^^^^^
+LL |         let _t = t.0.0;
+   |                  ^^^^^
 
 error: Min Capture analysis includes:
-  --> $DIR/move_closure.rs:58:5
+  --> $DIR/move_closure.rs:79:5
    |
 LL | /     move || {
 LL | |
 LL | |
-LL | |         t.0.0 = "new S".into();
+LL | |         let _t = t.0.0;
 LL | |
 LL | |
 LL | |     };
    | |_____^
    |
-note: Min Capture t[(0, 0),(0, 0)] -> ByValue
-  --> $DIR/move_closure.rs:61:9
+note: Min Capture t[(0, 0),Deref,(0, 0)] -> ImmBorrow
+  --> $DIR/move_closure.rs:82:18
    |
-LL |         t.0.0 = "new S".into();
-   |         ^^^^^
+LL |         let _t = t.0.0;
+   |                  ^^^^^
+
+error: First Pass analysis includes:
+  --> $DIR/move_closure.rs:101:5
+   |
+LL | /     move || {
+LL | |
+LL | |
+LL | |         let _t = t.0.0;
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing t[(0, 0),Deref,(0, 0)] -> ImmBorrow
+  --> $DIR/move_closure.rs:104:18
+   |
+LL |         let _t = t.0.0;
+   |                  ^^^^^
+note: Capturing t[(0, 0)] -> ByValue
+  --> $DIR/move_closure.rs:104:18
+   |
+LL |         let _t = t.0.0;
+   |                  ^^^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/move_closure.rs:101:5
+   |
+LL | /     move || {
+LL | |
+LL | |
+LL | |         let _t = t.0.0;
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture t[(0, 0)] -> ByValue
+  --> $DIR/move_closure.rs:104:18
+   |
+LL |         let _t = t.0.0;
+   |                  ^^^^^
+
+error: First Pass analysis includes:
+  --> $DIR/move_closure.rs:122:5
+   |
+LL | /     move || {
+LL | |
+LL | |
+LL | |         let _t = b.0;
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing b[Deref,(0, 0)] -> ByValue
+  --> $DIR/move_closure.rs:125:18
+   |
+LL |         let _t = b.0;
+   |                  ^^^
+note: Capturing b[] -> ByValue
+  --> $DIR/move_closure.rs:125:18
+   |
+LL |         let _t = b.0;
+   |                  ^^^
+
+error: Min Capture analysis includes:
+  --> $DIR/move_closure.rs:122:5
+   |
+LL | /     move || {
+LL | |
+LL | |
+LL | |         let _t = b.0;
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture b[] -> ByValue
+  --> $DIR/move_closure.rs:125:18
+   |
+LL |         let _t = b.0;
+   |                  ^^^
 
-error: aborting due to 9 previous errors; 1 warning emitted
+error: aborting due to 18 previous errors; 1 warning emitted
 
 For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/closures/2229_closure_analysis/run_pass/move_closure.rs b/src/test/ui/closures/2229_closure_analysis/run_pass/move_closure.rs
index 4007a5a48aa..afaafbda018 100644
--- a/src/test/ui/closures/2229_closure_analysis/run_pass/move_closure.rs
+++ b/src/test/ui/closures/2229_closure_analysis/run_pass/move_closure.rs
@@ -56,9 +56,50 @@ fn no_ref_nested() {
     c();
 }
 
+struct A<'a>(&'a mut String,  &'a mut String);
+// Test that reborrowing works as expected for move closures
+// by attempting a disjoint capture through a reference.
+fn disjoint_via_ref() {
+    let mut x = String::new();
+    let mut y = String::new();
+
+    let mut a = A(&mut x, &mut y);
+    let a = &mut a;
+
+    let mut c1 = move || {
+        a.0.truncate(0);
+    };
+
+    let mut c2 = move || {
+        a.1.truncate(0);
+    };
+
+    c1();
+    c2();
+}
+
+// Test that even if a path is moved into the closure, the closure is not FnOnce
+// if the path is not moved by the closure call.
+fn data_moved_but_not_fn_once() {
+    let x = Box::new(10i32);
+
+    let c = move || {
+        // *x has type i32 which is Copy. So even though the box `x` will be moved
+        // into the closure, `x` is never moved when the closure is called, i.e. the
+        // ownership stays with the closure and therefore we can call the function multiple times.
+        let _x = *x;
+    };
+
+    c();
+    c();
+}
+
 fn main() {
     simple_ref();
     struct_contains_ref_to_another_struct();
     no_ref();
     no_ref_nested();
+
+    disjoint_via_ref();
+    data_moved_but_not_fn_once();
 }