Files
sx/issues/0068-value-const-used-as-type-suppresses-unknown-type.md
agra 877014578e fix(ir): value const used as a type must not satisfy unknown-type check (issue 0068)
The A2.4 unknown-type pass (semantic_diagnostics) added EVERY const_decl name to
its declared-type-name set. A value const (`NotAType :: 123`) thus satisfied
reportIfUnknownType, so `v: NotAType` was not flagged; lowering then hit
TypeResolver.resolveNamed's empty-struct-stub fallback and fabricated
`NotAType{}` (the program ran, printing it).

Fix: collectDeclaredTypeNames and harvestScopeDecls now gate the const-name-add
on a new constValueIntroducesType — true only when the value introduces a type
(declarations: struct/enum/union/error; type-expression aliases: type_expr,
pointer/many-pointer/slice/optional/array/function/closure/tuple, parameterized).
`.identifier` / `.call` aliases are intentionally excluded: the scan registers
the type-valued ones into ProgramIndex.type_alias_map / the TypeTable (both
queried separately by the pass), so a value-RHS alias is correctly left out and
flagged, while a type-RHS alias stays covered by the canonical facts.

Regression: examples/1117-diagnostics-value-const-as-type-rejected.sx (exit 1).
Issue-0064 regressions 1111-1116 and the 0115 aliases stay green. Gate: zig
build, zig build test, run_examples 352/0.
2026-06-02 16:33:38 +03:00

3.8 KiB
Raw Blame History

0068 — top-level value const used as a type silently yields an empty struct

RESOLVED (2026-06-02). Root cause: the A2.4 unknown-type pass (semantic_diagnostics) inherited the issue-0064 behavior of adding EVERY const_decl name to its declared-type-name set. A value const (NotAType :: 123) thus satisfied reportIfUnknownType, so v: NotAType was not flagged; lowering then hit TypeResolver.resolveNamed's empty-struct-stub fallback and fabricated NotAType{}. Fix: collectDeclaredTypeNames and harvestScopeDecls now add a const name only when its value INTRODUCES a type — gated on a new constValueIntroducesType (type declarations: struct/enum/union/error; type-expression aliases: type_expr, pointer/many-pointer/slice/optional/array/function/closure/tuple, parameterized). .identifier / .call aliases are intentionally excluded: the scan registers the type-valued ones into ProgramIndex.type_alias_map / the TypeTable (both queried separately by the pass), so a value-RHS alias is correctly left out and flagged, while a type-RHS alias stays covered by the canonical facts. Regression test: examples/1117-diagnostics-value-const-as-type-rejected.sx (exit 1). Issue-0064 regressions 11111116 + the 0115 aliases stay green. Suite 352/0.

Symptom

A top-level value constant name is accepted in a type position and silently resolves to a fabricated empty struct.

Observed:

value = NotAType{}

Expected: a user-facing diagnostic rejecting NotAType as a type, with no fabricated empty-struct type.

Reproduction

#import "modules/std.sx";

NotAType :: 123;

main :: () -> s32 {
    v: NotAType = ---;
    print("value = {}\n", v);
    return 0;
}

Run:

./zig-out/bin/sx run .sx-tmp/probe-top-level-value-const-as-type.sx

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

Investigation prompt

Fix issue 0068: a value constant name must not satisfy the unknown-type diagnostic pass or resolve as a fabricated type when used in a type position.

Suspected area:

  • src/ir/semantic_diagnostics.zig, especially UnknownTypeChecker.collectDeclaredTypeNames and harvestScopeDecls.
  • The moved issue-0064 pass currently adds every const_decl name to the declared set. That preserves old behavior, but it means a value const like NotAType :: 123; suppresses reportIfUnknownType, then the later type resolver's unknown-name fallback interns an empty struct named NotAType.
  • Related fallback: TypeResolver.resolveNamed / type_bridge.resolveAstType still create empty struct stubs for unknown names in paths that the diagnostic pass is supposed to reject before lowering reaches codegen.

Likely fix:

  • Change collectDeclaredTypeNames / harvestScopeDecls so only declarations that actually introduce type-position names are added: struct / enum / union / error declarations, type aliases, generic templates, protocols, foreign classes, and local type declarations.
  • Do not add arbitrary value const names to the type-name set.
  • Preserve valid type alias behavior such as Alias :: u32; and local type-declaration behavior.
  • Keep the pass querying canonical facts (ProgramIndex, TypeResolver, and TypeTable) rather than reintroducing a parallel top-level truth table.

Verification:

  • Add a focused diagnostics example in the 11xx block for the repro above, expecting exit 1 and a clear diagnostic.
  • Keep issue-0064 regressions green (1111 through 1115) and keep existing alias/type-declaration examples green.
  • Run:
zig build
zig build test
bash tests/run_examples.sh

Expected result: NotAType :: 123; v: NotAType is rejected with a diagnostic, valid aliases and type declarations still resolve, and the full suite passes.