Files
sx/issues/0069-forward-identifier-type-alias-falsely-unknown.md
agra 49a383df6d fix(ir): resolve forward identifier type aliases in scanDecls (issue 0069)
scanDecls' `.identifier` alias branch registered `A :: B` into
ProgramIndex.type_alias_map only when `B` was already known (in
type_alias_map or the TypeTable). A forward target declared later
(`MyChain :: MyInt; MyInt :: s32;`) 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 as `unknown type 'MyChain'`.

Add 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 (forward alias +
forward chain). Gate: zig build, zig build test, run_examples.sh -> 353/0.
2026-06-02 16:59:20 +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 :: s32;) 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 :: s32; should resolve MyChain to s32 when used in a type annotation, matching the existing ordered-chain behavior (MyInt :: s32; MyChain :: MyInt;).

Reproduction

MyChain :: MyInt;
MyInt :: s32;

main :: () -> s32 {
    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 :: s32; 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 :: s32;.

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.