✓
Passing This code compiles and runs correctly.
Code
// Multi-resource discharge: two live String resources both freed on one path
// (`free(s2) |> free(s1)`). String can't auto-discharge (free/take both candidates
// → KORU030), so every resource needs an EXPLICIT free; two live resources means
// two explicit frees on the path.
//
// Regression for the positional-arg discharge bug (fixed 2026-06-12): the
// obligation checker matched a disposal's arg to its parameter by name
// (`field.name == arg.name`), but a POSITIONAL arg (`free(s1)`) carries the
// binding name in arg.name, not the param name (`s`). So `free(s1)`/`free(s2)`
// searched for params literally named "s1"/"s2", found none, and silently cleared
// nothing — a FALSE KORU030 on every flow whose binding name differs from the
// param name. It only ever worked by coincidence (`free(s)` where binding == param,
// or the named form `free(s: owned)`). Fix: resolve positional args by POSITION.
// See src/auto_discharge_inserter.zig:checkInvocationSatisfiesObligations.
~import std/string
~import std/io
~std/string:from-page(text: "a")
| ok s1 |> std/string:from-page(text: "b")
| ok s2 |> std/string:read(s2)
| slice t |> std/io:print.ln("{{ t:s }}") |> std/string:free(s2) |> std/string:free(s1)
| err _ |> std/string:free(s1)
| err _ |> _
Actual
b
Expected output
b
Test Configuration
MUST_RUN