# 0134 — a same-name `error` set collapses into a namespaced import's set (error sets lack per-decl nominal identity)

> **RESOLVED.** Error-set declarations now get the same per-decl nominal
> identity (E6a) as struct/enum/union. `registerErrorSetDecl` builds the
> `.error_set` `TypeInfo` (via a new `buildErrorSetInfo` helper factored out of
> `resolveInlineErrorSet`) and interns it through `internNamedTypeDecl` with a
> `shadowNominalId`; a `reserveShadowErrorSetSlot` reserves a distinct slot in
> `scanDecls`, and `namedRefTid`'s `.error_set_decl` arm consults the per-decl
> `type_decl_tids` before falling back to `findByName` — so a local set no
> longer collapses onto a same-name imported one. The inline/anonymous
> `findByName` short-circuit is preserved. Regression test:
> `examples/1059-errors-same-name-error-set-own-wins.sx`.

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