fix: give error-set decls per-decl nominal identity (issue 0134)
A local 'error { ... }' set with the same name as an imported one collapsed
onto the import, losing its own tags, because registerErrorSetDecl deduped via
the flat findByName path while struct/enum/union use E6a per-decl identity.
Build the .error_set TypeInfo (new buildErrorSetInfo helper factored from
resolveInlineErrorSet) and intern via internNamedTypeDecl with shadowNominalId;
reserve a distinct shadow slot in scanDecls; consult per-decl type_decl_tids in
namedRefTid before findByName. The inline/anonymous findByName short-circuit is
preserved.
Regression: examples/1059-errors-same-name-error-set-own-wins.sx (moved from
issues/0134).
This commit is contained in:
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
own EventErr.Boom
|
||||
@@ -1,5 +1,16 @@
|
||||
# 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
|
||||
|
||||
@@ -1745,6 +1745,7 @@ pub const Lowering = struct {
|
||||
pub const reserveShadowStructSlot = lower_nominal.reserveShadowStructSlot;
|
||||
pub const reserveShadowEnumSlot = lower_nominal.reserveShadowEnumSlot;
|
||||
pub const reserveShadowUnionSlot = lower_nominal.reserveShadowUnionSlot;
|
||||
pub const reserveShadowErrorSetSlot = lower_nominal.reserveShadowErrorSetSlot;
|
||||
pub const topLevelTypeDecl = lower_nominal.topLevelTypeDecl;
|
||||
pub const reserveShadowSlot = lower_nominal.reserveShadowSlot;
|
||||
pub const internNamedTypeDecl = lower_nominal.internNamedTypeDecl;
|
||||
|
||||
@@ -1939,7 +1939,13 @@ pub fn namedRefTid(self: *Lowering, ref: resolver_mod.RawDeclRef, name: []const
|
||||
.struct_decl => |d| (table.type_decl_tids.get(@ptrCast(d)) orelse table.findByName(table.internString(name))),
|
||||
.enum_decl => |d| (table.type_decl_tids.get(@ptrCast(d)) orelse table.findByName(table.internString(name))),
|
||||
.union_decl => |d| (table.type_decl_tids.get(@ptrCast(d)) orelse table.findByName(table.internString(name))),
|
||||
.error_set_decl, .protocol_decl, .runtime_class_decl => table.findByName(table.internString(name)),
|
||||
// Error sets now carry per-decl nominal identity (issue 0134), so prefer
|
||||
// the own author's reserved TypeId over the name-keyed first-author
|
||||
// `findByName` — mirroring the struct/enum/union arms above. A set that
|
||||
// was not decl-registered (no `type_decl_tids` entry) falls back to the
|
||||
// name lookup, byte-identical to pre-0134.
|
||||
.error_set_decl => |d| (table.type_decl_tids.get(@ptrCast(d)) orelse table.findByName(table.internString(name))),
|
||||
.protocol_decl, .runtime_class_decl => table.findByName(table.internString(name)),
|
||||
.fn_decl, .const_decl, .var_decl, .namespace_decl => null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,7 +31,20 @@ pub fn registerErrorSetDecl(self: *Lowering, node: *const Node) void {
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
// Per-decl nominal identity (E6a) — the error-set twin of `registerEnumDecl`.
|
||||
// A GENUINE same-name shadow already reserved its DISTINCT slot up-front in
|
||||
// `scanDecls` (the first at id 0, the rest at nonzero ids): reuse that id. A
|
||||
// single-author name keeps id 0 and the legacy registration. The body is built
|
||||
// by the shared `type_bridge.buildErrorSetInfo`; `internNamedTypeDecl` interns
|
||||
// it under the computed nominal id and records `decl_key → TypeId` so a local
|
||||
// `Foo :: error { Boom }` no longer collapses onto a same-name imported set
|
||||
// (issue 0134).
|
||||
const table = &self.module.types;
|
||||
const name_id = table.internString(esd.name);
|
||||
const decl_key: *const anyopaque = @ptrCast(&node.data.error_set_decl);
|
||||
const nominal_id: u32 = if (table.type_decl_tids.get(decl_key)) |id| nominalIdOf(table.get(id)) else self.shadowNominalId(name_id);
|
||||
const info = type_bridge.buildErrorSetInfo(&node.data.error_set_decl, table);
|
||||
_ = self.internNamedTypeDecl(decl_key, name_id, info, nominal_id);
|
||||
}
|
||||
|
||||
/// The `nominal_id` stamped on a nominal `TypeInfo` (0 for non-nominal /
|
||||
@@ -121,6 +134,23 @@ pub fn reserveShadowUnionSlot(self: *Lowering, ud: *const ast.UnionDecl) void {
|
||||
table.type_decl_tids.put(decl_key, reserved) catch {};
|
||||
}
|
||||
|
||||
/// Reserve a GENUINE same-name ERROR-SET shadow author's DISTINCT nominal slot
|
||||
/// up-front — the error-set twin of `reserveShadowStructSlot` (E6a). The reserved
|
||||
/// slot is an empty `.error_set` (its body — the tag id list — is not part of the
|
||||
/// intern key, only name + nominal id), so `internNamedTypeDecl` later fills the
|
||||
/// real tags via `updatePreservingKey`. Without this, a local `Foo :: error { ... }`
|
||||
/// declared after a same-name imported set would collapse onto the imported
|
||||
/// TypeId via the `findByName` first-author fallback (issue 0134).
|
||||
pub fn reserveShadowErrorSetSlot(self: *Lowering, esd: *const ast.ErrorSetDecl) void {
|
||||
const table = &self.module.types;
|
||||
const decl_key: *const anyopaque = @ptrCast(esd);
|
||||
if (table.type_decl_tids.contains(decl_key)) return;
|
||||
const name_id = table.internString(esd.name);
|
||||
const nominal_id = self.shadowNominalId(name_id);
|
||||
const reserved = table.internNominal(.{ .error_set = .{ .name = name_id, .tags = &.{} } }, nominal_id);
|
||||
table.type_decl_tids.put(decl_key, reserved) catch {};
|
||||
}
|
||||
|
||||
/// A top-level NAMED type decl the genuine-shadow scan tracks, KIND-tagged so
|
||||
/// same-name authors of DIFFERENT kinds (a `struct Foo` and an `enum Foo`) are
|
||||
/// NOT mistaken for one shadow group. Carries the stable decl pointer (the
|
||||
@@ -130,6 +160,7 @@ const ShadowTypeDecl = union(enum) {
|
||||
@"struct": *const ast.StructDecl,
|
||||
@"enum": *const ast.EnumDecl,
|
||||
@"union": *const ast.UnionDecl,
|
||||
@"error_set": *const ast.ErrorSetDecl,
|
||||
|
||||
pub fn key(self: ShadowTypeDecl) *const anyopaque {
|
||||
return switch (self) {
|
||||
@@ -159,10 +190,12 @@ pub fn topLevelTypeDecl(decl: *const Node) ?ShadowTypeDecl {
|
||||
.struct_decl => .{ .@"struct" = &decl.data.struct_decl },
|
||||
.enum_decl => .{ .@"enum" = &decl.data.enum_decl },
|
||||
.union_decl => .{ .@"union" = &decl.data.union_decl },
|
||||
.error_set_decl => .{ .@"error_set" = &decl.data.error_set_decl },
|
||||
.const_decl => |cd| switch (cd.value.data) {
|
||||
.struct_decl => .{ .@"struct" = &cd.value.data.struct_decl },
|
||||
.enum_decl => .{ .@"enum" = &cd.value.data.enum_decl },
|
||||
.union_decl => .{ .@"union" = &cd.value.data.union_decl },
|
||||
.error_set_decl => .{ .@"error_set" = &cd.value.data.error_set_decl },
|
||||
else => null,
|
||||
},
|
||||
else => null,
|
||||
@@ -175,6 +208,7 @@ pub fn reserveShadowSlot(self: *Lowering, td: ShadowTypeDecl) void {
|
||||
.@"struct" => |sd| self.reserveShadowStructSlot(sd),
|
||||
.@"enum" => |ed| self.reserveShadowEnumSlot(ed),
|
||||
.@"union" => |ud| self.reserveShadowUnionSlot(ud),
|
||||
.@"error_set" => |esd| self.reserveShadowErrorSetSlot(esd),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -591,18 +591,40 @@ pub fn buildUnionInfo(ud: *const ast.UnionDecl, table: *TypeTable, inner: anytyp
|
||||
/// interned into the global tag pool; the set stores their (sorted) ids. The
|
||||
/// caller (lowering) is responsible for rejecting an empty set, so this only
|
||||
/// sees non-empty declarations.
|
||||
///
|
||||
/// INLINE / structural path ONLY: keeps the `findByName` short-circuit so an
|
||||
/// anonymous / re-resolved set re-uses an existing same-name slot. The
|
||||
/// declaration-side per-decl nominal path (`Lowering.registerErrorSetDecl`)
|
||||
/// builds the body via `buildErrorSetInfo` and interns under its own nominal id
|
||||
/// instead — see issue 0134.
|
||||
fn resolveInlineErrorSet(esd: *const ast.ErrorSetDecl, table: *TypeTable) TypeId {
|
||||
const alloc = table.alloc;
|
||||
const name_id = table.internString(esd.name);
|
||||
|
||||
if (table.findByName(name_id)) |existing| return existing;
|
||||
|
||||
const info = buildErrorSetInfo(esd, table);
|
||||
return table.intern(info);
|
||||
}
|
||||
|
||||
/// Build the `.error_set` `TypeInfo` body for an error-set decl WITHOUT
|
||||
/// interning a top-level slot — the shared body-BUILDER behind both the
|
||||
/// structural inline path (`resolveInlineErrorSet`) and the stateful per-decl
|
||||
/// registration (`Lowering.registerErrorSetDecl`, which interns it under a
|
||||
/// per-decl nominal identity so two same-name top-level sets get DISTINCT
|
||||
/// TypeIds). Tags are interned into the global pool and stored sorted in the
|
||||
/// slice arena (mirrors `errorSetType`'s canonicalization).
|
||||
pub fn buildErrorSetInfo(esd: *const ast.ErrorSetDecl, table: *TypeTable) TypeInfo {
|
||||
const alloc = table.alloc;
|
||||
const name_id = table.internString(esd.name);
|
||||
|
||||
var tag_ids = std.ArrayList(u32).empty;
|
||||
defer tag_ids.deinit(alloc);
|
||||
for (esd.tag_names) |tn| {
|
||||
tag_ids.append(alloc, table.internTag(tn)) catch unreachable;
|
||||
}
|
||||
return table.errorSetType(name_id, tag_ids.items);
|
||||
const owned = table.slice_arena.allocator().dupe(u32, tag_ids.items) catch unreachable;
|
||||
std.mem.sort(u32, owned, {}, std.sort.asc(u32));
|
||||
return .{ .error_set = .{ .name = name_id, .tags = owned } };
|
||||
}
|
||||
|
||||
/// The error channel of a failable signature: `!Named` → the declared error
|
||||
|
||||
Reference in New Issue
Block a user