Pure file-boundary move of lib/io/stream.ww's single 'empty stream'
banner section (_empty_read, _empty_write, _empty_vt, empty) into
lib/io/empty.ww, mirroring ref/hare/io/empty.ha. No code, name,
signature, or behavior change. Comment-only adjustments: the banner
line is deleted (the file name carries it); the moved block's
'Lives in stream.ww' self-reference now says empty.ww; stream.ww's
head drops its empty bullet (content duplicated by empty.ww's own
comments); io.ww's ownership map gains the empty.ww line; types.ww's
'three files' count becomes four. empty.ww needs no imports (package
io types only).
Makefile: lib/io/empty.ww added beside every lib/io/stream.ww
occurrence (WWFIXTURE_SRC line 75; w6c_ww/wwdump_ww bootstrap prereq
lists at lines 184/204). lib/io has no _test.ww, so no LIBRARY_TESTS
or byteid-roster change; byteid coverage rides the bootstrap gates.
Validation: out/bin/w6c lib/io/io.ww (standalone contract) exit 0;
importer tests green: bufio 26, memio 11, log 11, getopt 12 (calls
io.empty), fmt 49.
io.error grows errors.invalid (ref/hare/io/types.ha:11 spreads
...errors::error, which includes it; Hare's memio seek returns it on
out-of-bounds, stream.ha:134-136) — appended last so existing member
tags stay put; no exhaustive io.error matches exist in lib.
seekfn mirrors ref/hare/memio/stream.ha:122-140 over the flat header,
shared by fixed/dynamic/dynamicfrom (Hare wires the same seek into
both vtables). The io.off-vs-i64 arithmetic runs on an i64 copy:
cstage binop typing is nominal on aliases, wwstage accepts (filed,
ww-core #54).
791's stream_seek_unsupported row re-pins st_seek's void-arm on a
hand-built seekerless vtable: its old premise (memio wires no seeker)
is retired by this commit; memio seek success is pinned by memiotest.
w6c/wwdump main.combined.ww regen'd: they embed lib/io + lib/memio
(the freshness gates are blind to this — embedded-source discipline);
w6a/w6l/ww don't embed io, verified untouched.
Ports ref/hare/io handle.ha: a handle is (file | *stream); ww stream is already *vtable (#94 collapse) so the payload is (file | stream). file=i32 (Hare int is 32-bit, ww int is 8B word -- width-faithful, USER-ruled). read/write/close/seek/tell match on the handle: file-arm to os.read/write/close/lseek (os plays Hare sys role), stream-arm to the unchanged st_* vtable bodies; seeker is the 4th vtable slot (copier deferred). On a file-arm syscall error the stub returns errors.unsupported with a #199b marker -- faithful errno to io.error needs io.error to spread ...errors.error (#204/#199b-blocked); only the error value is lossy until then, the type stays faithful. Regenerates w6c/wwdump combined.ww.
The Option-C parallel _v vstream API was scaffolding to bring the io stack up alongside the old surface; carrying both permanently is a rule-9 divergence from ref/hare, which has exactly one io surface. Collapse onto that surface (stream = *vtable, ref/hare/io/stream.ha) and rename the _v symbols to their Hare names (io vstream->stream, fmt vfprint->fprint, bufio/memio/log surfaces, log.new). Deletes the 4 lib/*/vstream.ww scaffold files; regenerates w6c/wwdump combined.ww. cstage and wwstage stay byte-identical and combined_ww_fresh holds; all 220 tests pass.
Additive: keeps lib/io/io.ww's pre-vtable `stream` struct +
`read`/`write`/`close` wrappers (fold-e2-eN migrates the legacy
surface to vtable-backed implementations and retires it).
lib/io/stream.ww — vtable struct (reader/writer/closer slots,
spelled `(*T | void)` per #192 — ww parser rejects `nullable *T`),
vstream = *vtable, and the st_read/st_write/st_close dispatchers
per ref/hare/io/stream.ha:33-68. Void-arm `return e;` chains two
direct widens: concrete `errors.unsupported` → `error` (#199 α)
then `error` → (size|eof|error) (#205 NAMED-variant nominal at
tagged→tagged subset). Hare's `?`-propagating st_close collapses
to a direct `return (*c)(s);` because #173 is still open; the
surface stays Hare-shaped.
lib/io/types.ww — retarget reader/writer/closer fn-aliases from
*stream to vstream. Extend `error` union to include
`errors.unsupported` explicitly (no spread — per ken's #204-block
the wrapper-vs-flatten layout asymmetry would mis-widen; the
deferred fix is filed as #199b layout-extension).
test/wcc/775_io_vtable_run.c — 7-row sentinel: reader/writer/
closer × {set, void} happy paths + branched-callee runtime.
Rows verify the call runs + the constant exit; the void-arm
rows do NOT inspect the resulting variant tag (deferred #199b
wrapped-slot tag-remap mis-routes to dst tag 0). Cstage and
wwstage emit byte-identical asm on every row.
test/wcc/768_io_types_run.c — track the alias retarget; rows
now build a vtable, pass `&vt` (= vstream), and call through
the fn-VALUE param shape.
Combined.ww regen for w6c + wwdump per #110: lib/errors lands
transitively via the new `import errors;` in types.ww.
Sibling filed inline (NOT fixed): the checker rejects bare
`&fn_name` / `let p: *alias = &fn` assignment to a
`(*alias | void)` field — the structural `*fn(...)` value isn't
accepted as the `*alias` NAMED variant. Both stages reject.
Probes route around via explicit `(&fn): *io.reader` cast at
each vtable-field assignment.
New lib/io/types.ww mirrors ref/hare/io/types.ha — the surrounding
port that lives alongside the existing lib/io/io.ww (pre-vtable
stream + eof + underread). Hare splits the same way (stream.ha +
types.ha share `module io`); ww does the equivalent via dir-enum.
Each type cites Hare per CLAUDE.md rule 9:
- mode (enum u8) — ref/hare/io/types.ha:29-34. RDWR=3 (not
Hare's `READ | WRITE`) because ww enum-value
positions don't fold expressions; bitfield
value SSoT preserved, divergence inline.
- whence (enum i32) — ref/hare/io/types.ha:37-41. Hare leaves the
underlying implicit; ww requires one. i32
matches the `off` type fold-e wires in.
- error — ref/hare/io/types.ha:11. Hare spreads
`errors::error`; lib/errors not ported, so
the union carries the two tags observable
in this fold: underread (from io.ww) and
the predeclared `nomem` (#29, type.c:72 /
check.ww:78). NOT redefined here.
- reader/writer/closer — ref/hare/io/types.ha:46/51/55. EOF=eof
(not Hare's `done` singleton) per #93
and the io.ww:8 rationale. `*stream`
forward-refs the existing pre-vtable
struct in io.ww; same cross-file pattern
Hare uses.
Drew signoff (this fold only): seeker, copier, strerror, and the
EOF=done singleton DEFERRED to fold-e — they need the `handle` sum
and #93's done landing. Hare's `_unsafe` carve-out unaffected.
eof / underread / stream re-used from io.ww (NOT redefined); io.ww
keeps the pre-vtable struct unchanged, ditto its WHY-comments.
Combined.ww regen (#110): selfhost/cmd/{w6c,wwdump}/main.combined.ww
auto-pulled the new types.ww via dir-enum (+52 lines each, same
package io). Makefile dep lines for wwdump_ww + w6c_ww add the new
source so editing it triggers rebuild.
Probe: test/wcc/768_io_types_run.c — 5 rows × 2 stages = 10
invocations. Pins enum value/underlying + the three fn-type aliases
at the param slot. Both siblings filed inline in the probe header:
- #189 wwstage `let r: io.reader = fn_name;` bails "let: not
assignable". cstage accepts. Param + struct-field paths work
in both stages, so io vtable port is unblocked. Probe uses
the alias only at the param slot.
- #190 wwstage match-arm on cross-module variant tag bails
"case: not a variant of scrutinee (io.eof | io.error)". Likely
same family as #178. cstage accepts. Probe uses `is` instead
of `match` for the variant gate.
make test: 201/201 (was 200; +1 from 768). 990-997 byte-id +
combined_ww_fresh + sizelint all green.