Skip to content

Commit a36a534

Browse files
coord-eclaude
andauthored
Fix enum-drop of aggregate mutable-reference fields (#167)
* Fix enum-drop of aggregate mutable-reference fields Dropping a value whose enum variant holds an aggregate of mutable references (e.g. `Pair::Two((&mut i32, &mut i32))`, or the `Option<(&mut T, &mut [T])>` returned by `split_first_mut`) panicked in `refine/env.rs` with `assert!(assumption_existentials.is_empty())`. The enum-drop path built each field's drop assumption by re-deriving it through a fresh `Path`, but projecting an aggregate field (`PlaceType::tuple_proj`/`deref`) allocates fresh existentials, so the recursive call returned an assumption with existentials the assert never expected. Minimum repro: enum Pair<'a> { Two((&'a mut i32, &'a mut i32)), None, } #[thrust::callable] fn check(a: &mut i32, b: &mut i32) { let _p = Pair::Two((a, b)); } Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QmZGoGNQRhg6aZsGhhSaDF * fixup! Fix enum-drop of aggregate mutable-reference fields --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent aa0d081 commit a36a534

3 files changed

Lines changed: 91 additions & 67 deletions

File tree

src/refine/env.rs

Lines changed: 47 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,7 +1050,6 @@ where
10501050

10511051
#[derive(Debug, Clone)]
10521052
enum Path {
1053-
PlaceTy(Box<PlaceType>),
10541053
Local(Local),
10551054
Deref(Box<Path>),
10561055
TupleProj(Box<Path>, usize),
@@ -1079,23 +1078,12 @@ impl<'tcx> From<Place<'tcx>> for Path {
10791078
}
10801079
}
10811080

1082-
impl Path {
1083-
fn deref(self) -> Self {
1084-
Path::Deref(Box::new(self))
1085-
}
1086-
1087-
fn tuple_proj(self, idx: usize) -> Self {
1088-
Path::TupleProj(Box::new(self), idx)
1089-
}
1090-
}
1091-
10921081
impl<T> Env<T>
10931082
where
10941083
T: EnumDefProvider,
10951084
{
10961085
fn path_type(&self, path: &Path) -> PlaceType {
10971086
match path {
1098-
Path::PlaceTy(pty) => *pty.clone(),
10991087
Path::Local(local) => self.local_type(*local),
11001088
Path::Deref(path) => self.path_type(path).deref(),
11011089
Path::TupleProj(path, idx) => self.path_type(path).tuple_proj(*idx),
@@ -1111,81 +1099,73 @@ where
11111099
}
11121100

11131101
fn dropping_assumption(&mut self, path: &Path) -> Assumption {
1114-
let ty = self.path_type(path);
1115-
if ty.ty.is_mut() {
1116-
let mut builder = PlaceTypeBuilder::default();
1117-
let (_, term) = builder.subsume(ty);
1118-
builder.push_formula(term.clone().mut_final().equal_to(term.mut_current()));
1119-
builder.build_assumption()
1120-
} else if ty.ty.is_own() {
1121-
self.dropping_assumption(&path.clone().deref())
1122-
} else if let Some(tty) = ty.ty.as_tuple() {
1123-
(0..tty.elems.len())
1124-
.map(|i| self.dropping_assumption(&path.clone().tuple_proj(i)))
1125-
.collect()
1126-
} else if let Some(ety) = ty.ty.as_enum() {
1102+
let PlaceType {
1103+
ty,
1104+
mut existentials,
1105+
term,
1106+
mut formula,
1107+
} = self.path_type(path);
1108+
formula.push_conj(self.dropping_formula_for_term(&mut existentials, &ty, term));
1109+
Assumption::new(existentials, formula)
1110+
}
1111+
1112+
fn dropping_formula_for_term(
1113+
&self,
1114+
existentials: &mut IndexVec<rty::ExistentialVarIdx, chc::Sort>,
1115+
ty: &rty::Type<Var>,
1116+
term: chc::Term<PlaceTypeVar>,
1117+
) -> chc::Body<PlaceTypeVar> {
1118+
if ty.is_mut() {
1119+
term.clone().mut_final().equal_to(term.mut_current()).into()
1120+
} else if ty.is_own() {
1121+
let inner = &ty.as_pointer().unwrap().elem.ty;
1122+
self.dropping_formula_for_term(existentials, inner, term.box_current())
1123+
} else if let Some(tty) = ty.as_tuple() {
1124+
let mut body = chc::Body::default();
1125+
for (i, elem) in tty.elems.iter().enumerate() {
1126+
body.push_conj(self.dropping_formula_for_term(
1127+
existentials,
1128+
&elem.ty,
1129+
term.clone().tuple_proj(i),
1130+
));
1131+
}
1132+
body
1133+
} else if let Some(ety) = ty.as_enum() {
1134+
let mut body = chc::Body::default();
1135+
11271136
let enum_def = self.enum_defs.enum_def(&ety.symbol);
11281137
let matcher_pred = chc::MatcherPred::new(ety.symbol.clone(), ety.arg_sorts());
11291138

1130-
let PlaceType {
1131-
ty: _,
1132-
mut existentials,
1133-
term,
1134-
mut formula,
1135-
} = ty;
1136-
11371139
let mut pred_args = vec![];
11381140
for field_ty in enum_def.field_tys() {
11391141
let mut field_rty = rty::RefinedType::unrefined(field_ty.clone().vacuous());
11401142
field_rty.instantiate_ty_params(ety.args.clone());
1143+
let field_type = field_rty.ty;
11411144

1142-
let ev = existentials.push(field_rty.ty.to_sort());
1143-
pred_args.push(chc::Term::var(ev.into()));
1145+
let ev = existentials.push(field_type.to_sort());
1146+
let field_term = chc::Term::var(ev.into());
1147+
pred_args.push(field_term.clone());
11441148

1145-
if let Some(p) = field_rty.ty.as_pointer() {
1149+
if let Some(p) = field_type.as_pointer() {
11461150
if matches!(&p.elem.ty, rty::Type::Enum(e) if e.symbol == ety.symbol) {
11471151
// TODO: we need recursively defined drop_pred for the recursive ADTs!
11481152
tracing::warn!("skipping recursive variant");
11491153
continue;
11501154
}
11511155
}
11521156

1153-
let field_pty = {
1154-
let rty::RefinedType {
1155-
ty: field_ty,
1156-
refinement,
1157-
} = field_rty;
1158-
let rty::Refinement { body, existentials } = refinement;
1159-
PlaceType {
1160-
ty: field_ty,
1161-
existentials,
1162-
term: chc::Term::var(ev.into()),
1163-
formula: body.map_var(|v| match v {
1164-
rty::RefinedTypeVar::Value => PlaceTypeVar::Existential(ev),
1165-
rty::RefinedTypeVar::Free(v) => PlaceTypeVar::Var(v),
1166-
// TODO: (but otherwise we can't distinguish field existentials from them...)
1167-
rty::RefinedTypeVar::Existential(_) => {
1168-
panic!("cannot handle existentials in field_rty")
1169-
}
1170-
}),
1171-
}
1172-
};
1173-
1174-
let Assumption {
1175-
existentials: assumption_existentials,
1176-
body: assumption_body,
1177-
} = self.dropping_assumption(&Path::PlaceTy(Box::new(field_pty)));
1178-
// dropping assumption should not generate any existential
1179-
assert!(assumption_existentials.is_empty());
1180-
formula.push_conj(assumption_body);
1157+
body.push_conj(self.dropping_formula_for_term(
1158+
existentials,
1159+
&field_type,
1160+
field_term,
1161+
));
11811162
}
11821163

11831164
pred_args.push(term);
1184-
formula.push_conj(chc::Atom::new(matcher_pred.into(), pred_args));
1185-
1186-
Assumption::new(existentials, formula)
1165+
body.push_conj(chc::Atom::new(matcher_pred.into(), pred_args));
1166+
body
11871167
} else {
1188-
Assumption::default()
1168+
chc::Body::default()
11891169
}
11901170
}
11911171

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//@error-in-other-file: Unsat
2+
//@compile-flags: -C debug-assertions=off
3+
4+
#[allow(dead_code)]
5+
enum Pair<'a> {
6+
Two((&'a mut i32, &'a mut i32)),
7+
None,
8+
}
9+
10+
#[thrust::callable]
11+
fn check(a: &mut i32, b: &mut i32) {
12+
*a = 10;
13+
*b = 20;
14+
{
15+
let _p = Pair::Two((a, b));
16+
}
17+
// wrong: `*a` is 10, not 11
18+
assert!(*a == 11);
19+
}
20+
21+
fn main() {}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//@check-pass
2+
//@compile-flags: -C debug-assertions=off
3+
4+
#[allow(dead_code)]
5+
enum Pair<'a> {
6+
Two((&'a mut i32, &'a mut i32)),
7+
None,
8+
}
9+
10+
#[thrust::callable]
11+
fn check(a: &mut i32, b: &mut i32) {
12+
*a = 10;
13+
*b = 20;
14+
{
15+
// Construct and drop the enum without mutating through the packed references.
16+
let _p = Pair::Two((a, b));
17+
}
18+
// Dropping resolves both prophecies to identity, so the values are unchanged.
19+
assert!(*a == 10);
20+
assert!(*b == 20);
21+
}
22+
23+
fn main() {}

0 commit comments

Comments
 (0)