Skip to content

Commit

Permalink
Rollup merge of rust-lang#82007 - sexxi-goose:reborrow, r=nikomatsakis
Browse files Browse the repository at this point in the history
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: rust-lang/project-rfc-2229#31

r? ````@nikomatsakis````
  • Loading branch information
Dylan-DPC committed Feb 17, 2021
2 parents cdd93fd + f99e152 commit 0b2f2b9
Show file tree
Hide file tree
Showing 8 changed files with 408 additions and 89 deletions.
105 changes: 62 additions & 43 deletions compiler/rustc_typeck/src/check/upvar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 })
};

Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -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;
Expand All @@ -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),
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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() {
Expand All @@ -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(),
Expand Down
3 changes: 2 additions & 1 deletion src/test/ui/closures/2229_closure_analysis/by_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions src/test/ui/closures/2229_closure_analysis/by_value.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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);
| ^^^
Expand All @@ -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);
| ^^^
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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`.
Loading

0 comments on commit 0b2f2b9

Please sign in to comment.