From 633c0a2540ebd71908f59fff1e0926c44f576547 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 15:55:32 +0300 Subject: [PATCH] =?UTF-8?q?docs(issues):=20file=200075=20=E2=80=94=20silen?= =?UTF-8?q?t=20.s64=20type=20fallback=20in=20reflection=20builtins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Discovered during the 0074 fix + a codebase-wide silent-type-fallback sweep. getRefIRType(...) orelse TypeId.s64 at ops.zig:1023/1049/1055 (type_name/type_eq). Blocker; to be resolved before the arch-refactor stream closes. --- ...75-reflection-builtin-s64-type-fallback.md | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 issues/0075-reflection-builtin-s64-type-fallback.md diff --git a/issues/0075-reflection-builtin-s64-type-fallback.md b/issues/0075-reflection-builtin-s64-type-fallback.md new file mode 100644 index 0000000..8e3c3b7 --- /dev/null +++ b/issues/0075-reflection-builtin-s64-type-fallback.md @@ -0,0 +1,77 @@ +# 0075 — silent `getRefIRType(...) orelse TypeId.s64` fallback in reflection builtins + +## Symptom +**One-line:** The `type_name` and `type_eq` reflection builtins resolve their Type +argument's IR type via `getRefIRType(...) orelse TypeId.s64` — the forbidden +silent-type-lookup fallback (`.s64` is the exact issue-0042 sentinel the project +rules name) — so a failed must-succeed lookup silently decides "not boxed (`!= .any`)" +and mis-handles the value with no diagnostic. + +**Observed (primary — must fix):** `self.e.getRefIRType(...) orelse TypeId.s64` at: +- `src/backend/llvm/ops.zig:1023` (`.type_name` builtin — `arg_ir_ty`, gates the + `== .any` boxed-extract vs bare-i64 decision) +- `src/backend/llvm/ops.zig:1049` (`.type_eq` builtin — first operand) +- `src/backend/llvm/ops.zig:1055` (`.type_eq` builtin — second operand) + +`getRefIRType` (`src/ir/emit_llvm.zig:2229`, `?TypeId`) returns `null` only when a ref +is neither a function param nor a block instruction result — a must-not-happen case +for a real builtin argument. On `null` the code defaults to `.s64`, then tests +`arg_ir_ty == .any`; the `.s64` default silently means "treat as a bare TypeId index, +not a boxed `Any`", so a genuinely-boxed arg whose lookup failed would skip the +`ExtractValue` and use the wrong value — silent miscompile, no diagnostic. + +**Expected:** per `CLAUDE.md` REJECTED PATTERNS, a failed must-succeed type lookup +surfaces a diagnostic / hard tripwire (e.g. the `.unresolved` sentinel introduced for +issue 0074), never a real-type default. + +## Secondary (confirm — borderline) +- `src/ir/lower.zig:2527` — `.null_literal => constNull(self.target_type orelse .void)` +- `src/ir/lower.zig:2528` — `.undef_literal => constUndef(self.target_type orelse .void)` + `target_type` is a context hint that may be legitimately absent for a bare + `null`/`undef` with no expected type — this may be an INTENTIONAL default rather + than a lookup-swallow. The fix session should confirm: if a `null`/`undef` literal + reaching here without a `target_type` is actually a must-not-happen case, make it + loud; if a typeless null/undef is legitimate, leave it and add a one-line comment + stating the invariant. + +## Audited — intentional language defaults (NO action; documented so they aren't re-flagged) +- `src/ir/lower.zig:4855` — `int_literal => constInt(lit.value, info.ty orelse .s64)`: + an untyped integer literal defaulting to `s64` is standard language semantics, not a + lookup failure. +- `src/ir/lower.zig:4856` — `float_literal => constFloat(..., info.ty orelse .f64)`: + untyped float literal defaults to `f64` — language semantics. +- `src/ir/type_bridge.zig:334` — `.tag_type = tag_type orelse .s64`: documented + ("enum unions are always tagged (default i64)") — an intentional default tag type, + not a swallowed lookup. + +## Provenance / scope +Pre-existing, NOT introduced by the arch-refactor. Discovered during the **issue-0074 +fix** (the fix worker surfaced the reflection `.s64` fallbacks as a separate pattern +outside 0074's FFI-arg scope) and confirmed by a manager sweep +(`rg "orelse \.(s64|void|...)" src`). Filed per the IMPASSIBLE RULE (existing +default-returns that swallow a lookup failure → file, don't fix in place). + +## Reproduction +Latent / static (same nature as 0074): well-formed IR always gives a builtin arg a +resolvable type, so the `.s64` default can't be driven at runtime today — which is why +it's dangerous (a future IR change would silently miscompile `type_name`/`type_eq`). +Exercised by the comptime/reflection examples; the fix must keep the suite at 361/0. + +## Investigation prompt (ready to paste into a fresh session) +> In `/Users/agra/projects/sx`, the `.type_name` and `.type_eq` reflection builtins in +> `src/backend/llvm/ops.zig` (lines 1023, 1049, 1055) resolve a Type argument's IR type +> with the forbidden silent fallback `getRefIRType(...) orelse TypeId.s64`, used to gate +> a `== .any` boxed-vs-bare decision. Issue 0074 already added the shared resolver +> `LLVMEmitter.argIRTypeOrFail` (`src/ir/emit_llvm.zig`) returning the dedicated +> `.unresolved` sentinel on a failed lookup. Route these three sites through that helper +> (or a sibling) so a failed lookup yields `.unresolved` — never `.s64`; then `==.any` +> is false for `.unresolved` AND you must make the unresolved case loud (diagnostic via +> `self.diagnostics.addFmt(.err, span, ...)` or a hard tripwire), not silently "bare +> i64". Also resolve the borderline `lower.zig:2527/2528` `target_type orelse .void` +> (confirm intentional vs make-loud; comment the invariant either way). Leave the +> audited intentional defaults (`lower.zig:4855/4856`, `type_bridge.zig:334`) untouched. +> Verify: `/Users/agra/.zvm/bin/zig build && /Users/agra/.zvm/bin/zig build test && +> bash tests/run_examples.sh` stays 361/0; add a `*.test.zig` regression test asserting +> the loud `.unresolved` path for a `type_name`/`type_eq` arg with an unresolvable ref +> (fail-before/pass-after). Expected new behavior: an unresolved reflection-builtin arg +> type surfaces loudly, never silently defaults to `.s64`.