# 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.
