; RED fixture (refs #55, Implicit-cutover double-drop). ; ; Property under test: a ctor arm with a literal sub-pattern in a ; non-tail field — `(Cons (pat-lit K) _)` — must drop the matched ; value's owned children EXACTLY ONCE on the head!=K path. ; ; The 16c lit-sub-pattern desugar (crates/ailang-core/src/desugar.rs ; `desugar_one_arm` / `wrap_sub`) lowers `(Cons (pat-lit K) _)` into an ; outer Cons-match that binds the Cons fields, then an ; `if (== head K) body else fall_k`. `fall_k` is the rest of the arm ; chain, which RE-MATCHES the same owned scrutinee (re-binding its ; children). Once the param mode is `Own` (post-#55 cutover, was ; `Implicit`), the arm-close Iter-A drop in ; crates/ailang-codegen/src/match_lower.rs (gated on ; `scrutinee_is_owned`) fires on the tail child in BOTH the re-match arm ; AND the enclosing lit-arm's join — the same heap pointer (`arg_xs+16`) ; is dropped twice → double-free → SIGSEGV on the head!=K path. ; ; Pre-cutover this leaked instead of crashing (Implicit suppressed the ; drop entirely). The fixture pins single-drop, not merely exit-0: ; `cat` consumes its owned IntList arg, so a leak-free run frees every ; cell exactly once. `main` drives both the head==K (0) and head!=K (7) ; paths so the regression is visible on whichever path the fix touches. (module lit_pat_ctor_tail_drop (data IntList (ctor Nil) (ctor Cons (con Int) (con IntList))) (fn cat (doc "Empty -> -1; first element 0 -> 0; otherwise the head.") (type (fn-type (params (borrow (con IntList))) (ret (own (con Int))))) (params xs) (body (match xs (case (pat-ctor Nil) -1) (case (pat-ctor Cons (pat-lit 0) _) 0) (case (pat-ctor Cons h _) h)))) (fn main (doc "Drive head==0 then head!=0. Expected (per line): 0, 7.") (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (seq (seq (app print (app cat (term-ctor IntList Cons 0 (term-ctor IntList Nil)))) (do io/print_str "\n")) (seq (app print (app cat (term-ctor IntList Cons 7 (term-ctor IntList Nil)))) (do io/print_str "\n"))))))