ffi checkpoint: step 4 partial — .type_tag activated by the book

Logs the four 4.x slices (ac60d98fd03b58):

- 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.
This commit is contained in:
agra
2026-05-27 18:54:13 +03:00
parent fd03b5812f
commit 12b4824a0d

View File

@@ -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 → <non-Any, non-identity>` 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 `$<pack>[<int_literal>]` 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 →
<runtime kind>` 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.0M1.3 + M2.1M2.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.04.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.04.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