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:
@@ -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 1111–1116 + 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.
|
||||
Reference in New Issue
Block a user