Compute mutability of closure captures

When `capture_disjoint_fields` is not enabled, checking if the root variable
binding is mutable would suffice.

However with the feature enabled, the captured place might be mutable
because it dereferences a mutable reference.

This PR computes the mutability of each capture after capture analysis
in rustc_typeck. We store this in `ty::CapturedPlace` and then use
`ty::CapturedPlace::mutability` in mir_build and borrow_check.
This commit is contained in:
Aman Arora 2020-12-02 03:03:56 -05:00
parent b421cd56d9
commit 3488082582
4 changed files with 60 additions and 22 deletions

View File

@ -661,11 +661,17 @@ pub type RootVariableMinCaptureList<'tcx> = FxIndexMap<hir::HirId, MinCaptureLis
/// Part of `MinCaptureInformationMap`; List of `CapturePlace`s.
pub type MinCaptureList<'tcx> = Vec<CapturedPlace<'tcx>>;
/// A `Place` and the corresponding `CaptureInfo`.
/// A composite describing a `Place` that is captured by a closure.
#[derive(PartialEq, Clone, Debug, TyEncodable, TyDecodable, TypeFoldable, HashStable)]
pub struct CapturedPlace<'tcx> {
/// The `Place` that is captured.
pub place: HirPlace<'tcx>,
/// `CaptureKind` and expression(s) that resulted in such capture of `place`.
pub info: CaptureInfo<'tcx>,
/// Represents if `place` can be mutated or not.
pub mutability: hir::Mutability,
}
pub fn place_to_string_for_capture(tcx: TyCtxt<'tcx>, place: &HirPlace<'tcx>) -> String {

View File

@ -170,17 +170,12 @@ fn do_mir_borrowck<'a, 'tcx>(
ty::UpvarCapture::ByValue(_) => false,
ty::UpvarCapture::ByRef(..) => true,
};
let mut upvar = Upvar {
Upvar {
name: tcx.hir().name(var_hir_id),
var_hir_id,
by_ref,
mutability: Mutability::Not,
};
let bm = *tables.pat_binding_modes().get(var_hir_id).expect("missing binding mode");
if bm == ty::BindByValue(hir::Mutability::Mut) {
upvar.mutability = Mutability::Mut;
mutability: captured_place.mutability,
}
upvar
})
.collect();

View File

@ -851,22 +851,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
_ => bug!("Expected an upvar")
};
let mut mutability = Mutability::Not;
let mutability = captured_place.mutability;
// FIXME(project-rfc-2229#8): Store more precise information
let mut name = kw::Empty;
if let Some(Node::Binding(pat)) = tcx_hir.find(var_id) {
if let hir::PatKind::Binding(_, _, ident, _) = pat.kind {
name = ident.name;
match hir_typeck_results
.extract_binding_mode(tcx.sess, pat.hir_id, pat.span)
{
Some(ty::BindByValue(hir::Mutability::Mut)) => {
mutability = Mutability::Mut;
}
Some(_) => mutability = Mutability::Not,
_ => {}
}
}
}

View File

@ -252,8 +252,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let capture = captured_place.info.capture_kind;
debug!(
"place={:?} upvar_ty={:?} capture={:?}",
captured_place.place, upvar_ty, capture
"final_upvar_tys: place={:?} upvar_ty={:?} capture={:?}, mutability={:?}",
captured_place.place, upvar_ty, capture, captured_place.mutability,
);
match capture {
@ -423,7 +423,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let min_cap_list = match root_var_min_capture_list.get_mut(&var_hir_id) {
None => {
let min_cap_list = vec![ty::CapturedPlace { place, info: capture_info }];
let mutability = self.determine_capture_mutability(&place);
let min_cap_list =
vec![ty::CapturedPlace { place, info: capture_info, mutability }];
root_var_min_capture_list.insert(var_hir_id, min_cap_list);
continue;
}
@ -486,8 +488,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Only need to insert when we don't have an ancestor in the existing min capture list
if !ancestor_found {
let mutability = self.determine_capture_mutability(&place);
let captured_place =
ty::CapturedPlace { place: place.clone(), info: updated_capture_info };
ty::CapturedPlace { place, info: updated_capture_info, mutability };
min_cap_list.push(captured_place);
}
}
@ -607,6 +610,49 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
}
/// A captured place is mutable if
/// 1. Projections don't include a Deref of an immut-borrow, **and**
/// 2. PlaceBase is mut or projections include a Deref of a mut-borrow.
fn determine_capture_mutability(&self, place: &Place<'tcx>) -> hir::Mutability {
let var_hir_id = match place.base {
PlaceBase::Upvar(upvar_id) => upvar_id.var_path.hir_id,
_ => unreachable!(),
};
let bm = *self
.typeck_results
.borrow()
.pat_binding_modes()
.get(var_hir_id)
.expect("missing binding mode");
let mut is_mutbl = match bm {
ty::BindByValue(mutability) => mutability,
ty::BindByReference(_) => hir::Mutability::Not,
};
for pointer_ty in place.deref_tys() {
match pointer_ty.kind() {
// We don't capture derefs of raw ptrs
ty::RawPtr(_) => unreachable!(),
// Derefencing a mut-ref allows us to mut the Place if we don't deref
// an immut-ref after on top of this.
ty::Ref(.., hir::Mutability::Mut) => is_mutbl = hir::Mutability::Mut,
// The place isn't mutable once we dereference a immutable reference.
ty::Ref(.., hir::Mutability::Not) => return hir::Mutability::Not,
// Dereferencing a box doesn't change mutability
ty::Adt(def, ..) if def.is_box() => {}
unexpected_ty => bug!("deref of unexpected pointer type {:?}", unexpected_ty),
}
}
is_mutbl
}
}
struct InferBorrowKind<'a, 'tcx> {