Files
sx/issues/0069-forward-identifier-type-alias-falsely-unknown.md
agra d8076b9333 lang: rename signed integer types sN -> iN
Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.

Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).

Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.

zig build test: 426/426; examples suite: 595/595.
2026-06-12 09:31:53 +03:00

4.4 KiB

0069 — forward identifier type alias is falsely rejected by unknown-type pass

RESOLVED. Root cause: Lowering.scanDecls' .identifier alias branch only registered A :: B into ProgramIndex.type_alias_map when B was already known (in type_alias_map or the TypeTable). A forward target declared later (MyChain :: MyInt; MyInt :: i32;) was never present during the single forward scan, so the alias name went unregistered and the A2.4 unknown-type pass — which treats type_alias_map keys as declared types — flagged its uses. Fix: added a fixpoint post-pass resolveForwardIdentifierAliases at the end of scanDecls that re-resolves identifier-RHS aliases until no progress, after every top-level name has been seen. A value const is never an .identifier node and an alias whose target is a value const still misses both lookups, so issue 0068's value-const rejection is preserved. Regression: examples/0132-types-forward-type-alias.sx.

Symptom

A forward-referenced identifier type alias is rejected as an unknown type, even though the same alias chain works when ordered after its target.

Observed: MyChain is diagnosed as an unknown type.

Expected: MyChain :: MyInt; MyInt :: i32; should resolve MyChain to i32 when used in a type annotation, matching the existing ordered-chain behavior (MyInt :: i32; MyChain :: MyInt;).

Reproduction

MyChain :: MyInt;
MyInt :: i32;

main :: () -> i32 {
    v: MyChain = 7;
    return v;
}

Run:

./zig-out/bin/sx run .sx-tmp/probe-0068-forward-alias.sx

Observed output:

error: unknown type 'MyChain'
  --> .sx-tmp/probe-0068-forward-alias.sx:7:8
   |
 7 |     v: MyChain = 7;
   |        ^^^^^^^

The repro is standalone; the inline source above is sufficient to recreate the scratch file under .sx-tmp/.

Investigation prompt

Fix issue 0069: a forward-referenced identifier type alias must not be falsely rejected by the A2.4 unknown-type diagnostic pass.

Context:

  • This surfaced while re-reviewing 8770145, the issue-0068 fix. That fix correctly stopped arbitrary value consts (NotAType :: 123) from satisfying the unknown-type check.
  • Ordered identifier aliases still work: MyInt :: i32; MyChain :: MyInt;.
  • .call type aliases still work: Vec3 :: Vec(3, f32); and Foo :: Complex(u32);.
  • The failing shape is specifically a forward identifier alias: MyChain :: MyInt; MyInt :: i32;.

Suspected area:

  • src/ir/lower.zig, Lowering.scanDecls, especially the .identifier alias branch for const_decl values. It only inserts cd.name into ProgramIndex.type_alias_map if the RHS is already in type_alias_map or already registered in the TypeTable. A forward target is not present yet, so the alias name is never recorded.
  • src/ir/semantic_diagnostics.zig, UnknownTypeChecker.collectDeclaredTypeNames / reportIfUnknownType. After issue 0068, .identifier aliases are intentionally excluded from constValueIntroducesType and are supposed to be covered by canonical facts (ProgramIndex.type_alias_map / TypeTable). Because the forward alias never reaches those facts, the checker flags the alias as unknown.

Likely fix:

  • Do not reintroduce the issue-0068 bug by adding all .identifier const names to the declared-type set.
  • Instead, make identifier aliases converge through canonical alias facts even when the RHS is declared later. A small two-pass alias registration/resolution in scanDecls, or an explicit pending-alias graph that resolves after all top-level declarations are scanned, would keep ProgramIndex.type_alias_map authoritative without accepting value constants.
  • Preserve value const rejection: NotAType :: 123; v: NotAType must continue to emit unknown type 'NotAType'.
  • Preserve ordered/chained aliases and type-returning call aliases: examples/0116-types-type-alias-size-align.sx, examples/0201-generics-generic-struct.sx, and examples/1117-diagnostics-value-const-as-type-rejected.sx.

Verification:

  • Add a focused regression for the repro above, likely in the 01xx types block because the desired behavior is successful alias resolution.
  • Run the new regression and the existing alias/diagnostic guards:
zig build
zig build test
bash tests/run_examples.sh

Expected result: the forward alias program compiles/runs (returning 7), the issue-0068 value-const case still fails with a diagnostic, and the full suite passes.