Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/analyze/basic_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
} else {
rty
};
if self.is_mut_local(local) || rty.ty.is_mut() {
if self.is_mut_local(local) || self.env.type_contains_mut(&rty.ty) {
self.env.mut_bind(local, rty);
} else {
self.env.immut_bind(local, rty);
Expand Down Expand Up @@ -1278,7 +1278,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
match bb_ty.param_kind(param_idx) {
BasicBlockTypeParamKind::Local(local, _) => {
if bb_ty.mutbl_of_param(param_idx).unwrap().is_mut()
|| param_unrefined_rty.ty.is_mut()
|| self.env.type_contains_mut(&param_unrefined_rty.ty)
{
self.env.mut_bind(local, param_unrefined_rty);
} else {
Expand Down
48 changes: 47 additions & 1 deletion src/refine/env.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::{BTreeMap, HashMap};
use std::collections::{BTreeMap, HashMap, HashSet};

use pretty::{termcolor, Pretty};
use rustc_abi::{FieldIdx, VariantIdx};
Expand Down Expand Up @@ -1013,6 +1013,52 @@ where
}
}

/// Returns `true` if a `&mut` can be reached from a place of this type by
/// following tuple/struct field, pointer, and enum-variant projections, so
/// that a sub-place may be mut-borrowed even when the enclosing local is not
/// reassignable. Such a local must be given flow-decomposed bindings.
///
/// This is the "needs decomposition" notion, decided purely from the type
/// (resolving enum variants through the enum definitions). It is independent
/// of whether the local is reassignable/mut-borrowed: that is the separate
/// concern handled by box-elaboration before binding. A bare `Box`/`own` is
/// not itself reborrowable, so it only matters here when it wraps a `&mut`.
pub fn type_contains_mut(&self, ty: &rty::Type<Var>) -> bool {
self.type_contains_mut_rec(ty, &mut HashSet::new())
}

fn type_contains_mut_rec(
&self,
ty: &rty::Type<Var>,
visiting: &mut HashSet<chc::DatatypeSymbol>,
) -> bool {
match ty {
rty::Type::Pointer(ty) => {
ty.is_mut() || self.type_contains_mut_rec(&ty.elem.ty, visiting)
}
rty::Type::Tuple(ty) => ty
.elems
.iter()
.any(|elem| self.type_contains_mut_rec(&elem.ty, visiting)),
rty::Type::Enum(ty) => {
// Guard against recursive enum types; a back edge reaches no new
// `&mut` (any reachable one is found along a finite path).
if !visiting.insert(ty.symbol.clone()) {
return false;
}
let def = self.enum_defs.enum_def(&ty.symbol);
let found = def.field_tys().any(|field_ty| {
let mut field_ty = rty::RefinedType::unrefined(field_ty.clone().vacuous());
field_ty.instantiate_ty_params(ty.args.clone());
self.type_contains_mut_rec(&field_ty.ty, visiting)
});
visiting.remove(&ty.symbol);
found
}
_ => false,
}
}

fn locate_place(&self, place: Place<'_>) -> Var {
let mut var = place.local.into();

Expand Down
19 changes: 19 additions & 0 deletions tests/ui/fail/reborrow_mut_field_of_aggregate_param.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//@error-in-other-file: Unsat

// Regression test for #125: reborrowing a `&mut`-typed field out of an
// aggregate (tuple/struct) parameter used to panic with "deref unbound var"
// because the aggregate parameter was bound without flow bindings.

fn bump(r: &mut i64) {
*r = 1;
}

fn f(w: (&mut i64,)) {
bump(w.0);
}

fn main() {
let mut x = 0_i64;
f((&mut x,));
assert!(x == 0);
}
28 changes: 28 additions & 0 deletions tests/ui/fail/reborrow_mut_field_of_enum_param.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//@error-in-other-file: Unsat

// Companion to reborrow_mut_field_of_aggregate_param for enums: a `&mut`
// reachable through an enum variant. Exercises the enum arm of the
// "needs decomposition" check; binding must flow-decompose so the `&mut`
// pulled out of the enum can be reborrowed.

enum E<'a> {
A(&'a mut i64),
B,
}

fn bump(r: &mut i64) {
*r = 1;
}

fn f(w: E) {
match w {
E::A(r) => bump(r),
E::B => {}
}
}

fn main() {
let mut x = 0_i64;
f(E::A(&mut x));
assert!(x == 0);
}
19 changes: 19 additions & 0 deletions tests/ui/pass/reborrow_mut_field_of_aggregate_param.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//@check-pass

// Regression test for #125: reborrowing a `&mut`-typed field out of an
// aggregate (tuple/struct) parameter used to panic with "deref unbound var"
// because the aggregate parameter was bound without flow bindings.

fn bump(r: &mut i64) {
*r = 1;
}

fn f(w: (&mut i64,)) {
bump(w.0);
}

fn main() {
let mut x = 0_i64;
f((&mut x,));
assert!(x == 1);
}
28 changes: 28 additions & 0 deletions tests/ui/pass/reborrow_mut_field_of_enum_param.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//@check-pass

// Companion to reborrow_mut_field_of_aggregate_param for enums: a `&mut`
// reachable through an enum variant. Exercises the enum arm of the
// "needs decomposition" check; binding must flow-decompose so the `&mut`
// pulled out of the enum can be reborrowed.

enum E<'a> {
A(&'a mut i64),
B,
}

fn bump(r: &mut i64) {
*r = 1;
}

fn f(w: E) {
match w {
E::A(r) => bump(r),
E::B => {}
}
}

fn main() {
let mut x = 0_i64;
f(E::A(&mut x));
assert!(x == 1);
}