Closes the two residual silent holes in the unknown-type diagnostic:
- Nested closure / function bodies. The body walk stopped at closure and
nested-fn boundaries, so a typo'd type in a closure's local annotation
silently became a 0-field struct. `walkBodyTypes` now descends control
flow and expressions to re-enter each closure / nested fn via `checkScope`,
which accumulates that scope's generic + value-`Type` params onto the
parent's — so an inner closure still sees the outer function's `$T` (no
false positive) while a genuine unknown is flagged at any nesting depth.
`harvestScopeDecls` collects type-decl names across the whole body
(including nested scopes) up front so locals are never false-flagged.
- Cast targets. `cast(T)` where `T` is a value-`Type` param (no `$`) cast to
a fabricated empty struct silently; it now gets the tailored `$T` hint. An
unknown *literal* cast target already errors via value resolution, so it's
left to that path — no double diagnostic.
Suite: 350 passed, 0 failed. Regressions: examples/1114 (nested-closure
annotation), 1115 (cast value param).
The signature/field check missed body-level type positions: a local
annotation naming a non-existent type flowed through the empty-struct stub
untouched, so `v: Coordnate = 5` silently compiled and ran (the value
dropped) — an invalid program accepted with no diagnostic.
`checkUnknownTypeNames` now also walks each main-file function body
(`checkBodyTypes`): local var/const type annotations — including inside
if / loop / match / push / defer / onfail blocks and decl-value blocks — are
validated with the enclosing function's generic params in scope, and
body-local `T :: struct/enum/union` declarations are collected first
(`collectBodyDeclNames`) so legitimate locals aren't false-flagged. Nested
function/closure bodies are their own scope and are not descended (safe
under-coverage); explicit `cast(T)` already surfaces its own `unresolved`
diagnostic and is left to it.
Regression: examples/1113 (local annotation of a non-existent type, exit 1).
An identifier used in a type position that resolved to nothing fell through
to `type_bridge.resolveTypeName`'s empty-struct-stub fallback, silently
interning a 0-field struct named after the identifier. A value parameter
mistakenly used as a type (`(T: Type, ...) -> T`, missing the `$`) or a
typo'd type name therefore compiled and ran, rendering as `T{}`.
New post-scan diagnostic pass `checkUnknownTypeNames` (lower.zig Pass 1f)
walks every main-file function signature and non-generic struct field type
and rejects any leaf name that is not a primitive, an in-scope generic param
(`$T` / `type_params`), a declared type, or a real (non-stub) registered
type. The load-bearing empty-struct stub is left intact — forward references
and foreign-class opaque types still depend on it during the scan — and the
pass runs before body lowering, so `hasErrors()` halts the build before any
stub reaches codegen.
A value param used as a type gets a tailored hint to write `$T: Type`; a
genuine unknown gets "unknown type 'X'". Imported concrete types are
recognized via the type table, and inline compound spellings (`[:0]u8`),
arbitrary-width ints (`u1`/`u2`), and `$`-introduced generics (`-> $R`) are
exempted to avoid false positives.
Regressions: examples/1111 (tailored hint) + 1112 (typo'd field type).
Generic value-carrying failable composition works with the documented
$T: Type generic form (catch / destructure / failure-propagation / a
second monomorphization at a different T). Issue 0062 was an invalid-repro
report — it used the non-generic T: type form, which is a plain Type-valued
param, not a generic type parameter. Marked 0062 resolved (not a bug).
The only real residual: a non-$ T: Type function param used as a type
silently resolves to an empty {} (renders T{}) instead of erroring. Filed
as 0064 (deferred, orthogonal to ERR — the $T idiom works).
Regression: 1044-errors-generic-failable-composition.sx.