# 0134 — a same-name `error` set collapses into a namespaced import's set (error sets lack per-decl nominal identity) ## Symptom One-line: a top-level `error { ... }` whose NAME matches an error set reachable through a (namespaced) import **collapses into the imported set** at registration — losing its own tags — because error-set declarations are NOT given per-decl nominal identity the way struct / enum / union are (E6a). So a local set's tags become "unknown". - **Observed:** `error: error tag 'error.Boom' is not in error set 'EventErr'` on `raise error.Boom` (and on `r == error.Boom`), where `EventErr :: error { Boom }` is declared locally but `#import "modules/std.sx"` also carries `event.EventErr` (tags `Init` / `Register` / `Wait`). The membership check sees the IMPORTED set, which has no `Boom`. - **Expected:** the local `EventErr { Boom }` is its OWN type; `Boom` is a member; the program prints `own EventErr.Boom`, exit 0 — exactly as a uniquely-named local error set already does. This is the **declaration-side** twin of issue 0132's class. The **reference-side** is already visibility-aware: `error_type_expr` (`!EventErr`) resolves its name through `Lowering.resolveName` → `resolveNominalLeaf` (own-author-wins). But that fix is **dormant** for error sets: because the local declaration never gets its own TypeId (it collapses into the import's), there is only ONE `EventErr` in the type table for the reference to find. Fixing THIS issue is what makes the reference-side resolution observable. ## Reproduction Minimal, standalone (only `modules/std.sx`). The trigger is the name `EventErr` colliding with `std/event.sx`'s `EventErr` error set: ```sx #import "modules/std.sx"; EventErr :: error { Boom } // collides with std/event.sx `EventErr { Init, Register, Wait }` fail :: () -> !EventErr { raise error.Boom; // Boom IS a member of the local set } main :: () -> i32 { r := fail(); if r == error.Boom { print("own EventErr.Boom\n"); return 0; } print("wrong set\n"); return 1; } ``` Run: `./zig-out/bin/sx run issues/0134-error-set-no-per-decl-nominal-identity-same-name-collapse.sx` Actual (today): ``` error: error tag 'error.Boom' is not in error set 'EventErr' --> ...:NN:NN | | fail :: () -> !EventErr { raise error.Boom; } | ^^^^^^^^^^ ``` (and again on `r == error.Boom`). The fix should make it print `own EventErr.Boom`, exit 0. ### Decisive bisection (verified) | Variant | Result | |---|---| | Local `EventErr` (name collides with `std/event.sx`) | **FAILS** — membership checked against the imported set | | Rename the local set `MyErr :: error { Boom }` (no collision) | **OK** — prints `own EventErr.Boom`-equivalent | So the trigger is purely the same-name collision; the local set's body (`{ Boom }`) is correct — it's simply never registered under its own identity. ## Root cause Error sets are excluded from the per-decl nominal identity system (E6a) that struct / enum / union use: - `Lowering.registerErrorSetDecl` (`src/ir/lower/nominal.zig`) registers via the FLAT `type_bridge.resolveAstType(node, …)` → `resolveInlineErrorSet` (`src/ir/type_bridge.zig`), whose first line is `if (table.findByName(name_id)) |existing| return existing;` — so the SECOND author of a name (here the local `EventErr`, registered after the imported one) just gets the first author's TypeId. No distinct nominal slot, no own tags. - Contrast `registerEnumDecl` / `registerStructDecl` / `registerUnionDecl`, which intern through `internNamedTypeDecl(decl_key, name_id, info, nominal_id)` with `nominal_id = shadowNominalId(name_id)` — each author gets a distinct TypeId. - The E6a shadow-reservation scan only enumerates struct / enum / union: `ShadowTypeDecl` (`src/ir/lower/nominal.zig`) is `union(enum) { @"struct", @"enum", @"union" }`, `topLevelTypeDecl` maps only those, and there is `reserveShadow{Struct,Enum,Union}Slot` but no error-set equivalent. So a same-name error-set shadow is never reserved up-front. - The plumbing is half-there: `nominalIdOf` / `stampNominalId` already handle the `.error_set` arm — registration just never sets a nominal id. ## Investigation prompt > A top-level `error { ... }` whose name collides with a same-name error > set from a namespaced import collapses into the imported set, so its > own tags are lost ("error tag 'X' is not in error set 'Name'"). Repro: > `issues/0134-error-set-no-per-decl-nominal-identity-same-name-collapse.sx` > (expect it to FAIL today; the fix should make it print > `own EventErr.Boom`, exit 0). > > Root cause: error sets are excluded from the per-decl nominal identity > system (E6a). `Lowering.registerErrorSetDecl` > (`src/ir/lower/nominal.zig`) registers through the flat > `type_bridge.resolveAstType` → `resolveInlineErrorSet` > (`src/ir/type_bridge.zig`), which short-circuits on > `findByName(name)` and returns the first same-name author's TypeId — > instead of interning under a per-decl nominal id like > `registerEnumDecl` does via `internNamedTypeDecl` + > `shadowNominalId`. > > Fix direction (mirror E6a for error sets): > 1. Add an `@"error_set"` variant to `ShadowTypeDecl`, an arm in > `topLevelTypeDecl`, and a `reserveShadowErrorSetSlot` (mirroring > `reserveShadowEnumSlot` — reserve a `.error_set` placeholder under > the computed `shadowNominalId`). > 2. Rewrite `registerErrorSetDecl` to build the `.error_set` `TypeInfo` > (intern the tag ids — factor the body out of `resolveInlineErrorSet` > if helpful, like `buildEnumInfo`) and intern it via > `internNamedTypeDecl(decl_key, name_id, info, nominal_id)` with > `nominal_id` from the reserved slot / `shadowNominalId`, instead of > the flat `resolveAstType`. > 3. The reference side is ALREADY visibility-aware (issue 0132's broader > fix): `resolveErrorType` (`src/ir/type_bridge.zig`) resolves a named > set through `inner.resolveName`, which for `*Lowering` is > `resolveNominalLeaf` (own-wins). Once the declaration has its own > TypeId, the named reference `!EventErr` will resolve to it > automatically — no further reference-side change needed. > > Per CLAUDE.md "Silent fallback defaults": don't paper over with a > findByName default — give error-set declarations real per-decl > identity so the wrong-author resolution stops at the source. > > Verification: the repro prints `own EventErr.Boom` exit 0; then > `zig build && zig build test` green. When resolved, promote the repro > to `examples/10xx-errors-same-name-error-set-own-wins.sx` (the example > was drafted during the 0132 broader-latent work and removed because it > could not pass until this lands). ## Notes - Membership-check diagnostic site (where the symptom surfaces, not the root cause): `src/ir/lower/expr.zig` ("error tag '...' is not in error set '...'"). - Root-cause sites: `src/ir/lower/nominal.zig` `registerErrorSetDecl` (flat registration, no nominal id) + the `ShadowTypeDecl` / `topLevelTypeDecl` / `reserveShadow*Slot` set (error sets excluded); `src/ir/type_bridge.zig` `resolveInlineErrorSet` (the `findByName` short-circuit). - Related: issue 0132 (same class, reference + payload/field side, fixed for struct/enum/union). This issue is the error-set declaration side; the 0132 reference-side `error_type_expr` fix stays in place and activates once this lands.