Skip to content

Commit a09fd46

Browse files
committed
hy-check(loans): use effective_loan_last in check_a2_conflict/emit_o8_alias_if_live and unwrap move in extend_map_slot_loan_to_alias
1 parent 4ccbd0b commit a09fd46

2 files changed

Lines changed: 61 additions & 4 deletions

File tree

‎compiler/crates/hy-check/src/loans.rs‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -423,11 +423,12 @@ impl<'a> Checker<'a> {
423423
/// borrow-mode loan on the same handle (or an alias / may-share peer).
424424
fn check_a2_conflict(&mut self, st: &FnState, cr: &CallRef, root: &str, idx: usize) {
425425
let conflict = st.loans.iter().find(|l| {
426+
let eff_last = self.effective_loan_last(st, l);
426427
matches!(
427428
&l.target,
428429
LoanTarget::Handle { handle } if a3_paths_conflict(st, handle, root)
429430
) && l.created <= idx
430-
&& idx <= l.last
431+
&& idx <= eff_last
431432
});
432433
let Some(loan) = conflict else { return };
433434
let msg = format!("mutating call on `{}` while a borrow loan is live", root);
@@ -538,7 +539,11 @@ impl<'a> Checker<'a> {
538539
val: &Expr,
539540
idx: usize,
540541
) {
541-
let Expr::Ident { name: src, .. } = unwrap_parens(val) else {
542+
let mut cur = val;
543+
while let Expr::Paren { inner: e, .. } | Expr::Move { operand: e, .. } = cur {
544+
cur = e;
545+
}
546+
let Expr::Ident { name: src, .. } = cur else {
542547
return;
543548
};
544549
let alias_last = st.last_use.get(alias_name).copied().unwrap_or(idx).max(idx);
@@ -617,15 +622,17 @@ impl<'a> Checker<'a> {
617622
what: &str,
618623
) {
619624
let Some(loan) = st.loans.iter().find(|l| {
625+
let eff_last = self.effective_loan_last(st, l);
620626
matches!(&l.target, LoanTarget::MapSlot { binding, path: p, key: k }
621627
if owner != Some(binding.as_str())
622628
&& a3_paths_conflict(st, p, path)
623629
&& !matches!((k.as_deref(), key), (Some(a), Some(b)) if a != b))
624630
&& l.created <= idx
625-
&& idx <= l.last
631+
&& idx <= eff_last
626632
}) else {
627633
return;
628634
};
635+
let eff_last = self.effective_loan_last(st, loan);
629636
let LoanTarget::MapSlot { binding, .. } = &loan.target else {
630637
unreachable!("filtered above");
631638
};
@@ -636,7 +643,7 @@ impl<'a> Checker<'a> {
636643
let notes = vec![
637644
format!(
638645
"{} ({}:{}) is a unique loan on a slot of `{}`, live from creation to its last use (O8, non-lexical window ending at statement {})",
639-
loan.desc, self.path, loan.span.start, path, loan.last
646+
loan.desc, self.path, loan.span.start, path, eff_last
640647
),
641648
format!(
642649
"the loan lowers to copy-out / store-back, so this access and `{}` would silently overwrite each other — finish using `{}` first, or mutate the slot only through `{}`",

‎compiler/crates/hy-check/tests/addr_of_loan.rs‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,53 @@ fn addr_of_plain_local_is_not_affected() {
9696
r.diagnostics
9797
);
9898
}
99+
100+
#[test]
101+
fn loop_carried_o8_slot_alias_a2_handle_loan_and_move_alias_reject() {
102+
// 1. O8 slot loan created before a loop and used before a second slot loan / field store inside the loop body.
103+
let r_o8_loop = check(
104+
"struct node { v int }\n\
105+
fn main() {\n\
106+
m := map[string]node{\"k\": node{v: 1}}\n\
107+
p := &m[\"k\"]\n\
108+
for i in 0..2 {\n\
109+
_ = i\n\
110+
p.v = 2\n\
111+
q := &m[\"k\"]\n\
112+
q.v = 3\n\
113+
}\n\
114+
}\n",
115+
);
116+
assert_eq!(r_o8_loop.verdict, Verdict::Reject, "got {:?}", r_o8_loop.diagnostics);
117+
assert!(codes(&r_o8_loop).iter().any(|c| c == "HYB-E3102"));
118+
119+
// 2. A2 handle borrow loan created before a loop and read before a mutating handle call inside the loop body.
120+
let r_a2_loop = check(
121+
"import \"rust/serde_json\"\n\
122+
fn main() {\n\
123+
doc := serde_json.parse(`{}`)?\n\
124+
items := doc.array(\"items\")?\n\
125+
for i in 0..2 {\n\
126+
_ = i\n\
127+
fmt.println(items.len())\n\
128+
doc.set(\"k\", 1)?\n\
129+
}\n\
130+
}\n",
131+
);
132+
assert_eq!(r_a2_loop.verdict, Verdict::Reject, "got {:?}", r_a2_loop.diagnostics);
133+
assert!(codes(&r_a2_loop).iter().any(|c| c == "HYB-E3101"));
134+
135+
// 3. `q := move p` extends `p`'s slot-loan window to `q`'s last use so an intervening map insert is rejected.
136+
let r_move_alias = check(
137+
"struct node { v int }\n\
138+
fn main() {\n\
139+
m := map[string]node{\"k\": node{v: 1}}\n\
140+
p := &m[\"k\"]\n\
141+
q := move p\n\
142+
m[\"x\"] = node{v: 2}\n\
143+
q.v = 3\n\
144+
}\n",
145+
);
146+
assert_eq!(r_move_alias.verdict, Verdict::Reject, "got {:?}", r_move_alias.diagnostics);
147+
assert!(codes(&r_move_alias).iter().any(|c| c == "HYB-E3102"));
148+
}

0 commit comments

Comments
 (0)