From 07bcc1c4851f07cf1f9ea43ca3507257314dce4a Mon Sep 17 00:00:00 2001 From: "shoney.arickathil" Date: Tue, 18 Aug 2026 19:07:10 +0200 Subject: [PATCH] fix(compiler): demand promotion targets the escaping projection's class The escape hook promoted the borrow-root local's class, so `return h.box` over-promoted the container `Holder` alongside `Box`. Now `owner.ml`'s `transfer` passes the escaping place's type (`place_ty p`) as `~promote_class`, so only the value that actually escapes is promoted. - escape: takes ~promote_class; records it in collect mode, else reports WO-E304. - borrow-escape.wo now promotes only Box (was Box + Holder); rc.wo sans @gc still promotes Cache. Verified: woc-test 566/0, test_diag 14/0, oop-e2e 79/0. Co-Authored-By: Claude Opus 5 (1M context) --- compiler/src/gcinfer.ml | 7 +++---- compiler/src/owner.ml | 22 ++++++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/compiler/src/gcinfer.ml b/compiler/src/gcinfer.ml index cda6a66..f589b47 100644 --- a/compiler/src/gcinfer.ml +++ b/compiler/src/gcinfer.ml @@ -13,10 +13,9 @@ `infer`, so `Types.is_gc_class` — which field-kind derivation, owner exemptions, and the class flag all key off — answers identically everywhere. - KNOWN LIMITATION (to refine with the Phase-3 landing): the demand hook - promotes the escaping *root local's* class, so `return h.box` over-promotes - `Holder` as well as `Box`. It should promote the escaping projection's type - only. Sound (never under-promotes) but imprecise. *) + The demand hook promotes the escaping *projection's* type (owner.ml's + `transfer` passes `place_ty p`), so `return h.box` promotes `Box`, never the + container `Holder`. *) module SMap = Types.StringMap diff --git a/compiler/src/owner.ml b/compiler/src/owner.ml index f132908..b9afa93 100644 --- a/compiler/src/owner.ml +++ b/compiler/src/owner.ml @@ -1075,15 +1075,15 @@ let class_of_ty (syms : Types.symbols) (t : Ast.field_ty) : string option = | Ast.Scalar n when Types.StringMap.mem n syms.Types.classes -> Some n | _ -> None -let escape (ctx : ctx) (l : local) ~(pos : Ast.pos) ~message : unit = - match ctx.promote with - | Some record -> ( - match class_of_ty ctx.syms l.l_ty with - | Some c -> record c (* demand promotion instead of WO-E304 *) - | None -> - report ctx ~code:borrow_escape_code ~pos ~message ~rel:l.l_pos - ~label:(borrow_label l)) - | None -> +let escape (ctx : ctx) (l : local) ~(pos : Ast.pos) ~message + ~(promote_class : string option) : unit = + (* `promote_class` is the class of the value that actually escapes (the + escaping place's type), not the borrow-root local's — so `return h.box` + promotes `Box`, never `Holder`. In collect mode with a class in hand, record + it; otherwise report WO-E304. *) + match (ctx.promote, promote_class) with + | Some record, Some c -> record c + | _ -> report ctx ~code:borrow_escape_code ~pos ~message ~rel:l.l_pos ~label:(borrow_label l) @@ -1139,7 +1139,9 @@ let transfer (ctx : ctx) (p : place) ~(what : string) : bool = escape ctx l ~pos:p.ppos ~message: (Printf.sprintf "borrow of `%s` %s — borrows cannot outlive their scope" (place_text p) - what); + what) + ~promote_class: + (match place_ty ctx p with Some t -> class_of_ty ctx.syms t | None -> None); false | None -> ( match root_local ctx p with