✗
Failing This test is currently failing.
Failed: backend-exec
Failure Output
error[KORU030]: Resource 'item' obligation <open!> was not discharged. Call one of: cancel, __store_insert_batch, __store_inserth_batch
--> tests/regression/600_STDLIB/690_STORE/690_107_drain_arm_with_the_parameter_omitted/input.k:27:0
❌ Compiler coordination error: Auto-discharge failed (multiple disposal options or no disposal event)
(set KORU_BACKEND_TRACE=1 for the backend return trace) Code
// The drain arm's binding reaches the call's one unfilled parameter.
//
// 690_106 is this program with `p: item` written. Here the label is dropped and
// the arm's binding is the only live `*Pending<!open>` in scope, so the argument
// thread has exactly one candidate for exactly one open slot — the ordinary
// by-type fill (210_191/192/194), which selects by TYPE and never by name.
//
// The store SYNTHESIZES this arm, and that is the whole difficulty: the thread
// normalizes authored surface, and a synthesized arm is born to the right of
// every pass that would fill it. 690_094 is the same question wearing `| full`;
// this is its `! discharge` face, and the two share a fix.
//
// What makes this the sharper of the pair: the store's own refusal, when the
// binding is dropped entirely, teaches `! discharge item |> ...(<param>: item)` —
// so the diagnostic prescribes the written label because the fill cannot be
// relied on. When the fill lands, that teaching is free to shrink.
//
// The failure this guards against is not a miscompile. It is that the obligation
// checker reports the unfilled parameter as the BINDING's fault — "Resource
// 'item' obligation <open!> was not discharged" — which sends the author to
// discharge a value they already piped into the one call that discharges it.
import std/io
import std/store
import app/lib/pend
std/store:new(batch, capacity: 2) { *app/lib/pend:Pending<open!> }
! discharge item |> app/lib/pend:cancel(): n |> std/io:print.ln("cancelled {{ n:d }}")
app/lib/pend:start(): p |> std/store:insert(batch) { p }
| row _ |> _
std/io:print.ln("inserted")
Must contain:
insertedFlows
flow ~new click a branch to expand · @labels scroll to their anchor
new (expr: batch, capacity: 2, source: *app/lib/pend:Pending<open!>)
flow ~start click a branch to expand · @labels scroll to their anchor
start
flow ~print.ln click a branch to expand · @labels scroll to their anchor
print.ln (expr: "inserted")
Imported Files
// An owned resource with ZERO dischargers a store can fire: *Pending<open!> is
// consumed only by `cancel`, and `cancel` hands back a value. A store tears its
// elements down unattended, where there is nobody to take a return — so the
// column cannot be auto-discharged at any count, and must be DRAINED by a
// `! discharge` arm bolted onto the declaration.
//
// This is the shape koru/curl:Pending has in koru-examples/downloads, where
// `~pub tor cancel { p: *Pending<!open> } -> *Multi<owned!>` returns the batch.
// An i64 return carries the same property without a second obligation.
//
// The contrast is app/lib/tx (690_057/058/097): TWO void dischargers, so the
// store could fire either and refuses because it cannot pick. Here there is
// nothing to pick from.
const std = @import("std");
const Pending = struct { id: i32 };
~pub tor start { } -> *Pending<open!>
~proc start|zig {
const p = std.heap.page_allocator.create(Pending) catch unreachable;
p.* = .{ .id = 3 };
return p;
}
~pub tor cancel { p: *Pending<!open> } -> i64
~proc cancel|zig {
const id = p.id;
std.heap.page_allocator.destroy(p);
return id;
}