Skip to content

Commit 7e3a8ec

Browse files
Rollup merge of #80092 - sexxi-goose:restrict_precision, r=nikomatsakis
2229: Fix issues with move closures and mutability This PR fixes two issues when feature `capture_disjoint_fields` is used. 1. Can't mutate using a mutable reference 2. Move closures try to move value out through a reference. To do so, we 1. Compute the mutability of the capture and store it as part of the `CapturedPlace` that is written in TypeckResults 2. Restrict capture precision. Note this is temporary for now, to allow the feature to be used with move closures and ByValue captures and might change depending on discussions with the lang team. - No Derefs are captured for ByValue captures, since that 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. r? `````@nikomatsakis`````
2 parents 0e63af5 + 0f4bab2 commit 7e3a8ec

33 files changed

+1181
-75
lines changed

compiler/rustc_middle/src/ty/mod.rs

+18-1
Original file line numberDiff line numberDiff line change
@@ -661,11 +661,28 @@ pub type RootVariableMinCaptureList<'tcx> = FxIndexMap<hir::HirId, MinCaptureLis
661661
/// Part of `MinCaptureInformationMap`; List of `CapturePlace`s.
662662
pub type MinCaptureList<'tcx> = Vec<CapturedPlace<'tcx>>;
663663

664-
/// A `Place` and the corresponding `CaptureInfo`.
664+
/// A composite describing a `Place` that is captured by a closure.
665665
#[derive(PartialEq, Clone, Debug, TyEncodable, TyDecodable, TypeFoldable, HashStable)]
666666
pub struct CapturedPlace<'tcx> {
667+
/// The `Place` that is captured.
667668
pub place: HirPlace<'tcx>,
669+
670+
/// `CaptureKind` and expression(s) that resulted in such capture of `place`.
668671
pub info: CaptureInfo<'tcx>,
672+
673+
/// Represents if `place` can be mutated or not.
674+
pub mutability: hir::Mutability,
675+
}
676+
677+
impl CapturedPlace<'tcx> {
678+
/// Returns the hir-id of the root variable for the captured place.
679+
/// e.g., if `a.b.c` was captured, would return the hir-id for `a`.
680+
pub fn get_root_variable(&self) -> hir::HirId {
681+
match self.place.base {
682+
HirPlaceBase::Upvar(upvar_id) => upvar_id.var_path.hir_id,
683+
base => bug!("Expected upvar, found={:?}", base),
684+
}
685+
}
669686
}
670687

671688
pub fn place_to_string_for_capture(tcx: TyCtxt<'tcx>, place: &HirPlace<'tcx>) -> String {

compiler/rustc_mir/src/borrow_check/diagnostics/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
215215
PlaceRef { local, projection: [proj_base @ .., elem] } => {
216216
match elem {
217217
ProjectionElem::Deref => {
218+
// FIXME(project-rfc_2229#36): print capture precisely here.
218219
let upvar_field_projection = self.is_upvar_field_projection(place);
219220
if let Some(field) = upvar_field_projection {
220221
let var_index = field.index();
@@ -259,6 +260,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
259260
ProjectionElem::Field(field, _ty) => {
260261
autoderef = true;
261262

263+
// FIXME(project-rfc_2229#36): print capture precisely here.
262264
let upvar_field_projection = self.is_upvar_field_projection(place);
263265
if let Some(field) = upvar_field_projection {
264266
let var_index = field.index();

compiler/rustc_mir/src/borrow_check/diagnostics/move_errors.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,9 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
345345
};
346346

347347
let upvar = &self.upvars[upvar_field.unwrap().index()];
348-
let upvar_hir_id = upvar.var_hir_id;
348+
// FIXME(project-rfc-2229#8): Improve borrow-check diagnostics in case of precise
349+
// capture.
350+
let upvar_hir_id = upvar.place.get_root_variable();
349351
let upvar_name = upvar.name;
350352
let upvar_span = self.infcx.tcx.hir().span(upvar_hir_id);
351353

compiler/rustc_mir/src/borrow_check/diagnostics/mutability_errors.rs

+26-6
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,29 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
6464
Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty
6565
));
6666

67-
item_msg = format!("`{}`", access_place_desc.unwrap());
68-
if self.is_upvar_field_projection(access_place.as_ref()).is_some() {
69-
reason = ", as it is not declared as mutable".to_string();
67+
let imm_borrow_derefed = self.upvars[upvar_index.index()]
68+
.place
69+
.place
70+
.deref_tys()
71+
.any(|ty| matches!(ty.kind(), ty::Ref(.., hir::Mutability::Not)));
72+
73+
// If the place is immutable then:
74+
//
75+
// - Either we deref a immutable ref to get to our final place.
76+
// - We don't capture derefs of raw ptrs
77+
// - Or the final place is immut because the root variable of the capture
78+
// isn't marked mut and we should suggest that to the user.
79+
if imm_borrow_derefed {
80+
// If we deref an immutable ref then the suggestion here doesn't help.
81+
return;
7082
} else {
71-
let name = self.upvars[upvar_index.index()].name;
72-
reason = format!(", as `{}` is not declared as mutable", name);
83+
item_msg = format!("`{}`", access_place_desc.unwrap());
84+
if self.is_upvar_field_projection(access_place.as_ref()).is_some() {
85+
reason = ", as it is not declared as mutable".to_string();
86+
} else {
87+
let name = self.upvars[upvar_index.index()].name;
88+
reason = format!(", as `{}` is not declared as mutable", name);
89+
}
7390
}
7491
}
7592

@@ -259,9 +276,12 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
259276
Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty
260277
));
261278

279+
let captured_place = &self.upvars[upvar_index.index()].place;
280+
262281
err.span_label(span, format!("cannot {ACT}", ACT = act));
263282

264-
let upvar_hir_id = self.upvars[upvar_index.index()].var_hir_id;
283+
let upvar_hir_id = captured_place.get_root_variable();
284+
265285
if let Some(Node::Binding(pat)) = self.infcx.tcx.hir().find(upvar_hir_id) {
266286
if let hir::PatKind::Binding(
267287
hir::BindingAnnotation::Unannotated,

compiler/rustc_mir/src/borrow_check/diagnostics/var_name.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
1212
tcx: TyCtxt<'tcx>,
1313
body: &Body<'tcx>,
1414
local_names: &IndexVec<Local, Option<Symbol>>,
15-
upvars: &[Upvar],
15+
upvars: &[Upvar<'tcx>],
1616
fr: RegionVid,
1717
) -> Option<(Option<Symbol>, Span)> {
1818
debug!("get_var_name_and_span_for_region(fr={:?})", fr);
@@ -21,6 +21,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
2121
debug!("get_var_name_and_span_for_region: attempting upvar");
2222
self.get_upvar_index_for_region(tcx, fr)
2323
.map(|index| {
24+
// FIXME(project-rfc-2229#8): Use place span for diagnostics
2425
let (name, span) = self.get_upvar_name_and_span_for_region(tcx, upvars, index);
2526
(Some(name), span)
2627
})
@@ -59,10 +60,10 @@ impl<'tcx> RegionInferenceContext<'tcx> {
5960
crate fn get_upvar_name_and_span_for_region(
6061
&self,
6162
tcx: TyCtxt<'tcx>,
62-
upvars: &[Upvar],
63+
upvars: &[Upvar<'tcx>],
6364
upvar_index: usize,
6465
) -> (Symbol, Span) {
65-
let upvar_hir_id = upvars[upvar_index].var_hir_id;
66+
let upvar_hir_id = upvars[upvar_index].place.get_root_variable();
6667
debug!("get_upvar_name_and_span_for_region: upvar_hir_id={:?}", upvar_hir_id);
6768

6869
let upvar_name = tcx.hir().name(upvar_hir_id);

compiler/rustc_mir/src/borrow_check/mod.rs

+42-32
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ use rustc_data_structures::graph::dominators::Dominators;
55
use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorReported};
66
use rustc_hir as hir;
77
use rustc_hir::def_id::LocalDefId;
8-
use rustc_hir::{HirId, Node};
8+
use rustc_hir::Node;
99
use rustc_index::bit_set::BitSet;
1010
use rustc_index::vec::IndexVec;
1111
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
12-
use rustc_middle::hir::place::PlaceBase as HirPlaceBase;
1312
use rustc_middle::mir::{
1413
traversal, Body, ClearCrossCrate, Local, Location, Mutability, Operand, Place, PlaceElem,
1514
PlaceRef, VarDebugInfoContents,
@@ -18,7 +17,7 @@ use rustc_middle::mir::{AggregateKind, BasicBlock, BorrowCheckResult, BorrowKind
1817
use rustc_middle::mir::{Field, ProjectionElem, Promoted, Rvalue, Statement, StatementKind};
1918
use rustc_middle::mir::{InlineAsmOperand, Terminator, TerminatorKind};
2019
use rustc_middle::ty::query::Providers;
21-
use rustc_middle::ty::{self, ParamEnv, RegionVid, TyCtxt};
20+
use rustc_middle::ty::{self, CapturedPlace, ParamEnv, RegionVid, TyCtxt};
2221
use rustc_session::lint::builtin::{MUTABLE_BORROW_RESERVATION_CONFLICT, UNUSED_MUT};
2322
use rustc_span::{Span, Symbol, DUMMY_SP};
2423

@@ -73,16 +72,14 @@ crate use region_infer::RegionInferenceContext;
7372

7473
// FIXME(eddyb) perhaps move this somewhere more centrally.
7574
#[derive(Debug)]
76-
crate struct Upvar {
75+
crate struct Upvar<'tcx> {
76+
// FIXME(project-rfc_2229#36): print capture precisely here.
7777
name: Symbol,
7878

79-
// FIXME(project-rfc-2229#8): This should use Place or something similar
80-
var_hir_id: HirId,
79+
place: CapturedPlace<'tcx>,
8180

8281
/// If true, the capture is behind a reference.
8382
by_ref: bool,
84-
85-
mutability: Mutability,
8683
}
8784

8885
const DEREF_PROJECTION: &[PlaceElem<'_>; 1] = &[ProjectionElem::Deref];
@@ -161,26 +158,13 @@ fn do_mir_borrowck<'a, 'tcx>(
161158
let upvars: Vec<_> = tables
162159
.closure_min_captures_flattened(def.did.to_def_id())
163160
.map(|captured_place| {
164-
let var_hir_id = match captured_place.place.base {
165-
HirPlaceBase::Upvar(upvar_id) => upvar_id.var_path.hir_id,
166-
_ => bug!("Expected upvar"),
167-
};
161+
let var_hir_id = captured_place.get_root_variable();
168162
let capture = captured_place.info.capture_kind;
169163
let by_ref = match capture {
170164
ty::UpvarCapture::ByValue(_) => false,
171165
ty::UpvarCapture::ByRef(..) => true,
172166
};
173-
let mut upvar = Upvar {
174-
name: tcx.hir().name(var_hir_id),
175-
var_hir_id,
176-
by_ref,
177-
mutability: Mutability::Not,
178-
};
179-
let bm = *tables.pat_binding_modes().get(var_hir_id).expect("missing binding mode");
180-
if bm == ty::BindByValue(hir::Mutability::Mut) {
181-
upvar.mutability = Mutability::Mut;
182-
}
183-
upvar
167+
Upvar { name: tcx.hir().name(var_hir_id), place: captured_place.clone(), by_ref }
184168
})
185169
.collect();
186170

@@ -549,7 +533,7 @@ crate struct MirBorrowckCtxt<'cx, 'tcx> {
549533
dominators: Dominators<BasicBlock>,
550534

551535
/// Information about upvars not necessarily preserved in types or MIR
552-
upvars: Vec<Upvar>,
536+
upvars: Vec<Upvar<'tcx>>,
553537

554538
/// Names of local (user) variables (extracted from `var_debug_info`).
555539
local_names: IndexVec<Local, Option<Symbol>>,
@@ -1374,13 +1358,38 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
13741358

13751359
fn propagate_closure_used_mut_upvar(&mut self, operand: &Operand<'tcx>) {
13761360
let propagate_closure_used_mut_place = |this: &mut Self, place: Place<'tcx>| {
1377-
if !place.projection.is_empty() {
1378-
if let Some(field) = this.is_upvar_field_projection(place.as_ref()) {
1361+
// We have three possibilities here:
1362+
// a. We are modifying something through a mut-ref
1363+
// b. We are modifying something that is local to our parent
1364+
// c. Current body is a nested closure, and we are modifying path starting from
1365+
// a Place captured by our parent closure.
1366+
1367+
// Handle (c), the path being modified is exactly the path captured by our parent
1368+
if let Some(field) = this.is_upvar_field_projection(place.as_ref()) {
1369+
this.used_mut_upvars.push(field);
1370+
return;
1371+
}
1372+
1373+
for (place_ref, proj) in place.iter_projections().rev() {
1374+
// Handle (a)
1375+
if proj == ProjectionElem::Deref {
1376+
match place_ref.ty(this.body(), this.infcx.tcx).ty.kind() {
1377+
// We aren't modifying a variable directly
1378+
ty::Ref(_, _, hir::Mutability::Mut) => return,
1379+
1380+
_ => {}
1381+
}
1382+
}
1383+
1384+
// Handle (c)
1385+
if let Some(field) = this.is_upvar_field_projection(place_ref) {
13791386
this.used_mut_upvars.push(field);
1387+
return;
13801388
}
1381-
} else {
1382-
this.used_mut.insert(place.local);
13831389
}
1390+
1391+
// Handle(b)
1392+
this.used_mut.insert(place.local);
13841393
};
13851394

13861395
// This relies on the current way that by-value
@@ -2146,6 +2155,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
21462155
place: PlaceRef<'tcx>,
21472156
is_local_mutation_allowed: LocalMutationIsAllowed,
21482157
) -> Result<RootPlace<'tcx>, PlaceRef<'tcx>> {
2158+
debug!("is_mutable: place={:?}, is_local...={:?}", place, is_local_mutation_allowed);
21492159
match place.last_projection() {
21502160
None => {
21512161
let local = &self.body.local_decls[place.local];
@@ -2227,11 +2237,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
22272237
if let Some(field) = upvar_field_projection {
22282238
let upvar = &self.upvars[field.index()];
22292239
debug!(
2230-
"upvar.mutability={:?} local_mutation_is_allowed={:?} \
2231-
place={:?}",
2232-
upvar, is_local_mutation_allowed, place
2240+
"is_mutable: upvar.mutability={:?} local_mutation_is_allowed={:?} \
2241+
place={:?}, place_base={:?}",
2242+
upvar, is_local_mutation_allowed, place, place_base
22332243
);
2234-
match (upvar.mutability, is_local_mutation_allowed) {
2244+
match (upvar.place.mutability, is_local_mutation_allowed) {
22352245
(
22362246
Mutability::Not,
22372247
LocalMutationIsAllowed::No

compiler/rustc_mir/src/borrow_check/nll.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ pub(in crate::borrow_check) fn compute_regions<'cx, 'tcx>(
165165
flow_inits: &mut ResultsCursor<'cx, 'tcx, MaybeInitializedPlaces<'cx, 'tcx>>,
166166
move_data: &MoveData<'tcx>,
167167
borrow_set: &BorrowSet<'tcx>,
168-
upvars: &[Upvar],
168+
upvars: &[Upvar<'tcx>],
169169
) -> NllOutput<'tcx> {
170170
let mut all_facts = AllFacts::enabled(infcx.tcx).then_some(AllFacts::default());
171171

compiler/rustc_mir/src/borrow_check/path_utils.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ pub(super) fn borrow_of_local_data(place: Place<'_>) -> bool {
143143
/// of a closure type.
144144
pub(crate) fn is_upvar_field_projection(
145145
tcx: TyCtxt<'tcx>,
146-
upvars: &[Upvar],
146+
upvars: &[Upvar<'tcx>],
147147
place_ref: PlaceRef<'tcx>,
148148
body: &Body<'tcx>,
149149
) -> Option<Field> {

compiler/rustc_mir/src/borrow_check/type_check/mod.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ pub(crate) fn type_check<'mir, 'tcx>(
132132
flow_inits: &mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
133133
move_data: &MoveData<'tcx>,
134134
elements: &Rc<RegionValueElements>,
135-
upvars: &[Upvar],
135+
upvars: &[Upvar<'tcx>],
136136
) -> MirTypeckResults<'tcx> {
137137
let implicit_region_bound = infcx.tcx.mk_region(ty::ReVar(universal_regions.fr_fn_body));
138138
let mut constraints = MirTypeckRegionConstraints {
@@ -821,7 +821,7 @@ struct BorrowCheckContext<'a, 'tcx> {
821821
all_facts: &'a mut Option<AllFacts>,
822822
borrow_set: &'a BorrowSet<'tcx>,
823823
constraints: &'a mut MirTypeckRegionConstraints<'tcx>,
824-
upvars: &'a [Upvar],
824+
upvars: &'a [Upvar<'tcx>],
825825
}
826826

827827
crate struct MirTypeckResults<'tcx> {
@@ -2490,7 +2490,9 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
24902490
body,
24912491
);
24922492
let category = if let Some(field) = field {
2493-
ConstraintCategory::ClosureUpvar(self.borrowck_context.upvars[field.index()].var_hir_id)
2493+
let var_hir_id = self.borrowck_context.upvars[field.index()].place.get_root_variable();
2494+
// FIXME(project-rfc-2229#8): Use Place for better diagnostics
2495+
ConstraintCategory::ClosureUpvar(var_hir_id)
24942496
} else {
24952497
ConstraintCategory::Boring
24962498
};

compiler/rustc_mir_build/src/build/mod.rs

+1-10
Original file line numberDiff line numberDiff line change
@@ -851,22 +851,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
851851
_ => bug!("Expected an upvar")
852852
};
853853

854-
let mut mutability = Mutability::Not;
854+
let mutability = captured_place.mutability;
855855

856856
// FIXME(project-rfc-2229#8): Store more precise information
857857
let mut name = kw::Empty;
858858
if let Some(Node::Binding(pat)) = tcx_hir.find(var_id) {
859859
if let hir::PatKind::Binding(_, _, ident, _) = pat.kind {
860860
name = ident.name;
861-
match hir_typeck_results
862-
.extract_binding_mode(tcx.sess, pat.hir_id, pat.span)
863-
{
864-
Some(ty::BindByValue(hir::Mutability::Mut)) => {
865-
mutability = Mutability::Mut;
866-
}
867-
Some(_) => mutability = Mutability::Not,
868-
_ => {}
869-
}
870861
}
871862
}
872863

0 commit comments

Comments
 (0)