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.
3.8 KiB
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 EVERYconst_declname to its declared-type-name set. A value const (NotAType :: 123) thus satisfiedreportIfUnknownType, sov: NotATypewas not flagged; lowering then hitTypeResolver.resolveNamed's empty-struct-stub fallback and fabricatedNotAType{}. Fix:collectDeclaredTypeNamesandharvestScopeDeclsnow add a const name only when its value INTRODUCES a type — gated on a newconstValueIntroducesType(type declarations: struct/enum/union/error; type-expression aliases: type_expr, pointer/many-pointer/slice/optional/array/function/closure/tuple, parameterized)..identifier/.callaliases are intentionally excluded: the scan registers the type-valued ones intoProgramIndex.type_alias_map/ theTypeTable(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 1111–1116 + the0115aliases 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, especiallyUnknownTypeChecker.collectDeclaredTypeNamesandharvestScopeDecls.- The moved issue-0064 pass currently adds every
const_declname to thedeclaredset. That preserves old behavior, but it means a value const likeNotAType :: 123;suppressesreportIfUnknownType, then the later type resolver's unknown-name fallback interns an empty struct namedNotAType. - Related fallback:
TypeResolver.resolveNamed/type_bridge.resolveAstTypestill 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/harvestScopeDeclsso 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, andTypeTable) rather than reintroducing a parallel top-level truth table.
Verification:
- Add a focused diagnostics example in the
11xxblock for the repro above, expecting exit 1 and a clear diagnostic. - Keep issue-0064 regressions green (
1111through1115) 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.