Files
AILang/examples/prelude.ail
T
Brummel feb941363a bugfix: print leak — propagate ret_mode through rigid substitution
`(app print x)` under --alloc=rc leaked the heap-Str allocated by
`show x` in print's body: the let-binder `s` in
`(let s (app show x) (do io/print_str s))` was never flagged
trackable, so the drop site at scope-close emitted no
`ailang_rc_dec(s)`. Minimal repro `(body (app print 42))` produced
`allocs=1 frees=0 live=1`.

Root cause is two-layer:

1. examples/prelude.ail.json declared the Show class method `show`
   without an explicit `ret_mode` on the return type, so serde
   defaulted to ParamMode::Implicit. Fix: add `"ret_mode": "own"`
   on the Show.show method, parallel to how the heap-Str-producing
   builtins (int_to_str, bool_to_str, float_to_str, str_clone,
   str_concat) declare it. examples/prelude.ail regenerated via
   `ail render` to stay parse-isomorphic with the JSON.

2. crates/ailang-check/src/lib.rs `substitute_rigids` was silently
   stripping `param_modes` and `ret_mode` whenever it rebuilt a
   `Type::Fn`, so even after the prelude fix, the mono-synthesised
   `show__Int / Bool / Str / Float` lost the `Own` annotation
   during rigid substitution. Fix: preserve both fields through
   the substitution.

RED pin: crates/ail/tests/print_no_leak_pin.rs (test
`alloc_rc_print_int_does_not_leak_show_result_str`) + fixture
examples/print_int_no_leak_pin.ail asserts allocs == frees and
live == 0 for `(body (app print 42))` under --alloc=rc.

Ten mono-body hash pins re-recorded (eq__Int/Bool/Str,
compare__Int/Bool/Str, show__Int/Bool/Str/Float, eq__IntBox) —
the bodies are semantically identical; the canonical JSON now
carries the previously-stripped mode metadata, so the body hash
moves. Re-pinning is bookkeeping for the intentional drift, not
a workaround.

Surfaced 2026-05-14 during the rpe.1 BLOCKED orchestrator run
(Cat A). Existed in latent form since iter 24.3 (when Show + print
shipped); only became user-observable when `(app print ...)` joined
the corpus. cargo test --workspace: 565 / 0 / 3.

Open follow-up flagged for next /audit: grep for `Type::Fn { ..., .. }`
field-spread sites — any other shape that drops modes during
rebuild is a latent instance of the same bug class. Out of scope
for this minimal fix.
2026-05-14 01:51:50 +02:00

117 lines
6.2 KiB
Plaintext

(module prelude
(data Ordering
(doc "Result of a three-way comparison: LT (less than), EQ (equal), GT (greater than). Ships in milestone 23 as the codomain of Ord.compare.")
(ctor LT)
(ctor EQ)
(ctor GT))
(class Eq
(param a)
(doc "Structural equality. Ships in milestone 23 alongside Ord. The primitive `==` operator stays as the surface-level comparator this iter; routing `==` through `Eq.eq` is the declared P2 follow-up (see docs/specs/2026-05-10-23-eq-ord-prelude.md, `Out of scope`).")
(method eq
(type (fn-type (params (borrow a) (borrow a)) (ret (con Bool))))))
(instance
(class Eq)
(type (con Int))
(doc "Eq Int. Body lowers to `icmp eq i64` via the existing primitive `==` dispatch in `lower_eq`.")
(method eq
(body (lam (params (typed x a) (typed y a)) (ret (con Bool)) (body (app == x y))))))
(instance
(class Eq)
(type (con Bool))
(doc "Eq Bool. Body lowers to `icmp eq i1` via the existing primitive `==` dispatch in `lower_eq`.")
(method eq
(body (lam (params (typed x a) (typed y a)) (ret (con Bool)) (body (app == x y))))))
(instance
(class Eq)
(type (con Str))
(doc "Eq Str. The lambda body shape mirrors Int/Bool for round-trip stability, but the codegen intercept in `emit_fn` overrides the body to call `@ail_str_eq` directly — see `try_emit_primitive_instance_body` in `crates/ailang-codegen/src/lib.rs`.")
(method eq
(body (lam (params (typed x a) (typed y a)) (ret (con Bool)) (body (app == x y))))))
(class Ord
(param a)
(superclass (class Eq) (type a))
(doc "Total ordering. Ships in milestone 23 alongside Eq. `compare x y` returns LT, EQ, or GT (the three-ctor Ordering ADT also in the prelude). Decision 11's single-superclass closure requires `instance Eq T` for every `instance Ord T` — the three Ord instances below pair with the three Eq instances shipped in iter 23.2.3.")
(method compare
(type (fn-type (params (borrow a) (borrow a)) (ret (con Ordering))))))
(instance
(class Ord)
(type (con Int))
(doc "Ord Int. The lambda body shape is a placeholder for round-trip stability; the codegen intercept `try_emit_primitive_instance_body::\"compare__Int\"` emits a three-way `icmp slt` / `icmp eq` branch ladder constructing LT / EQ / GT.")
(method compare
(body (lam (params (typed x a) (typed y a)) (ret (con Ordering)) (body (term-ctor Ordering EQ))))))
(instance
(class Ord)
(type (con Bool))
(doc "Ord Bool. Body lowered via `try_emit_primitive_instance_body::\"compare__Bool\"` — `icmp ult i1` LT-test, `icmp eq i1` EQ-test, GT default.")
(method compare
(body (lam (params (typed x a) (typed y a)) (ret (con Ordering)) (body (term-ctor Ordering EQ))))))
(instance
(class Ord)
(type (con Str))
(doc "Ord Str. Body lowered via `try_emit_primitive_instance_body::\"compare__Str\"` — `call i32 @ail_str_compare(ptr, ptr)` then branch on slt-0 / eq-0 against the normalised {-1, 0, +1} return.")
(method compare
(body (lam (params (typed x a) (typed y a)) (ret (con Ordering)) (body (term-ctor Ordering EQ))))))
(class Show
(param a)
(doc "Producer of a human-readable Str representation. Ships in milestone 24 with primitive instances for Int/Bool/Str/Float; user types declare their own instance.")
(method show
(type (fn-type (params (borrow a)) (ret (own (con Str)))))))
(instance
(class Show)
(type (con Int))
(method show
(body (lam (params (typed x (con Int))) (ret (con Str)) (body (app int_to_str x))))))
(instance
(class Show)
(type (con Bool))
(method show
(body (lam (params (typed x (con Bool))) (ret (con Str)) (body (app bool_to_str x))))))
(instance
(class Show)
(type (con Str))
(method show
(body (lam (params (typed x (con Str))) (ret (con Str)) (body (app str_clone x))))))
(instance
(class Show)
(type (con Float))
(method show
(body (lam (params (typed x (con Float))) (ret (con Str)) (body (app float_to_str x))))))
(fn ne
(doc "Polymorphic disequality. `ne x y` ≡ not (eq x y). Ships in milestone 23 as the Eq-class free helper.")
(type (forall (vars a) (constraints (constraint Eq a)) (fn-type (params (borrow a) (borrow a)) (ret (con Bool)))))
(params x y)
(body (app not (app eq x y))))
(fn lt
(doc "Polymorphic strict-less-than. `lt x y` ≡ case compare x y of LT -> True; _ -> False. Ships in milestone 23 as the Ord-class free helper.")
(type (forall (vars a) (constraints (constraint Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (con Bool)))))
(params x y)
(body (match (app compare x y)
(case (pat-ctor LT) true)
(case _ false))))
(fn le
(doc "Polymorphic less-than-or-equal. `le x y` ≡ case compare x y of GT -> False; _ -> True. Ships in milestone 23 as the Ord-class free helper.")
(type (forall (vars a) (constraints (constraint Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (con Bool)))))
(params x y)
(body (match (app compare x y)
(case (pat-ctor GT) false)
(case _ true))))
(fn gt
(doc "Polymorphic strict-greater-than. `gt x y` ≡ case compare x y of GT -> True; _ -> False. Ships in milestone 23 as the Ord-class free helper.")
(type (forall (vars a) (constraints (constraint Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (con Bool)))))
(params x y)
(body (match (app compare x y)
(case (pat-ctor GT) true)
(case _ false))))
(fn ge
(doc "Polymorphic greater-than-or-equal. `ge x y` ≡ case compare x y of LT -> False; _ -> True. Ships in milestone 23 as the Ord-class free helper.")
(type (forall (vars a) (constraints (constraint Ord a)) (fn-type (params (borrow a) (borrow a)) (ret (con Bool)))))
(params x y)
(body (match (app compare x y)
(case (pat-ctor LT) false)
(case _ true))))
(fn print
(doc "Polymorphic console-print helper. `print x` ≡ `do io/print_str (show x)` with an explicit let-binder around `show x` for heap-Str RC discipline per eob.1 Str carve-out. Ships in milestone 24 as the second half of the Show prelude.")
(type (forall (vars a) (constraints (constraint Show a)) (fn-type (params (borrow a)) (ret (con Unit)) (effects IO))))
(params x)
(body (let s (app show x) (do io/print_str s)))))