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.
This commit is contained in:
agra
2026-06-02 16:33:38 +03:00
parent 8ff24472c9
commit 877014578e
6 changed files with 163 additions and 2 deletions

View File

@@ -0,0 +1,99 @@
# 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:
```text
value = NotAType{}
```
Expected: a user-facing diagnostic rejecting `NotAType` as a type, with no
fabricated empty-struct type.
## Reproduction
```sx
#import "modules/std.sx";
NotAType :: 123;
main :: () -> s32 {
v: NotAType = ---;
print("value = {}\n", v);
return 0;
}
```
Run:
```sh
./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:
```sh
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.