Files
sx/issues/0120-generic-struct-alias-head-unresolved-panic.md
agra d8076b9333 lang: rename signed integer types sN -> iN
Surface rename of the signed integer family: s1..s64 become i1..i64
(u1..u64, usize, isize unchanged). 'string' keeps the s-prefix arm in
name classification; width parsing moves to the i-prefix arm next to
isize.

Internal TypeId tags follow the surface (.s8/.s16/.s32/.s64 ->
.i8/.i16/.i32/.i64), as do mono-key mangle fragments (ptr_i64,
tu_i64_bool) and all display/diagnostic formatting (i{d}).

Migrated in the same sweep: stdlib + examples + issue repros + FFI C
companions (shared symbol names like ffi_id_i64), expected
stdout/stderr/ir snapshots, specs.md, readme.md, CLAUDE.md/AGENTS.md,
implementation_plan.md, docs/, issue writeups. Vendored stb_image and
historical flow state left untouched.

zig build test: 426/426; examples suite: 595/595.
2026-06-12 09:31:53 +03:00

162 lines
6.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 0120 — aliasing a GENERIC struct head: silent `.unresolved`, backend panic
> **RESOLVED** (2026-06-11, same session — Agra-directed fix). Root
> cause: a const alias of a generic struct head was registered nowhere
> (`type_alias_map` holds TypeIds, `struct_template_map` only direct
> struct decls), and the head selector's miss fell through as
> `.not_generic`; the `.call`-node type resolver then returned
> `.unresolved` SILENTLY (its parameterized sibling diagnosed; it
> didn't). Fix, option 1 (support): `selectGenericStructHead` now
> follows const-alias decls (`aliasedStructTemplate` in
> `src/ir/lower/nominal.zig`) — own-wins / single-flat author, each hop
> resolved from the ALIAS AUTHOR's source (`namespaceAliasVerdictFrom`
> for `ns.X` RHS), depth-capped against cycles, checked BEFORE the
> template map so a facade's same-name re-export beats an invisible
> global template. Plus the missing diagnostic: an unknown `.call` type
> head now errors "unknown type 'X'" instead of silently poisoning
> (`resolveTypeCallWithBindings`). Alias-vs-alias flat collisions stay
> loud (not-visible diagnostic). Still unsupported, by scope:
> `ns.AliasName(..)` qualified heads (namespace member that is itself
> an alias). Regression test:
> `examples/0211-generics-struct-alias-head.sx` (+ `-rich.sx` /
> `-facade.sx` companions; pins same-file alias, method, chain,
> annotation, and the cross-module facade re-export). Gates: zig build
> test 426/426 (incl. fixing the PRE-EXISTING stale
> `calls.test.zig` UFCS plan test that predated 0119's opt-in model),
> suite 587/587.
## Symptom
`Alias :: Box;` where `Box` is a generic struct (`struct ($T: Type)`)
lowers without any diagnostic, and instantiating through the alias
(`Alias(i64).{ ... }`) reaches LLVM emission with an `.unresolved`
type — the backend tripwire panics:
```
panic: unresolved type reached LLVM emission — a type resolution
failure was not diagnosed/aborted
src/backend/llvm/types.zig:175 toLLVMTypeInfo
src/backend/llvm/ops.zig:1204 emitStructInit
```
Observed (one probe family, three manifestations of the same root):
- field access through the aliased instantiation → **backend panic**
(no front-end diagnostic at all);
- method call through the aliased instantiation (`b.get()`) →
misleading `unresolved 'get'` (the receiver's type never resolved);
- cross-module re-export (`facade.sx`: `Box :: r.Box;`, consumer
flat-imports facade) → consumer gets `type 'Box' is not visible;
#import the module that declares it` even though the alias is the
facade's OWN declaration.
Expected: one of the two, decided explicitly —
1. **Support it** (desirable): a const decl whose RHS names a generic
struct head (bare `Box` or qualified `r.Box`) binds the alias to the
SAME template; instantiation, methods, and one-level flat-import
carry behave exactly as the non-generic struct alias already does.
2. **Reject it loudly**: a decl-site diagnostic ("cannot alias a
generic struct head" or similar) at `Alias :: Box;`.
Silently lowering and panicking in the backend is neither — it is the
REJECTED-PATTERNS "silent unresolved" shape.
For contrast, both of these alias re-exports already WORK across one
flat-import hop (own-decl visibility): `helper :: r.helper;` (plain
fn) and `Thing :: r.Thing;` (non-generic struct, including its static
`init`). Only the generic head breaks. A fix must not regress these.
## Reproduction
Backend panic (primary):
```sx
#import "modules/std.sx";
Box :: struct ($T: Type) {
item: T;
}
BoxAlias :: Box;
main :: () {
b := BoxAlias(i64).{ item = 3 };
print("{}\n", b.item);
}
```
Method-call variant (front-end `unresolved 'get'`, same root):
```sx
#import "modules/std.sx";
Box :: struct ($T: Type) {
item: T;
get :: (b: *Box(T)) -> T { b.item }
}
BoxAlias :: Box;
main :: () {
b := BoxAlias(i64).{ item = 3 };
print("{}\n", b.get());
}
```
Cross-module variant (`rich.sx` declares `Box`; `facade.sx` has
`r :: #import "rich.sx"; Box :: r.Box;`; a consumer flat-importing
facade.sx gets `type 'Box' is not visible` at `Box(i64).{ ... }`).
## Investigation prompt
Generic structs live as TEMPLATES in
`src/ir/program_index.zig``struct_template_map`
(`StringHashMap(StructTemplate)`, registered by `registerStructDecl`;
a parallel `struct_template_by_decl` exists but isn't read for
selection yet). Instantiation resolves the head name against that map
in `src/ir/lower/nominal.zig` (see the qualified-head comments around
nominal.zig:357382) and monomorphizes via
`lower_generic.instantiateGenericStruct` (re-exported at
`src/ir/lower.zig:1820`).
`BoxAlias :: Box;` is a const decl whose RHS identifier names a
template, not a value or a concrete Type — const-decl lowering neither
registers `BoxAlias` as a template alias nor rejects the decl. The
instantiation head lookup for `BoxAlias` then misses, and the
`Name(args).{ ... }` path continues with an `.unresolved` struct type
instead of diagnosing the miss — that silent continuation is the bug
underneath all three manifestations, and fixing it is step one
regardless of the language decision: a struct_init whose head fails to
resolve must produce a hard diagnostic, never reach emission.
Then the language decision (confirm with Agra if option 2 is ever
preferred; the motivating use case wants option 1): when a const
decl's RHS resolves to a generic struct head — bare identifier or
`ns.X` through a namespace alias — register the alias name in the
template registry bound to the same `StructTemplate`, scoped to the
declaring module with ordinary own-decl visibility so one-level
flat-import carry works (mirror whatever makes `Thing :: r.Thing;`
re-export correctly today). Mind collision semantics (own-wins /
ambiguity) and that the alias must also work as a plain type head in
annotations (`x: BoxAlias(i64)`), nested generics
(`List(BoxAlias(i64))` if applicable), and method/UFCS dispatch on
instantiations through the alias.
Motivating context: the std.sx-as-pure-re-exports restructure wants
`List :: list.List;` in `modules/std.sx` (with `list :: #import
"modules/std/list.sx";`) so `List` stays bare-visible to std.sx's flat
importers. Plain fns and plain structs already re-export this way;
generic heads are the missing piece.
Verification:
1. Primary repro: prints `3`, exit 0 (option 1) — or a clean decl-site
diagnostic, no panic (option 2).
2. Matrix: method-call variant runs (`b.get()` → 3); cross-module
variant runs through the facade; `helper :: r.helper;` and
`Thing :: r.Thing;` re-exports unchanged; two facades carrying the
same alias name still diagnose ambiguity.
3. `bash tests/run_examples.sh` — full suite ok, zero failures.
4. Pin the repro as a regression example per CLAUDE.md.