✗
Failing This test is currently failing.
Failed: expected-error-missing
Code
// PINS: an effect-branch proc body reaching its own module by a BARE name is
// refused by koru, naming the `$mod.` spelling.
//
// Cut-1 effect inlining splices the proc body into the CONSUMER's frame, where
// the declaring module's names do not exist. `$mod.` is the sanctioned spelling
// and 400_155 holds it green. This is the twin: get it wrong, and today the
// author meets
//
// error: use of undeclared identifier 'BASE'
//
// from Zig, pointing into `output_emitted.zig` — a file they did not write,
// about a rule stated only in a comment inside another library.
//
// WHERE THE WALL CANNOT GO, measured: neither frontend checker sees an
// imported module's declarations. `flow_checker.checkSourceFile` is handed the
// entry file's items only — in BOTH modes, for a test fixture and for a real
// package alike — so at check time there is no `proc_decl` to inspect and no
// host line to compare it against. (`shape_checker.checkSourceFile` has no
// production caller at all.) The merged program first exists at emission,
// where `ctx.ast_items` carries everything but there is no reporter.
//
// So this is not a missing conditional; it is a pass that does not exist yet.
// KORU112 is reserved for it.
//
// This is the first hour of every library author who writes an effect branch,
// which is why it is worth a wall rather than a docs line.
~import std/io
~import app/lib/gadget
~app/lib/gadget:spin(count: 3)
! tick w |> std/io:print.ln("tick {{ w.n:d }}")
| done total |> std/io:print.ln("total {{ total:d }}")
Frontend must reject with:
CONTAINS $mod.Flows
flow ~spin click a branch to expand · @labels scroll to their anchor
spin (count: 3)
Imported Files
// Module fixture for 400_155: proc bodies reach their OWN module scope
// through the sanctioned `$mod.` spelling — a module type (Widget), a module
// fn (twice), and a module const (BASE).
//
// Cut-1 effect inlining splices an effect event's proc body into the
// CONSUMER's frame, where the module's bare names don't exist; before this
// contract, libs reached back through emitter-internal naming
// (`@import("root").koru_libs.<mangled>`), which broke silently when the
// mangling changed. `$mod.` is the stable spelling: the emitter rewrites it
// to the declaring module's namespace on the splice path and strips it to
// bare names on the standalone Handlers-fn path — one spelling, every
// lowering.
const std = @import("std");
pub const Widget = struct { n: i32 };
pub const BASE: i32 = 10;
pub fn twice(v: i32) i32 {
return v * 2;
}
// Handler-path probe: no effect branch, so this emits as an ordinary handler
// fn INSIDE the module namespace — `$mod.` must strip to bare names there.
~pub tor calc { n: i32 } -> i32
~proc calc|zig {
return $mod.twice(n) + $mod.BASE;
}
// Splice-path probe: the effect branch makes this event inline-eligible, so
// this body lands in the consumer's frame — `$mod.` must rewrite to the
// module's emitted namespace there. Here ONE reference is deliberately left
// bare (`BASE`), which is the mistake this fixture exists to have diagnosed.
~pub tor spin { count: i32 }
! tick *Widget
| done i32
~proc spin|zig {
// NB: local is named `wid`, not `w` — the inline splice is deliberately
// non-hygienic (400_151), so proc locals share a frame with the caller's
// branch bindings.
var wid = $mod.Widget{ .n = 0 };
var i: i32 = 0;
var acc: i32 = 0;
while (i < count) : (i += 1) {
wid.n = $mod.twice(i) + BASE;
acc += wid.n;
tick(&wid);
}
return .{ .done = acc };
}