about summary refs log tree commit diff
diff options
context:
space:
mode:
authorChris Pardy <chrispardy36@gmail.com>2020-11-22 20:03:42 -0500
committerChris Pardy <chrispardy36@gmail.com>2021-01-15 19:39:24 -0500
commit2624b3e443b6a09c8338c92c09bc8d8b41fa5cb4 (patch)
tree4c44cb23d6a9ebc3092c4f10bb718a6610bc7b89
parentfcbd305ee93f49f19313b9bbeaa25ba8837030d9 (diff)
downloadrust-2624b3e443b6a09c8338c92c09bc8d8b41fa5cb4.tar.gz
rust-2624b3e443b6a09c8338c92c09bc8d8b41fa5cb4.zip
Improve diagnostics for Precise Capture
-rw-r--r--compiler/rustc_middle/src/ty/mod.rs21
-rw-r--r--compiler/rustc_typeck/src/check/upvar.rs149
-rw-r--r--compiler/rustc_typeck/src/check/writeback.rs2
-rw-r--r--compiler/rustc_typeck/src/expr_use_visitor.rs2
-rw-r--r--src/test/ui/closures/2229_closure_analysis/capture-analysis-1.rs36
-rw-r--r--src/test/ui/closures/2229_closure_analysis/capture-analysis-1.stderr77
-rw-r--r--src/test/ui/closures/2229_closure_analysis/capture-analysis-2.rs31
-rw-r--r--src/test/ui/closures/2229_closure_analysis/capture-analysis-2.stderr65
-rw-r--r--src/test/ui/closures/2229_closure_analysis/deep-multilevel-struct.rs52
-rw-r--r--src/test/ui/closures/2229_closure_analysis/deep-multilevel-struct.stderr70
-rw-r--r--src/test/ui/closures/2229_closure_analysis/deep-multilevel-tuple.rs28
-rw-r--r--src/test/ui/closures/2229_closure_analysis/deep-multilevel-tuple.stderr70
-rw-r--r--src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.rs4
-rw-r--r--src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.stderr5
14 files changed, 581 insertions, 31 deletions
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index 1399fc76e02..82f63528721 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -741,8 +741,20 @@ pub struct CapturedPlace<'tcx> {
 pub struct CaptureInfo<'tcx> {
     /// Expr Id pointing to use that resulted in selecting the current capture kind
     ///
+    /// Eg:
+    /// ```rust,no_run
+    /// let mut t = (0,1);
+    ///
+    /// let c = || {
+    ///     println!("{}",t); // L1
+    ///     t.1 = 4; // L2
+    /// };
+    /// ```
+    /// `capture_kind_expr_id` will point to the use on L2 and `path_expr_id` will point to the
+    /// use on L1.
+    ///
     /// If the user doesn't enable feature `capture_disjoint_fields` (RFC 2229) then, it is
-    /// possible that we don't see the use of a particular place resulting in expr_id being
+    /// possible that we don't see the use of a particular place resulting in capture_kind_expr_id being
     /// None. In such case we fallback on uvpars_mentioned for span.
     ///
     /// Eg:
@@ -756,7 +768,12 @@ pub struct CaptureInfo<'tcx> {
     ///
     /// In this example, if `capture_disjoint_fields` is **not** set, then x will be captured,
     /// but we won't see it being used during capture analysis, since it's essentially a discard.
-    pub expr_id: Option<hir::HirId>,
+    pub capture_kind_expr_id: Option<hir::HirId>,
+    /// Expr Id pointing to use that resulted the corresponding place being captured
+    ///
+    /// See `capture_kind_expr_id` for example.
+    ///
+    pub path_expr_id: Option<hir::HirId>,
 
     /// Capture mode that was selected
     pub capture_kind: UpvarCapture<'tcx>,
diff --git a/compiler/rustc_typeck/src/check/upvar.rs b/compiler/rustc_typeck/src/check/upvar.rs
index e8cbefd44ee..44652864f6e 100644
--- a/compiler/rustc_typeck/src/check/upvar.rs
+++ b/compiler/rustc_typeck/src/check/upvar.rs
@@ -42,7 +42,7 @@ use rustc_infer::infer::UpvarRegion;
 use rustc_middle::hir::place::{Place, PlaceBase, PlaceWithHirId, ProjectionKind};
 use rustc_middle::ty::{self, Ty, TyCtxt, UpvarSubsts};
 use rustc_span::sym;
-use rustc_span::{Span, Symbol};
+use rustc_span::{MultiSpan, Span, Symbol};
 
 /// Describe the relationship between the paths of two places
 /// eg:
@@ -135,7 +135,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
                     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 info = ty::CaptureInfo { expr_id: None, capture_kind };
+                    let info = ty::CaptureInfo {
+                        capture_kind_expr_id: None,
+                        path_expr_id: None,
+                        capture_kind,
+                    };
 
                     capture_information.insert(place, info);
                 }
@@ -298,8 +302,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         if let Some(capture_kind) = upvar_capture_map.get(&upvar_id) {
                             // upvar_capture_map only stores the UpvarCapture (CaptureKind),
                             // so we create a fake capture info with no expression.
-                            let fake_capture_info =
-                                ty::CaptureInfo { expr_id: None, capture_kind: *capture_kind };
+                            let fake_capture_info = ty::CaptureInfo {
+                                capture_kind_expr_id: None,
+                                path_expr_id: None,
+                                capture_kind: *capture_kind,
+                            };
                             determine_capture_info(fake_capture_info, capture_info).capture_kind
                         } else {
                             capture_info.capture_kind
@@ -349,20 +356,44 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     ///
     /// ```
     /// {
-    ///       Place(base: hir_id_s, projections: [], ....) -> (hir_id_L5, ByValue),
-    ///       Place(base: hir_id_p, projections: [Field(0, 0)], ...) -> (hir_id_L2, ByRef(MutBorrow))
-    ///       Place(base: hir_id_p, projections: [Field(1, 0)], ...) -> (hir_id_L3, ByRef(ImmutBorrow))
-    ///       Place(base: hir_id_p, projections: [], ...) -> (hir_id_L4, ByRef(ImmutBorrow))
+    ///       Place(base: hir_id_s, projections: [], ....) -> {
+    ///                                                            capture_kind_expr: hir_id_L5,
+    ///                                                            path_expr_id: hir_id_L5,
+    ///                                                            capture_kind: ByValue
+    ///                                                       },
+    ///       Place(base: hir_id_p, projections: [Field(0, 0)], ...) -> {
+    ///                                                                     capture_kind_expr: hir_id_L2,
+    ///                                                                     path_expr_id: hir_id_L2,
+    ///                                                                     capture_kind: ByValue
+    ///                                                                 },
+    ///       Place(base: hir_id_p, projections: [Field(1, 0)], ...) -> {
+    ///                                                                     capture_kind_expr: hir_id_L3,
+    ///                                                                     path_expr_id: hir_id_L3,
+    ///                                                                     capture_kind: ByValue
+    ///                                                                 },
+    ///       Place(base: hir_id_p, projections: [], ...) -> {
+    ///                                                          capture_kind_expr: hir_id_L4,
+    ///                                                          path_expr_id: hir_id_L4,
+    ///                                                          capture_kind: ByValue
+    ///                                                      },
     /// ```
     ///
     /// After the min capture analysis, we get:
     /// ```
     /// {
     ///       hir_id_s -> [
-    ///            Place(base: hir_id_s, projections: [], ....) -> (hir_id_L4, ByValue)
+    ///            Place(base: hir_id_s, projections: [], ....) -> {
+    ///                                                                capture_kind_expr: hir_id_L5,
+    ///                                                                path_expr_id: hir_id_L5,
+    ///                                                                capture_kind: ByValue
+    ///                                                            },
     ///       ],
     ///       hir_id_p -> [
-    ///            Place(base: hir_id_p, projections: [], ...) -> (hir_id_L2, ByRef(MutBorrow)),
+    ///            Place(base: hir_id_p, projections: [], ...) -> {
+    ///                                                               capture_kind_expr: hir_id_L2,
+    ///                                                               path_expr_id: hir_id_L4,
+    ///                                                               capture_kind: ByValue
+    ///                                                           },
     ///       ],
     /// ```
     fn compute_min_captures(
@@ -415,8 +446,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     // current place is ancestor of possible_descendant
                     PlaceAncestryRelation::Ancestor => {
                         descendant_found = true;
+                        let backup_path_expr_id = updated_capture_info.path_expr_id;
+
                         updated_capture_info =
                             determine_capture_info(updated_capture_info, possible_descendant.info);
+
+                        // we need to keep the ancestor's `path_expr_id`
+                        updated_capture_info.path_expr_id = backup_path_expr_id;
                         false
                     }
 
@@ -431,9 +467,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         // current place is descendant of possible_ancestor
                         PlaceAncestryRelation::Descendant => {
                             ancestor_found = true;
+                            let backup_path_expr_id = possible_ancestor.info.path_expr_id;
                             possible_ancestor.info =
                                 determine_capture_info(possible_ancestor.info, capture_info);
 
+                            // we need to keep the ancestor's `path_expr_id`
+                            possible_ancestor.info.path_expr_id = backup_path_expr_id;
+
                             // Only one ancestor of the current place will be in the list.
                             break;
                         }
@@ -508,7 +548,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 let capture_str = construct_capture_info_string(self.tcx, place, capture_info);
                 let output_str = format!("Capturing {}", capture_str);
 
-                let span = capture_info.expr_id.map_or(closure_span, |e| self.tcx.hir().span(e));
+                let span =
+                    capture_info.path_expr_id.map_or(closure_span, |e| self.tcx.hir().span(e));
                 diag.span_note(span, &output_str);
             }
             diag.emit();
@@ -532,9 +573,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                             construct_capture_info_string(self.tcx, place, capture_info);
                         let output_str = format!("Min Capture {}", capture_str);
 
-                        let span =
-                            capture_info.expr_id.map_or(closure_span, |e| self.tcx.hir().span(e));
-                        diag.span_note(span, &output_str);
+                        if capture.info.path_expr_id != capture.info.capture_kind_expr_id {
+                            let path_span = capture_info
+                                .path_expr_id
+                                .map_or(closure_span, |e| self.tcx.hir().span(e));
+                            let capture_kind_span = capture_info
+                                .capture_kind_expr_id
+                                .map_or(closure_span, |e| self.tcx.hir().span(e));
+
+                            let mut multi_span: MultiSpan =
+                                MultiSpan::from_spans(vec![path_span, capture_kind_span]);
+
+                            let capture_kind_label =
+                                construct_capture_kind_reason_string(self.tcx, place, capture_info);
+                            let path_label = construct_path_string(self.tcx, place);
+
+                            multi_span.push_span_label(path_span, path_label);
+                            multi_span.push_span_label(capture_kind_span, capture_kind_label);
+
+                            diag.span_note(multi_span, &output_str);
+                        } else {
+                            let span = capture_info
+                                .path_expr_id
+                                .map_or(closure_span, |e| self.tcx.hir().span(e));
+
+                            diag.span_note(span, &output_str);
+                        };
                     }
                 }
                 diag.emit();
@@ -632,7 +696,8 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
         );
 
         let capture_info = ty::CaptureInfo {
-            expr_id: Some(diag_expr_id),
+            capture_kind_expr_id: Some(diag_expr_id),
+            path_expr_id: Some(diag_expr_id),
             capture_kind: ty::UpvarCapture::ByValue(Some(usage_span)),
         };
 
@@ -752,7 +817,8 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
             let new_upvar_borrow = ty::UpvarBorrow { kind, region: curr_upvar_borrow.region };
 
             let capture_info = ty::CaptureInfo {
-                expr_id: Some(diag_expr_id),
+                capture_kind_expr_id: Some(diag_expr_id),
+                path_expr_id: Some(diag_expr_id),
                 capture_kind: ty::UpvarCapture::ByRef(new_upvar_borrow),
             };
             let updated_info = determine_capture_info(curr_capture_info, capture_info);
@@ -814,7 +880,11 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
                 self.fcx.init_capture_kind(self.capture_clause, upvar_id, self.closure_span);
 
             let expr_id = Some(diag_expr_id);
-            let capture_info = ty::CaptureInfo { expr_id, capture_kind };
+            let capture_info = ty::CaptureInfo {
+                capture_kind_expr_id: expr_id,
+                path_expr_id: expr_id,
+                capture_kind,
+            };
 
             debug!("Capturing new place {:?}, capture_info={:?}", place_with_id, capture_info);
 
@@ -880,11 +950,7 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'a, 'tcx> {
     }
 }
 
-fn construct_capture_info_string(
-    tcx: TyCtxt<'_>,
-    place: &Place<'tcx>,
-    capture_info: &ty::CaptureInfo<'tcx>,
-) -> String {
+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(),
         _ => bug!("Capture_information should only contain upvars"),
@@ -904,11 +970,42 @@ fn construct_capture_info_string(
         projections_str.push_str(proj.as_str());
     }
 
+    format!("{}[{}]", variable_name, projections_str)
+}
+
+fn construct_capture_kind_reason_string(
+    tcx: TyCtxt<'_>,
+    place: &Place<'tcx>,
+    capture_info: &ty::CaptureInfo<'tcx>,
+) -> String {
+    let place_str = construct_place_string(tcx, &place);
+
     let capture_kind_str = match capture_info.capture_kind {
         ty::UpvarCapture::ByValue(_) => "ByValue".into(),
         ty::UpvarCapture::ByRef(borrow) => format!("{:?}", borrow.kind),
     };
-    format!("{}[{}] -> {}", variable_name, projections_str, capture_kind_str)
+
+    format!("{} captured as {} here", place_str, capture_kind_str)
+}
+
+fn construct_path_string(tcx: TyCtxt<'_>, place: &Place<'tcx>) -> String {
+    let place_str = construct_place_string(tcx, &place);
+
+    format!("{} used here", place_str)
+}
+
+fn construct_capture_info_string(
+    tcx: TyCtxt<'_>,
+    place: &Place<'tcx>,
+    capture_info: &ty::CaptureInfo<'tcx>,
+) -> String {
+    let place_str = construct_place_string(tcx, &place);
+
+    let capture_kind_str = match capture_info.capture_kind {
+        ty::UpvarCapture::ByValue(_) => "ByValue".into(),
+        ty::UpvarCapture::ByRef(borrow) => format!("{:?}", borrow.kind),
+    };
+    format!("{} -> {}", place_str, capture_kind_str)
 }
 
 fn var_name(tcx: TyCtxt<'_>, var_hir_id: hir::HirId) -> Symbol {
@@ -920,7 +1017,9 @@ fn var_name(tcx: TyCtxt<'_>, var_hir_id: hir::HirId) -> Symbol {
 /// (Note: CaptureInfo contains CaptureKind and an expression that led to capture it in that way)
 ///
 /// If both `CaptureKind`s are considered equivalent, then the CaptureInfo is selected based
-/// on the `CaptureInfo` containing an associated expression id.
+/// on the `CaptureInfo` containing an associated `capture_kind_expr_id`.
+///
+/// It is the caller's duty to figure out which path_expr_id to use.
 ///
 /// If both the CaptureKind and Expression are considered to be equivalent,
 /// then `CaptureInfo` A is preferred. This can be useful in cases where we want to priortize
@@ -971,7 +1070,7 @@ fn determine_capture_info(
     };
 
     if eq_capture_kind {
-        match (capture_info_a.expr_id, capture_info_b.expr_id) {
+        match (capture_info_a.capture_kind_expr_id, capture_info_b.capture_kind_expr_id) {
             (Some(_), _) | (None, None) => capture_info_a,
             (None, Some(_)) => capture_info_b,
         }
diff --git a/compiler/rustc_typeck/src/check/writeback.rs b/compiler/rustc_typeck/src/check/writeback.rs
index 7c9cfe69fc9..3612da1ffa3 100644
--- a/compiler/rustc_typeck/src/check/writeback.rs
+++ b/compiler/rustc_typeck/src/check/writeback.rs
@@ -348,7 +348,7 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
                 let min_list_wb = min_list
                     .iter()
                     .map(|captured_place| {
-                        let locatable = captured_place.info.expr_id.unwrap_or(
+                        let locatable = captured_place.info.path_expr_id.unwrap_or(
                             self.tcx().hir().local_def_id_to_hir_id(closure_def_id.expect_local()),
                         );
 
diff --git a/compiler/rustc_typeck/src/expr_use_visitor.rs b/compiler/rustc_typeck/src/expr_use_visitor.rs
index 01519e4c8f7..b6f77aedc16 100644
--- a/compiler/rustc_typeck/src/expr_use_visitor.rs
+++ b/compiler/rustc_typeck/src/expr_use_visitor.rs
@@ -630,7 +630,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
                         PlaceBase::Local(*var_hir_id)
                     };
                     let place_with_id = PlaceWithHirId::new(
-                        capture_info.expr_id.unwrap_or(closure_expr.hir_id),
+                        capture_info.path_expr_id.unwrap_or(closure_expr.hir_id),
                         place.base_ty,
                         place_base,
                         place.projections.clone(),
diff --git a/src/test/ui/closures/2229_closure_analysis/capture-analysis-1.rs b/src/test/ui/closures/2229_closure_analysis/capture-analysis-1.rs
new file mode 100644
index 00000000000..fbcba6fc200
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/capture-analysis-1.rs
@@ -0,0 +1,36 @@
+
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+
+#[derive(Debug)]
+struct Point {
+    x: i32,
+    y: i32,
+}
+
+fn main() {
+    let p = Point { x: 10, y: 10 };
+    let q = Point { x: 10, y: 10 };
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ First Pass analysis includes:
+    //~| Min Capture analysis includes:
+        println!("{:?}", p);
+        //~^ NOTE: Capturing p[] -> ImmBorrow
+        //~| NOTE: Min Capture p[] -> ImmBorrow
+        println!("{:?}", p.x);
+        //~^ NOTE: Capturing p[(0, 0)] -> ImmBorrow
+
+        println!("{:?}", q.x);
+        //~^ NOTE: Capturing q[(0, 0)] -> ImmBorrow
+        println!("{:?}", q);
+        //~^ NOTE: Capturing q[] -> ImmBorrow
+        //~| NOTE: Min Capture q[] -> ImmBorrow
+    };
+}
\ No newline at end of file
diff --git a/src/test/ui/closures/2229_closure_analysis/capture-analysis-1.stderr b/src/test/ui/closures/2229_closure_analysis/capture-analysis-1.stderr
new file mode 100644
index 00000000000..f15d656be64
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/capture-analysis-1.stderr
@@ -0,0 +1,77 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/capture-analysis-1.rs:18: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/capture-analysis-1.rs:2: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: First Pass analysis includes:
+  --> $DIR/capture-analysis-1.rs:21:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         println!("{:?}", p);
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing p[] -> ImmBorrow
+  --> $DIR/capture-analysis-1.rs:24:26
+   |
+LL |         println!("{:?}", p);
+   |                          ^
+note: Capturing p[(0, 0)] -> ImmBorrow
+  --> $DIR/capture-analysis-1.rs:27:26
+   |
+LL |         println!("{:?}", p.x);
+   |                          ^^^
+note: Capturing q[(0, 0)] -> ImmBorrow
+  --> $DIR/capture-analysis-1.rs:30:26
+   |
+LL |         println!("{:?}", q.x);
+   |                          ^^^
+note: Capturing q[] -> ImmBorrow
+  --> $DIR/capture-analysis-1.rs:32:26
+   |
+LL |         println!("{:?}", q);
+   |                          ^
+
+error: Min Capture analysis includes:
+  --> $DIR/capture-analysis-1.rs:21:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         println!("{:?}", p);
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture p[] -> ImmBorrow
+  --> $DIR/capture-analysis-1.rs:24:26
+   |
+LL |         println!("{:?}", p);
+   |                          ^
+note: Min Capture q[] -> ImmBorrow
+  --> $DIR/capture-analysis-1.rs:32:26
+   |
+LL |         println!("{:?}", q);
+   |                          ^
+
+error: aborting due to 3 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/capture-analysis-2.rs b/src/test/ui/closures/2229_closure_analysis/capture-analysis-2.rs
new file mode 100644
index 00000000000..7758dac4a56
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/capture-analysis-2.rs
@@ -0,0 +1,31 @@
+
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+
+#[derive(Debug)]
+struct Point {
+    x: String,
+    y: i32,
+}
+
+fn main() {
+    let mut p = Point { x: String::new(), y: 10 };
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ First Pass analysis includes:
+    //~| Min Capture analysis includes:
+        let _x = p.x;
+        //~^ NOTE: Capturing p[(0, 0)] -> ByValue
+        //~| NOTE: p[] captured as ByValue here
+        println!("{:?}", p);
+        //~^ NOTE: Capturing p[] -> ImmBorrow
+        //~| NOTE: Min Capture p[] -> ByValue
+        //~| NOTE: p[] used here
+    };
+}
\ No newline at end of file
diff --git a/src/test/ui/closures/2229_closure_analysis/capture-analysis-2.stderr b/src/test/ui/closures/2229_closure_analysis/capture-analysis-2.stderr
new file mode 100644
index 00000000000..2805476d8d2
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/capture-analysis-2.stderr
@@ -0,0 +1,65 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/capture-analysis-2.rs:17: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/capture-analysis-2.rs:2: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: First Pass analysis includes:
+  --> $DIR/capture-analysis-2.rs:20:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let _x = p.x;
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing p[(0, 0)] -> ByValue
+  --> $DIR/capture-analysis-2.rs:23:18
+   |
+LL |         let _x = p.x;
+   |                  ^^^
+note: Capturing p[] -> ImmBorrow
+  --> $DIR/capture-analysis-2.rs:26:26
+   |
+LL |         println!("{:?}", p);
+   |                          ^
+
+error: Min Capture analysis includes:
+  --> $DIR/capture-analysis-2.rs:20:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let _x = p.x;
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture p[] -> ByValue
+  --> $DIR/capture-analysis-2.rs:23:18
+   |
+LL |         let _x = p.x;
+   |                  ^^^ p[] captured as ByValue here
+...
+LL |         println!("{:?}", p);
+   |                          ^ p[] used here
+
+error: aborting due to 3 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/deep-multilevel-struct.rs b/src/test/ui/closures/2229_closure_analysis/deep-multilevel-struct.rs
new file mode 100644
index 00000000000..f81866bb7e0
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/deep-multilevel-struct.rs
@@ -0,0 +1,52 @@
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+#![allow(unused)]
+
+#[derive(Debug)]
+struct Point {
+    x: i32,
+    y: i32,
+}
+#[derive(Debug)]
+struct Line {
+    p: Point,
+    q: Point
+}
+#[derive(Debug)]
+struct Plane {
+    a: Line,
+    b: Line,
+}
+
+fn main() {
+    let mut p = Plane {
+        a: Line {
+            p: Point { x: 1,y: 2 },
+            q: Point { x: 3,y: 4 },
+        },
+        b: Line {
+            p: Point { x: 1,y: 2 },
+            q: Point { x: 3,y: 4 },
+        }
+    };
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        let x = &p.a.p.x;
+        //~^ NOTE: Capturing p[(0, 0),(0, 0),(0, 0)] -> ImmBorrow
+        p.b.q.y = 9;
+        //~^ NOTE: Capturing p[(1, 0),(1, 0),(1, 0)] -> MutBorrow
+        //~| NOTE: p[] captured as MutBorrow here
+        println!("{:?}", p);
+        //~^ NOTE: Capturing p[] -> ImmBorrow
+        //~| NOTE: Min Capture p[] -> MutBorrow
+        //~| NOTE: p[] used here
+    };
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/deep-multilevel-struct.stderr b/src/test/ui/closures/2229_closure_analysis/deep-multilevel-struct.stderr
new file mode 100644
index 00000000000..863f1009131
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/deep-multilevel-struct.stderr
@@ -0,0 +1,70 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/deep-multilevel-struct.rs:36: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/deep-multilevel-struct.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: First Pass analysis includes:
+  --> $DIR/deep-multilevel-struct.rs:39:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let x = &p.a.p.x;
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing p[(0, 0),(0, 0),(0, 0)] -> ImmBorrow
+  --> $DIR/deep-multilevel-struct.rs:42:18
+   |
+LL |         let x = &p.a.p.x;
+   |                  ^^^^^^^
+note: Capturing p[(1, 0),(1, 0),(1, 0)] -> MutBorrow
+  --> $DIR/deep-multilevel-struct.rs:44:9
+   |
+LL |         p.b.q.y = 9;
+   |         ^^^^^^^
+note: Capturing p[] -> ImmBorrow
+  --> $DIR/deep-multilevel-struct.rs:47:26
+   |
+LL |         println!("{:?}", p);
+   |                          ^
+
+error: Min Capture analysis includes:
+  --> $DIR/deep-multilevel-struct.rs:39:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let x = &p.a.p.x;
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture p[] -> MutBorrow
+  --> $DIR/deep-multilevel-struct.rs:44:9
+   |
+LL |         p.b.q.y = 9;
+   |         ^^^^^^^ p[] captured as MutBorrow here
+...
+LL |         println!("{:?}", p);
+   |                          ^ p[] used here
+
+error: aborting due to 3 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/deep-multilevel-tuple.rs b/src/test/ui/closures/2229_closure_analysis/deep-multilevel-tuple.rs
new file mode 100644
index 00000000000..8f53b2b8aa0
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/deep-multilevel-tuple.rs
@@ -0,0 +1,28 @@
+
+#![feature(capture_disjoint_fields)]
+//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
+//~| NOTE: `#[warn(incomplete_features)]` on by default
+//~| NOTE: see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
+#![feature(rustc_attrs)]
+#![allow(unused)]
+
+fn main() {
+    let mut t = (((1,2),(3,4)),((5,6),(7,8)));
+
+    let c = #[rustc_capture_analysis]
+    //~^ ERROR: attributes on expressions are experimental
+    //~| NOTE: see issue #15701 <https://github.com/rust-lang/rust/issues/15701>
+    || {
+    //~^ ERROR: First Pass analysis includes:
+    //~| ERROR: Min Capture analysis includes:
+        let x = &t.0.0.0;
+        //~^ NOTE: Capturing t[(0, 0),(0, 0),(0, 0)] -> ImmBorrow
+        t.1.1.1 = 9;
+        //~^ NOTE: Capturing t[(1, 0),(1, 0),(1, 0)] -> MutBorrow
+        //~| NOTE: t[] captured as MutBorrow here
+        println!("{:?}", t);
+        //~^ NOTE: Min Capture t[] -> MutBorrow
+        //~| NOTE: Capturing t[] -> ImmBorrow
+        //~| NOTE: t[] used here
+    };
+}
diff --git a/src/test/ui/closures/2229_closure_analysis/deep-multilevel-tuple.stderr b/src/test/ui/closures/2229_closure_analysis/deep-multilevel-tuple.stderr
new file mode 100644
index 00000000000..8fce18f3d60
--- /dev/null
+++ b/src/test/ui/closures/2229_closure_analysis/deep-multilevel-tuple.stderr
@@ -0,0 +1,70 @@
+error[E0658]: attributes on expressions are experimental
+  --> $DIR/deep-multilevel-tuple.rs:12: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/deep-multilevel-tuple.rs:2: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: First Pass analysis includes:
+  --> $DIR/deep-multilevel-tuple.rs:15:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let x = &t.0.0.0;
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Capturing t[(0, 0),(0, 0),(0, 0)] -> ImmBorrow
+  --> $DIR/deep-multilevel-tuple.rs:18:18
+   |
+LL |         let x = &t.0.0.0;
+   |                  ^^^^^^^
+note: Capturing t[(1, 0),(1, 0),(1, 0)] -> MutBorrow
+  --> $DIR/deep-multilevel-tuple.rs:20:9
+   |
+LL |         t.1.1.1 = 9;
+   |         ^^^^^^^
+note: Capturing t[] -> ImmBorrow
+  --> $DIR/deep-multilevel-tuple.rs:23:26
+   |
+LL |         println!("{:?}", t);
+   |                          ^
+
+error: Min Capture analysis includes:
+  --> $DIR/deep-multilevel-tuple.rs:15:5
+   |
+LL | /     || {
+LL | |
+LL | |
+LL | |         let x = &t.0.0.0;
+...  |
+LL | |
+LL | |     };
+   | |_____^
+   |
+note: Min Capture t[] -> MutBorrow
+  --> $DIR/deep-multilevel-tuple.rs:20:9
+   |
+LL |         t.1.1.1 = 9;
+   |         ^^^^^^^ t[] captured as MutBorrow here
+...
+LL |         println!("{:?}", t);
+   |                          ^ t[] used here
+
+error: aborting due to 3 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/simple-struct-min-capture.rs b/src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.rs
index aaff3531e58..a6b5e12d2ed 100644
--- a/src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.rs
+++ b/src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.rs
@@ -32,9 +32,11 @@ fn main() {
     //~| ERROR: Min Capture analysis includes:
         p.x += 10;
         //~^ NOTE: Capturing p[(0, 0)] -> MutBorrow
-        //~| NOTE: Min Capture p[] -> MutBorrow
+        //~| NOTE: p[] captured as MutBorrow here
         println!("{:?}", p);
         //~^ NOTE: Capturing p[] -> ImmBorrow
+        //~| NOTE: Min Capture p[] -> MutBorrow
+        //~| NOTE: p[] used here
     };
 
     c();
diff --git a/src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.stderr b/src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.stderr
index 30d3d5f504e..cbbc8792199 100644
--- a/src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.stderr
+++ b/src/test/ui/closures/2229_closure_analysis/simple-struct-min-capture.stderr
@@ -55,7 +55,10 @@ note: Min Capture p[] -> MutBorrow
   --> $DIR/simple-struct-min-capture.rs:33:9
    |
 LL |         p.x += 10;
-   |         ^^^
+   |         ^^^ p[] captured as MutBorrow here
+...
+LL |         println!("{:?}", p);
+   |                          ^ p[] used here
 
 error: aborting due to 3 previous errors; 1 warning emitted