From 12b4824a0ddd56cfde0f5e334e1855b9701c6cf6 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 27 May 2026 18:54:13 +0300 Subject: [PATCH] =?UTF-8?q?ffi=20checkpoint:=20step=204=20partial=20?= =?UTF-8?q?=E2=80=94=20.type=5Ftag=20activated=20by=20the=20book?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Logs the four 4.x slices (ac60d98 → fd03b58): - 4.0 foundation: const_type opcode, asTypeId helper, cmp_eq arm, Zig unit tests. - 4.1 reflection arms: type_name/type_eq interp implementations reading .type_tag values; has_impl bails (snapshot work pending). - 4.2 audit + bitcast guard: box_any/unbox_any layout confirmed correct, bitcast guards against .type_tag mis-coercion. - 4.3 source construction: parser accepts $args[$i] in expression position; lowering emits const_type with the bound TypeId; resolveTypeArg + tryConstBoolCondition fold through pack_arg_types. End-to-end working: type_name($args[0]), inline-if type_eq dispatch over $args[0] per-mono. Test count 209/209. Remaining within step 4: - 4B compile_error(fmt, args) intrinsic. - 4A bare $args (whole pack as []Type) — step 5 needs this. - has_impl interp-time wiring. Step 5 (generic Into(Block) impl) needs only bare-$args from the remaining list to be fully unblocked. --- current/CHECKPOINT-FFI.md | 101 +++++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/current/CHECKPOINT-FFI.md b/current/CHECKPOINT-FFI.md index f9f04b6..52283a7 100644 --- a/current/CHECKPOINT-FFI.md +++ b/current/CHECKPOINT-FFI.md @@ -6,6 +6,69 @@ add a test and make it pass — that's two commits). ## Last completed step +**M5.A.next.4 — activate Value.type_tag (Type as a first-class value)** +(commits `ac60d98`, `9600ba5`, `55c72af`, `fd03b58` — 4 slices). + +Activated the dormant `Value.type_tag(TypeId)` variant in the +interp by the book — no silent-error budget violations, +explicit construction path through a new IR opcode, kind-honest +helpers, source-language `$args[$i]` in expression position. + +| Slice | Commit | What | +|---|---|---| +| 4.0 foundation | `ac60d98` | New `Op.const_type: TypeId` opcode (dedicated, never piggybacks on `const_int`). Interp emits `Value.type_tag(tid)`. emit_llvm bails loudly (Type is comptime-only; LLVM never sees one). `Value.asTypeId() ?TypeId` helper. `evalCmp` arm for `.type_tag, .type_tag` — TypeId equality. Mixed `.type_tag` vs `.int` falls through to `typeErrorDetail`. Zig unit tests confirm the variant. | +| 4.1 reflection arms | `9600ba5` | `BuiltinId.type_name` / `.type_eq` / `.has_impl` for the interp-time fallback when lowering can't fold the call statically. Static-arg calls keep the existing `tryLowerReflectionCall` const-emission fast path. `has_impl` interp arm bails with "not yet wired" — interp-time has_impl needs a queryable snapshot of the host's protocol maps (its own follow-up). emit_llvm bails loudly on all three (comptime-only). | +| 4.2 audit + bitcast guard | `55c72af` | `box_any`/`unbox_any` audit: layout was already correct (tag stays `.int`; value field can be `.type_tag`). `bitcast` interp arm guards against `.type_tag → ` casts — catches the `xx val to string` shape in `any_to_string`'s `case type:` arm that pre-dates type_tag and would silently mis-coerce. | +| 4.3 source construction | `fd03b58` | Parser accepts `$[]` in expression position (yields the same `pack_index_type_expr` AST node already used in type positions in step 3). Lowering: `lowerExpr` arm emits `const_type(arg_tys[index])`; `resolveTypeArg` arm reads `pack_arg_types[name][index]` directly so lower-time fold paths (`tryLowerReflectionCall`, `tryConstBoolCondition`) see the bound TypeId rather than falling through to the `.s64` silent-arm default. | + +Audit summary — every Value-switch in interp.zig was checked +for silent fall-through. Findings: +- All existing `else` arms are either already `bailDetail` / + `error.TypeError` (loud) or pass-through helpers where transit- + unchanged is semantically correct for `.type_tag`. +- `box_any` tag field stays `.int`; value field can carry any + Value kind including `.type_tag`. No changes needed. +- `asInt`/`asFloat`/`asBool`/`asString` keep returning `null` for + `.type_tag` — no silent coercion to int just because TypeId is + internally an int. +- Comparison op `cmp_eq` got an explicit `.type_tag, .type_tag` arm. +- Coercion op `bitcast` got an explicit bail arm for `.type_tag → + ` to catch any stale `xx val to string` paths. + +What's now possible end-to-end (from `examples/169-pack-value-dispatch.sx`): + +```sx +show :: (..$args) -> string => type_name($args[0]); +show(42) // "s64" +show("hi") // "string" + +describe :: (..$args) -> string { + inline if type_eq($args[0], s64) { return "got s64"; } + inline if type_eq($args[0], string) { return "got string"; } + ... +} +``` + +`$args[0]` as a value flows through `const_type` → `Value.type_tag` +→ `type_name`/`type_eq` (lower-time fold via `resolveTypeArg`) +without losing its kind anywhere. + +Known follow-ups: +- `has_impl` interp arm currently bails. Needs a protocol-map + snapshot on `Interpreter.init`. +- `any_to_string`'s `case type:` arm in stdlib still does + `xx val to string` — pre-`type_tag` shape. Once `.type_tag` + flows into a print/format path, the bitcast guard fires. + Fix is to replace with `type_name(val)` once the lowering + supports value-shaped type_name. +- `$args` (bare, without indexing) as a `[]Type` value — + needed by full step-5 builder bodies. Single-element access + works; whole-slice access deferred. + +209/209 example tests + `zig build test` green. + +--- + **M5.A.next.3 — type-position `$args[$i]` + reflection intrinsics** (commits `69dcee8` → `8b457ff`, 5 total). @@ -724,7 +787,7 @@ plus 2 codegen fixes surfaced along the way.** ## Current state -- 208/208 example tests pass; `zig build test` green. +- 209/209 example tests pass; `zig build test` green. - Phase 3.0/3.1/3.2 + M1.0–M1.3 + M2.1–M2.3 + M3 + M4.0 + M4.A all landed. - Pack feature step 1 done (1c.A → 1d.B; commits bb6eca6 → 08feb60). - Pack feature step 2 done — typed `args[$i]` at literal indices @@ -740,6 +803,9 @@ plus 2 codegen fixes surfaced along the way.** - issue-0047 (#run stderr vs runtime stdout split) FILED. - Pack feature step 3 done — type-position `$args[$i]` + reflection intrinsics (`type_name`, `type_eq`, `has_impl`). +- Pack feature step 4.0–4.3 done — `Value.type_tag` activated + honestly; source-language `$args[$i]` in expression position + yields a comptime Type value end-to-end. - iOS-sim chess running end-to-end (verified post-step-2b screencap). - Chess on macOS / iOS-sim / Android all build and run. @@ -751,17 +817,27 @@ heterogeneous picks, OOB diagnostics, bare/runtime `args` access, mixed comptime+pack, `$args[$i]` in type positions, type-reflection intrinsics. -Step 4 (`#insert` pack passthrough + `compile_error`) is a -small parser/interp tweak letting builders accept `$args` as a -comptime `[]Type` value AND raise build-time diagnostics from -inside `#insert` bodies. Prep work for step 5/6. +Step 4 partially done — `.type_tag` activation + `$args[$i]` +in expression position landed (4.0–4.3). Still open within +step 4: +- 4B `compile_error(fmt, args)` comptime intrinsic — raise a + build-time diagnostic from inside a builder. Small commit + set; not blocking step 5 but useful for builder error paths. +- 4A's bare `$args` (whole pack as `[]Type` value) — the only + remaining shape needed for step 5's full generic builder + pattern; single-element `$args[$i]` works today. +- `has_impl` interp arm — currently bails, needs a protocol- + map snapshot on `Interpreter.init`. Step 5 (generic `Into(Block)` impl) — the visible end-user payoff. Replaces stdlib's per-signature hand-rolled Into impls with ONE generic that the compiler emits per-call-shape. Body uses `$args[$i]` in fn-pointer type positions for the -trampoline signature (step 3 unblocked this) + emits a -trampoline-fn per mono. +trampoline signature (step 3 unblocked) + `const_type` Type +values in expression position (step 4 unblocked) + a single +`#insert build_block_convert(...)` emission. Needs bare-`$args` +(4A) to land first, plus a builder fn that emits the trampoline ++ Block literal source string. Step 6 (stdlib `print` / `format` refactor) — rewrite the existing `($fmt: string, args: ..Any)` signatures to use the @@ -769,12 +845,15 @@ new pack feature. Compile-time arity and type checking instead of runtime Any boxing. Outstanding items not blocking the next slice: -- `$args[$i]` in EXPRESSION position (parser limitation). -- Non-literal comptime args in mixed-mode pack-fns. +- Non-literal comptime args in mixed-mode pack-fns (degrades + to a `?` mangle segment today). - LSP "undefined variable" warnings on type-name args to reflection intrinsics (cosmetic). -- has_impl static-only false-negative for un-instantiated - plain protocols. +- `any_to_string`'s `case type:` arm in stdlib uses `xx val + to string` — pre-`type_tag` shape. Once `.type_tag` flows + into a print/format path, the bitcast guard fires. Fix is + to replace with `type_name(val)` once value-form + `type_name` is wired through `tryLowerReflectionCall`. **M4.0 — context.allocator threading** (4 commits this session): - `__sx_allocator: Allocator` prepended at field index 0 of every