Skip to content

Commit 7e8d0c6

Browse files
authored
Rollup merge of rust-lang#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: rust-lang/project-rfc-2229#31 r? ``@nikomatsakis``
2 parents 4de1fa2 + f99e152 commit 7e8d0c6

File tree

8 files changed

+408
-89
lines changed

8 files changed

+408
-89
lines changed

compiler/rustc_typeck/src/check/upvar.rs

+62-43
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
180180
debug!("seed place {:?}", place);
181181

182182
let upvar_id = ty::UpvarId::new(*var_hir_id, local_def_id);
183-
let capture_kind = self.init_capture_kind(capture_clause, upvar_id, span);
183+
let capture_kind =
184+
self.init_capture_kind_for_place(&place, capture_clause, upvar_id, span);
184185
let fake_info = ty::CaptureInfo {
185186
capture_kind_expr_id: None,
186187
path_expr_id: None,
@@ -205,11 +206,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
205206
// If we have an origin, store it.
206207
if let Some(origin) = delegate.current_origin.clone() {
207208
let origin = if self.tcx.features().capture_disjoint_fields {
208-
origin
209+
(origin.0, restrict_capture_precision(origin.1))
209210
} else {
210-
// FIXME(project-rfc-2229#31): Once the changes to support reborrowing are
211-
// made, make sure we are selecting and restricting
212-
// the origin correctly.
213211
(origin.0, Place { projections: vec![], ..origin.1 })
214212
};
215213

@@ -449,7 +447,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
449447
base => bug!("Expected upvar, found={:?}", base),
450448
};
451449

452-
let place = restrict_capture_precision(place, capture_info.capture_kind);
450+
let place = restrict_capture_precision(place);
453451

454452
let min_cap_list = match root_var_min_capture_list.get_mut(&var_hir_id) {
455453
None => {
@@ -897,15 +895,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
897895
}
898896
}
899897

900-
fn init_capture_kind(
898+
fn init_capture_kind_for_place(
901899
&self,
900+
place: &Place<'tcx>,
902901
capture_clause: hir::CaptureBy,
903902
upvar_id: ty::UpvarId,
904903
closure_span: Span,
905904
) -> ty::UpvarCapture<'tcx> {
906905
match capture_clause {
907-
hir::CaptureBy::Value => ty::UpvarCapture::ByValue(None),
908-
hir::CaptureBy::Ref => {
906+
// In case of a move closure if the data is accessed through a reference we
907+
// want to capture by ref to allow precise capture using reborrows.
908+
//
909+
// If the data will be moved out of this place, then the place will be truncated
910+
// at the first Deref in `adjust_upvar_borrow_kind_for_consume` and then moved into
911+
// the closure.
912+
hir::CaptureBy::Value if !place.deref_tys().any(ty::TyS::is_ref) => {
913+
ty::UpvarCapture::ByValue(None)
914+
}
915+
hir::CaptureBy::Value | hir::CaptureBy::Ref => {
909916
let origin = UpvarRegion(upvar_id, closure_span);
910917
let upvar_region = self.next_region_var(origin);
911918
let upvar_borrow = ty::UpvarBorrow { kind: ty::ImmBorrow, region: upvar_region };
@@ -1109,12 +1116,25 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
11091116
place_with_id, diag_expr_id, mode
11101117
);
11111118

1112-
// we only care about moves
1113-
match mode {
1114-
euv::Copy => {
1119+
match (self.capture_clause, mode) {
1120+
// In non-move closures, we only care about moves
1121+
(hir::CaptureBy::Ref, euv::Copy) => return,
1122+
1123+
// We want to capture Copy types that read through a ref via a reborrow
1124+
(hir::CaptureBy::Value, euv::Copy)
1125+
if place_with_id.place.deref_tys().any(ty::TyS::is_ref) =>
1126+
{
11151127
return;
11161128
}
1117-
euv::Move => {}
1129+
1130+
(hir::CaptureBy::Ref, euv::Move) | (hir::CaptureBy::Value, euv::Move | euv::Copy) => {}
1131+
};
1132+
1133+
let place = truncate_capture_for_move(place_with_id.place.clone());
1134+
let place_with_id = PlaceWithHirId { place: place.clone(), hir_id: place_with_id.hir_id };
1135+
1136+
if !self.capture_information.contains_key(&place) {
1137+
self.init_capture_info_for_place(&place_with_id, diag_expr_id);
11181138
}
11191139

11201140
let tcx = self.fcx.tcx;
@@ -1128,13 +1148,15 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
11281148

11291149
let usage_span = tcx.hir().span(diag_expr_id);
11301150

1131-
// To move out of an upvar, this must be a FnOnce closure
1132-
self.adjust_closure_kind(
1133-
upvar_id.closure_expr_id,
1134-
ty::ClosureKind::FnOnce,
1135-
usage_span,
1136-
place_with_id.place.clone(),
1137-
);
1151+
if matches!(mode, euv::Move) {
1152+
// To move out of an upvar, this must be a FnOnce closure
1153+
self.adjust_closure_kind(
1154+
upvar_id.closure_expr_id,
1155+
ty::ClosureKind::FnOnce,
1156+
usage_span,
1157+
place.clone(),
1158+
);
1159+
}
11381160

11391161
let capture_info = ty::CaptureInfo {
11401162
capture_kind_expr_id: Some(diag_expr_id),
@@ -1317,8 +1339,12 @@ impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
13171339
if let PlaceBase::Upvar(upvar_id) = place_with_id.place.base {
13181340
assert_eq!(self.closure_def_id.expect_local(), upvar_id.closure_expr_id);
13191341

1320-
let capture_kind =
1321-
self.fcx.init_capture_kind(self.capture_clause, upvar_id, self.closure_span);
1342+
let capture_kind = self.fcx.init_capture_kind_for_place(
1343+
&place_with_id.place,
1344+
self.capture_clause,
1345+
upvar_id,
1346+
self.closure_span,
1347+
);
13221348

13231349
let expr_id = Some(diag_expr_id);
13241350
let capture_info = ty::CaptureInfo {
@@ -1392,15 +1418,10 @@ impl<'a, 'tcx> euv::Delegate<'tcx> for InferBorrowKind<'a, 'tcx> {
13921418
}
13931419

13941420
/// Truncate projections so that following rules are obeyed by the captured `place`:
1395-
///
1396-
/// - No Derefs in move closure, this will result in value behind a reference getting moved.
13971421
/// - No projections are applied to raw pointers, since these require unsafe blocks. We capture
13981422
/// them completely.
13991423
/// - No Index projections are captured, since arrays are captured completely.
1400-
fn restrict_capture_precision<'tcx>(
1401-
mut place: Place<'tcx>,
1402-
capture_kind: ty::UpvarCapture<'tcx>,
1403-
) -> Place<'tcx> {
1424+
fn restrict_capture_precision<'tcx>(mut place: Place<'tcx>) -> Place<'tcx> {
14041425
if place.projections.is_empty() {
14051426
// Nothing to do here
14061427
return place;
@@ -1412,7 +1433,6 @@ fn restrict_capture_precision<'tcx>(
14121433
}
14131434

14141435
let mut truncated_length = usize::MAX;
1415-
let mut first_deref_projection = usize::MAX;
14161436

14171437
for (i, proj) in place.projections.iter().enumerate() {
14181438
if proj.ty.is_unsafe_ptr() {
@@ -1426,31 +1446,30 @@ fn restrict_capture_precision<'tcx>(
14261446
truncated_length = truncated_length.min(i);
14271447
break;
14281448
}
1429-
ProjectionKind::Deref => {
1430-
// We only drop Derefs in case of move closures
1431-
// There might be an index projection or raw ptr ahead, so we don't stop here.
1432-
first_deref_projection = first_deref_projection.min(i);
1433-
}
1449+
ProjectionKind::Deref => {}
14341450
ProjectionKind::Field(..) => {} // ignore
14351451
ProjectionKind::Subslice => {} // We never capture this
14361452
}
14371453
}
14381454

1439-
let length = place
1440-
.projections
1441-
.len()
1442-
.min(truncated_length)
1443-
// In case of capture `ByValue` we want to not capture derefs
1444-
.min(match capture_kind {
1445-
ty::UpvarCapture::ByValue(..) => first_deref_projection,
1446-
ty::UpvarCapture::ByRef(..) => usize::MAX,
1447-
});
1455+
let length = place.projections.len().min(truncated_length);
14481456

14491457
place.projections.truncate(length);
14501458

14511459
place
14521460
}
14531461

1462+
/// Truncates a place so that the resultant capture doesn't move data out of a reference
1463+
fn truncate_capture_for_move(mut place: Place<'tcx>) -> Place<'tcx> {
1464+
if let Some(i) = place.projections.iter().position(|proj| proj.kind == ProjectionKind::Deref) {
1465+
// We only drop Derefs in case of move closures
1466+
// There might be an index projection or raw ptr ahead, so we don't stop here.
1467+
place.projections.truncate(i);
1468+
}
1469+
1470+
place
1471+
}
1472+
14541473
fn construct_place_string(tcx: TyCtxt<'_>, place: &Place<'tcx>) -> String {
14551474
let variable_name = match place.base {
14561475
PlaceBase::Upvar(upvar_id) => var_name(tcx, upvar_id.var_path.hir_id).to_string(),

src/test/ui/closures/2229_closure_analysis/by_value.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ fn big_box() {
2626
//~^ First Pass analysis includes:
2727
//~| Min Capture analysis includes:
2828
let p = t.0.0;
29-
//~^ NOTE: Capturing t[(0, 0),Deref,(0, 0)] -> ByValue
29+
//~^ NOTE: Capturing t[(0, 0),Deref,(0, 0)] -> ImmBorrow
30+
//~| NOTE: Capturing t[(0, 0)] -> ByValue
3031
//~| NOTE: Min Capture t[(0, 0)] -> ByValue
3132
println!("{} {:?}", t.1, p);
3233
//~^ NOTE: Capturing t[(1, 0)] -> ImmBorrow

src/test/ui/closures/2229_closure_analysis/by_value.stderr

+8-3
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,18 @@ LL | |
2828
LL | | };
2929
| |_____^
3030
|
31-
note: Capturing t[(0, 0),Deref,(0, 0)] -> ByValue
31+
note: Capturing t[(0, 0),Deref,(0, 0)] -> ImmBorrow
32+
--> $DIR/by_value.rs:28:17
33+
|
34+
LL | let p = t.0.0;
35+
| ^^^^^
36+
note: Capturing t[(0, 0)] -> ByValue
3237
--> $DIR/by_value.rs:28:17
3338
|
3439
LL | let p = t.0.0;
3540
| ^^^^^
3641
note: Capturing t[(1, 0)] -> ImmBorrow
37-
--> $DIR/by_value.rs:31:29
42+
--> $DIR/by_value.rs:32:29
3843
|
3944
LL | println!("{} {:?}", t.1, p);
4045
| ^^^
@@ -57,7 +62,7 @@ note: Min Capture t[(0, 0)] -> ByValue
5762
LL | let p = t.0.0;
5863
| ^^^^^
5964
note: Min Capture t[(1, 0)] -> ImmBorrow
60-
--> $DIR/by_value.rs:31:29
65+
--> $DIR/by_value.rs:32:29
6166
|
6267
LL | println!("{} {:?}", t.1, p);
6368
| ^^^
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#![feature(capture_disjoint_fields)]
2+
//~^ WARNING: the feature `capture_disjoint_fields` is incomplete
3+
//~| `#[warn(incomplete_features)]` on by default
4+
//~| see issue #53488 <https://github.com/rust-lang/rust/issues/53488>
5+
6+
// Test that array access is not stored as part of closure kind origin
7+
8+
fn expect_fn<F: Fn()>(_f: F) {}
9+
10+
fn main() {
11+
let s = [format!("s"), format!("s")];
12+
let c = || { //~ ERROR expected a closure that implements the `Fn`
13+
let [_, _s] = s;
14+
};
15+
expect_fn(c);
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
warning: the feature `capture_disjoint_fields` is incomplete and may not be safe to use and/or cause compiler crashes
2+
--> $DIR/closure-origin-array-diagnostics.rs:1:12
3+
|
4+
LL | #![feature(capture_disjoint_fields)]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `#[warn(incomplete_features)]` on by default
8+
= note: see issue #53488 <https://github.com/rust-lang/rust/issues/53488> for more information
9+
10+
error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
11+
--> $DIR/closure-origin-array-diagnostics.rs:12:13
12+
|
13+
LL | let c = || {
14+
| ^^ this closure implements `FnOnce`, not `Fn`
15+
LL | let [_, _s] = s;
16+
| - closure is `FnOnce` because it moves the variable `s` out of its environment
17+
LL | };
18+
LL | expect_fn(c);
19+
| --------- the requirement to implement `Fn` derives from here
20+
21+
error: aborting due to previous error; 1 warning emitted
22+
23+
For more information about this error, try `rustc --explain E0525`.

0 commit comments

Comments
 (0)