ir: dedicated TypeId.unresolved sentinel; kill inferred_type => .s64

An unannotated param resolving to a plausible .s64 was the classic
silent-default trap (root of the 2.5 multi-param-closure bug). Replace it
with a dedicated TypeId.unresolved at slot 0, so a zero-initialised or
forgotten TypeId trips the sentinel instead of masquerading as a real type.

- types.zig: TypeId.unresolved = 0 (void moves to 17); TypeInfo.unresolved;
  sizeOf/toLLVMType @panic on it (codegen tripwire); hash/eql/printer cover it.
- type_bridge: inferred_type => .unresolved (was .s64).
- resolveParamType: emit "parameter 'x' has no type annotation" for a
  genuinely-unannotated value param (comptime/variadic/pack params exempt --
  they resolve via per-call substitution).
- lowerLambda: resolve unannotated params from the target closure signature;
  otherwise emit "cannot infer type of lambda parameter".
- CLAUDE.md: .void documented as an UNACCEPTABLE failed-type sentinel (it
  conflates with a real, heavily-checked type); prescribe a distinct
  .unresolved-style value + codegen tripwire.

Snapshot churn: one .ir (ffi-objc-call-06) -- the runtime type-name table and
typeof match arms renumber by the new builtin slot; program output unchanged.
This commit is contained in:
agra
2026-05-29 22:25:45 +03:00
parent 5fd513466f
commit 55e62694d1
6 changed files with 575 additions and 509 deletions

View File

@@ -93,10 +93,28 @@ everything else.
**Required:** when a lookup that *must* succeed fails, emit a
diagnostic via `self.diagnostics.addFmt(.err, span, "...", .{...})`
and return the most clearly-broken sentinel the calling code can
survive (e.g. `.void`, a `Ref.none`, or via a `?T` return that forces
the caller to handle the null). Errors must surface to the user as
text, not as a silently-corrupted size or alignment.
and return a **dedicated, unmistakable sentinel** — one that can never
be confused with a legitimate result — or a `?T` return that forces the
caller to handle the null. Errors must surface to the user as text, not
as a silently-corrupted size or alignment.
**`.void` is an UNACCEPTABLE sentinel for a failed *type* lookup.**
`void` is a real, heavily-checked type (void returns, void params, "no
value" markers), and pervasive `if (ty == .void) { skip / return-nothing }`
checks would silently swallow the failure — trading one silent default
(`.s64`) for another (`.void`) one layer down. The same objection rules
out `noreturn` (diverging expressions) and any other load-bearing builtin.
Instead, add a **distinct** `.unresolved`-style `TypeId` whose sole meaning
is "resolution failed". A dedicated value (1) can't be mistaken for a real
type by any downstream check, (2) makes the exhaustive `switch`es in the
type table fail to compile until every site handles it (forcing coverage),
and (3) can be a hard tripwire — `if (ty == .unresolved) @panic(...)` in
codegen/emit guarantees it never silently ships. The one-time plumbing
cost is exactly the trade this file mandates ("the field plumbing is a
one-time cost; silent-clobber debugging is forever"). The same principle
applies to non-type sentinels: prefer a `Ref.none`-style value that is
distinct from every valid result, not a real one that "looks broken
enough".
If you find an existing default-return in the compiler that swallows
a lookup failure, treat it as a discovered bug — file an issue per