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)
|
# 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
|
## Symptom
|
||||||
|
|
||||||
One-line: a top-level `error { ... }` whose NAME matches an error set
|
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 reserveShadowStructSlot = lower_nominal.reserveShadowStructSlot;
|
||||||
pub const reserveShadowEnumSlot = lower_nominal.reserveShadowEnumSlot;
|
pub const reserveShadowEnumSlot = lower_nominal.reserveShadowEnumSlot;
|
||||||
pub const reserveShadowUnionSlot = lower_nominal.reserveShadowUnionSlot;
|
pub const reserveShadowUnionSlot = lower_nominal.reserveShadowUnionSlot;
|
||||||
|
pub const reserveShadowErrorSetSlot = lower_nominal.reserveShadowErrorSetSlot;
|
||||||
pub const topLevelTypeDecl = lower_nominal.topLevelTypeDecl;
|
pub const topLevelTypeDecl = lower_nominal.topLevelTypeDecl;
|
||||||
pub const reserveShadowSlot = lower_nominal.reserveShadowSlot;
|
pub const reserveShadowSlot = lower_nominal.reserveShadowSlot;
|
||||||
pub const internNamedTypeDecl = lower_nominal.internNamedTypeDecl;
|
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))),
|
.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))),
|
.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))),
|
.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,
|
.fn_decl, .const_decl, .var_decl, .namespace_decl => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,20 @@ pub fn registerErrorSetDecl(self: *Lowering, node: *const Node) void {
|
|||||||
}
|
}
|
||||||
return;
|
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 /
|
/// 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 {};
|
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
|
/// 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
|
/// 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
|
/// NOT mistaken for one shadow group. Carries the stable decl pointer (the
|
||||||
@@ -130,6 +160,7 @@ const ShadowTypeDecl = union(enum) {
|
|||||||
@"struct": *const ast.StructDecl,
|
@"struct": *const ast.StructDecl,
|
||||||
@"enum": *const ast.EnumDecl,
|
@"enum": *const ast.EnumDecl,
|
||||||
@"union": *const ast.UnionDecl,
|
@"union": *const ast.UnionDecl,
|
||||||
|
@"error_set": *const ast.ErrorSetDecl,
|
||||||
|
|
||||||
pub fn key(self: ShadowTypeDecl) *const anyopaque {
|
pub fn key(self: ShadowTypeDecl) *const anyopaque {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
@@ -159,10 +190,12 @@ pub fn topLevelTypeDecl(decl: *const Node) ?ShadowTypeDecl {
|
|||||||
.struct_decl => .{ .@"struct" = &decl.data.struct_decl },
|
.struct_decl => .{ .@"struct" = &decl.data.struct_decl },
|
||||||
.enum_decl => .{ .@"enum" = &decl.data.enum_decl },
|
.enum_decl => .{ .@"enum" = &decl.data.enum_decl },
|
||||||
.union_decl => .{ .@"union" = &decl.data.union_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) {
|
.const_decl => |cd| switch (cd.value.data) {
|
||||||
.struct_decl => .{ .@"struct" = &cd.value.data.struct_decl },
|
.struct_decl => .{ .@"struct" = &cd.value.data.struct_decl },
|
||||||
.enum_decl => .{ .@"enum" = &cd.value.data.enum_decl },
|
.enum_decl => .{ .@"enum" = &cd.value.data.enum_decl },
|
||||||
.union_decl => .{ .@"union" = &cd.value.data.union_decl },
|
.union_decl => .{ .@"union" = &cd.value.data.union_decl },
|
||||||
|
.error_set_decl => .{ .@"error_set" = &cd.value.data.error_set_decl },
|
||||||
else => null,
|
else => null,
|
||||||
},
|
},
|
||||||
else => null,
|
else => null,
|
||||||
@@ -175,6 +208,7 @@ pub fn reserveShadowSlot(self: *Lowering, td: ShadowTypeDecl) void {
|
|||||||
.@"struct" => |sd| self.reserveShadowStructSlot(sd),
|
.@"struct" => |sd| self.reserveShadowStructSlot(sd),
|
||||||
.@"enum" => |ed| self.reserveShadowEnumSlot(ed),
|
.@"enum" => |ed| self.reserveShadowEnumSlot(ed),
|
||||||
.@"union" => |ud| self.reserveShadowUnionSlot(ud),
|
.@"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
|
/// 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
|
/// caller (lowering) is responsible for rejecting an empty set, so this only
|
||||||
/// sees non-empty declarations.
|
/// 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 {
|
fn resolveInlineErrorSet(esd: *const ast.ErrorSetDecl, table: *TypeTable) TypeId {
|
||||||
const alloc = table.alloc;
|
|
||||||
const name_id = table.internString(esd.name);
|
const name_id = table.internString(esd.name);
|
||||||
|
|
||||||
if (table.findByName(name_id)) |existing| return existing;
|
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;
|
var tag_ids = std.ArrayList(u32).empty;
|
||||||
defer tag_ids.deinit(alloc);
|
defer tag_ids.deinit(alloc);
|
||||||
for (esd.tag_names) |tn| {
|
for (esd.tag_names) |tn| {
|
||||||
tag_ids.append(alloc, table.internTag(tn)) catch unreachable;
|
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
|
/// The error channel of a failable signature: `!Named` → the declared error
|
||||||
|
|||||||
Reference in New Issue
Block a user