feat(stdlib/E6a): per-decl nominal identity for enum + union decls
Give top-level ENUM and UNION decls per-decl nominal identity so two same-name flat enums/unions intern DISTINCT nominal TypeIds instead of collapsing to one global last-wins entry. Establishes the reusable non-struct register path the later E6 kind-steps (E6b error-set, E6c protocol, E6d foreign-class) extend. Registration side (was: stateless `type_bridge.resolveInlineEnum/Union` `findByName` last-wins short-circuit, no Lowering access): - Split the type_bridge inline builders into a body-BUILDER (`buildEnumInfo` / `buildUnionInfo`) + the existing thin interner wrappers (field-type positions keep the legacy single-slot path). - Add `Lowering.registerEnumDecl` / `registerUnionDecl` mirroring `registerStructDecl`: build the TypeInfo, intern via `internNamedTypeDecl(decl_key, name_id, info, nominal_id)` under the per-decl nominal identity (reserved slot id, else `shadowNominalId`). - Reroute all six enum/union registration dispatch sites (scanDecls const-wrapped + top-level, lowerDecls/comptime, block-local, local const) to the new path. Shared infra generalized ONCE: - Pass-0b genuine-shadow pre-pass now reserves struct/enum/union shadow slots of the MATCHING kind, grouped by (kind, name), via a kind-generic `topLevelTypeDecl` / `reserveShadowSlot`. A forward/self/mutual ref to a shadow name binds to the reserved nominal TypeId. - `namedRefTid` consults `type_decl_tids` for `.enum_decl`/`.union_decl` before the global `findByName`. No new per-kind resolution path: selectNominalLeaf / headTypeGate / flatTypeAuthorCount already gate every kind. Single-author / phantom-double-spelling names keep nominal_id 0 (byte-identical corpus). Regressions 0795-0798 (enum + union: ambiguity over every bare-type form, and own-wins with distinct nominal TypeIds), fail-before/pass-after: 0795/0797 exit 0 -> exit 1 with the loud "type is ambiguous" diagnostic; 0796 silently printed `own=.east` -> correct `own=.north`; 0798 hard `field 'm' not found` error -> correct `own=5 dep=9`. Gate: zig build && zig build test (423/423) && run_examples.sh (537/537) all exit 0; m3te ios-sim build via the main binary exit 0.
This commit is contained in:
37
examples/0795-modules-same-name-enum-ambiguous.sx
Normal file
37
examples/0795-modules-same-name-enum-ambiguous.sx
Normal file
@@ -0,0 +1,37 @@
|
||||
// E6a — per-decl nominal identity for ENUM decls. A bare ENUM reference is
|
||||
// non-transitive AND ambiguity-checked at every site, exactly like the struct
|
||||
// leaf (0755) and the non-leaf struct forms (0767). `main` flat-imports two
|
||||
// modules that each author a same-name `Dir` enum and authors none itself, so
|
||||
// EACH of the following bare ENUM forms is a genuine collision the source cannot
|
||||
// disambiguate — and each must emit the LOUD "type 'Dir' is ambiguous" diagnostic
|
||||
// and poison the result, NEVER silently pick a global `findByName` last-wins
|
||||
// author:
|
||||
//
|
||||
// - reflection / type-arg slot `size_of(Dir)`
|
||||
// - typed enum-value annotation `d : Dir = .north`
|
||||
// - type-as-value `t : Type = Dir`
|
||||
// - type-category match arm `case Dir:`
|
||||
//
|
||||
// Fail-before (pre-E6a): the stateless `type_bridge.resolveInlineEnum`
|
||||
// `findByName` short-circuit interned ONE global last-wins `Dir`, so every bare
|
||||
// form silently resolved to it and the program exited 0.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0795-modules-same-name-enum-ambiguous/a.sx";
|
||||
#import "0795-modules-same-name-enum-ambiguous/b.sx";
|
||||
|
||||
describe :: ($T: Type) -> s32 {
|
||||
r := if T == {
|
||||
case Dir: 1;
|
||||
else: 0;
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
sz := size_of(Dir);
|
||||
d : Dir = .north;
|
||||
t : Type = Dir;
|
||||
k := describe(s64);
|
||||
0
|
||||
}
|
||||
4
examples/0795-modules-same-name-enum-ambiguous/a.sx
Normal file
4
examples/0795-modules-same-name-enum-ambiguous/a.sx
Normal file
@@ -0,0 +1,4 @@
|
||||
// One of two flat-imported authors of a same-name `Dir` enum. With both modules
|
||||
// flat-visible from a file that authors none itself, every bare reference to the
|
||||
// name is genuinely ambiguous.
|
||||
Dir :: enum { north; south; east; west; }
|
||||
4
examples/0795-modules-same-name-enum-ambiguous/b.sx
Normal file
4
examples/0795-modules-same-name-enum-ambiguous/b.sx
Normal file
@@ -0,0 +1,4 @@
|
||||
// The second flat-imported author of a same-name `Dir` enum. A separate nominal
|
||||
// identity from a.sx's `Dir`, so each bare reference is a real collision the
|
||||
// importing source cannot disambiguate.
|
||||
Dir :: enum { north; south; east; west; }
|
||||
22
examples/0796-modules-same-name-enum-own-wins.sx
Normal file
22
examples/0796-modules-same-name-enum-own-wins.sx
Normal file
@@ -0,0 +1,22 @@
|
||||
// E6a — own-wins-over-flat for ENUM per-decl nominal identity. `main` flat-imports
|
||||
// `dep.sx` (which authors `Dir { east; west }`) AND authors its OWN `Dir { north;
|
||||
// south }`. A bare `Dir` reference in `main` resolves to `main`'s OWN author, not
|
||||
// the flat-imported one (the querying source's author wins outright — no
|
||||
// ambiguity), so `d : Dir = .north` binds `main`'s enum (whose `.north` variant
|
||||
// dep's `Dir` lacks) while `dep_dir()` returns dep's DISTINCT `Dir`.
|
||||
//
|
||||
// Fail-before (pre-E6a): the stateless `type_bridge.resolveInlineEnum` `findByName`
|
||||
// short-circuit interned ONE global last-wins `Dir`, so `main`'s `Dir` and dep's
|
||||
// `Dir` collapsed to a single nominal — `.north` would resolve against whichever
|
||||
// author won the global slot, silently wrong with no diagnostic.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0796-modules-same-name-enum-own-wins/dep.sx";
|
||||
|
||||
Dir :: enum { north; south; }
|
||||
|
||||
main :: () -> s32 {
|
||||
d : Dir = .north;
|
||||
print("own={} dep={}\n", d, dep_dir());
|
||||
0
|
||||
}
|
||||
6
examples/0796-modules-same-name-enum-own-wins/dep.sx
Normal file
6
examples/0796-modules-same-name-enum-own-wins/dep.sx
Normal file
@@ -0,0 +1,6 @@
|
||||
// A flat-imported module authors its OWN `Dir { east; west }`. The importing file
|
||||
// (`main`) ALSO authors a `Dir` — its own author must win there (own-wins), while
|
||||
// this module's `Dir` stays a DISTINCT nominal type used by `dep_dir`. The variant
|
||||
// sets are disjoint, so a cross-binding to the wrong `Dir` is a hard compile error.
|
||||
Dir :: enum { east; west; }
|
||||
dep_dir :: () -> Dir { return .west; }
|
||||
36
examples/0797-modules-same-name-union-ambiguous.sx
Normal file
36
examples/0797-modules-same-name-union-ambiguous.sx
Normal file
@@ -0,0 +1,36 @@
|
||||
// E6a — per-decl nominal identity for UNION decls. A bare UNION reference is
|
||||
// non-transitive AND ambiguity-checked at every site, exactly like the enum
|
||||
// (0795) and struct (0767) forms. `main` flat-imports two modules that each author
|
||||
// a same-name `Pair` union and authors none itself, so EACH of the following bare
|
||||
// UNION forms is a genuine collision the source cannot disambiguate — and each must
|
||||
// emit the LOUD "type 'Pair' is ambiguous" diagnostic and poison the result, NEVER
|
||||
// silently pick a global `findByName` last-wins author:
|
||||
//
|
||||
// - reflection / type-arg slot `size_of(Pair)`
|
||||
// - typed annotation `u : Pair = ---`
|
||||
// - type-as-value `t : Type = Pair`
|
||||
// - type-category match arm `case Pair:`
|
||||
//
|
||||
// Fail-before (pre-E6a): the stateless `type_bridge.resolveInlineUnion`
|
||||
// `findByName` short-circuit interned ONE global last-wins `Pair`, so every bare
|
||||
// form silently resolved to it and the program exited 0.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0797-modules-same-name-union-ambiguous/a.sx";
|
||||
#import "0797-modules-same-name-union-ambiguous/b.sx";
|
||||
|
||||
describe :: ($T: Type) -> s32 {
|
||||
r := if T == {
|
||||
case Pair: 1;
|
||||
else: 0;
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
sz := size_of(Pair);
|
||||
u : Pair = ---;
|
||||
t : Type = Pair;
|
||||
k := describe(s64);
|
||||
0
|
||||
}
|
||||
4
examples/0797-modules-same-name-union-ambiguous/a.sx
Normal file
4
examples/0797-modules-same-name-union-ambiguous/a.sx
Normal file
@@ -0,0 +1,4 @@
|
||||
// One of two flat-imported authors of a same-name `Pair` union. With both modules
|
||||
// flat-visible from a file that authors none itself, every bare reference to the
|
||||
// name is genuinely ambiguous.
|
||||
Pair :: union { f: f32; i: s32; }
|
||||
4
examples/0797-modules-same-name-union-ambiguous/b.sx
Normal file
4
examples/0797-modules-same-name-union-ambiguous/b.sx
Normal file
@@ -0,0 +1,4 @@
|
||||
// The second flat-imported author of a same-name `Pair` union. A separate nominal
|
||||
// identity from a.sx's `Pair`, so each bare reference is a real collision the
|
||||
// importing source cannot disambiguate.
|
||||
Pair :: union { f: f32; i: s32; }
|
||||
23
examples/0798-modules-same-name-union-own-wins.sx
Normal file
23
examples/0798-modules-same-name-union-own-wins.sx
Normal file
@@ -0,0 +1,23 @@
|
||||
// E6a — own-wins-over-flat for UNION per-decl nominal identity. `main` flat-imports
|
||||
// `dep.sx` (which authors `Pair { a }`) AND authors its OWN `Pair { m }`. A bare
|
||||
// `Pair` reference in `main` resolves to `main`'s OWN author, not the flat-imported
|
||||
// one (the querying source's author wins outright — no ambiguity), so `p : Pair`
|
||||
// here binds `main`'s union (whose `m` field dep's `Pair` lacks) while `dep_pair()`
|
||||
// uses dep's DISTINCT `Pair`.
|
||||
//
|
||||
// Fail-before (pre-E6a): the stateless `type_bridge.resolveInlineUnion` `findByName`
|
||||
// short-circuit interned ONE global last-wins `Pair`, so `main`'s `Pair` and dep's
|
||||
// `Pair` collapsed to a single nominal — `p.m` would resolve against whichever
|
||||
// author won the global slot, silently wrong with no diagnostic.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0798-modules-same-name-union-own-wins/dep.sx";
|
||||
|
||||
Pair :: union { m: s32; }
|
||||
|
||||
main :: () -> s32 {
|
||||
p : Pair = ---;
|
||||
p.m = 5;
|
||||
print("own={} dep={}\n", p.m, dep_pair());
|
||||
0
|
||||
}
|
||||
10
examples/0798-modules-same-name-union-own-wins/dep.sx
Normal file
10
examples/0798-modules-same-name-union-own-wins/dep.sx
Normal file
@@ -0,0 +1,10 @@
|
||||
// A flat-imported module authors its OWN `Pair { a }`. The importing file (`main`)
|
||||
// ALSO authors a `Pair` — its own author must win there (own-wins), while this
|
||||
// module's `Pair` stays a DISTINCT nominal type used by `dep_pair`. The field sets
|
||||
// are disjoint, so a cross-binding to the wrong `Pair` is a hard compile error.
|
||||
Pair :: union { a: s32; }
|
||||
dep_pair :: () -> s32 {
|
||||
p : Pair = ---;
|
||||
p.a = 9;
|
||||
return p.a;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,23 @@
|
||||
error: type 'Dir' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0795-modules-same-name-enum-ambiguous.sx:32:19
|
||||
|
|
||||
32 | sz := size_of(Dir);
|
||||
| ^^^
|
||||
|
||||
error: type 'Dir' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0795-modules-same-name-enum-ambiguous.sx:33:9
|
||||
|
|
||||
33 | d : Dir = .north;
|
||||
| ^^^
|
||||
|
||||
error: type 'Dir' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0795-modules-same-name-enum-ambiguous.sx:34:16
|
||||
|
|
||||
34 | t : Type = Dir;
|
||||
| ^^^
|
||||
|
||||
error: type 'Dir' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0795-modules-same-name-enum-ambiguous.sx:25:14
|
||||
|
|
||||
25 | case Dir: 1;
|
||||
| ^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
own=.north dep=.west
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,23 @@
|
||||
error: type 'Pair' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0797-modules-same-name-union-ambiguous.sx:31:19
|
||||
|
|
||||
31 | sz := size_of(Pair);
|
||||
| ^^^^
|
||||
|
||||
error: type 'Pair' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0797-modules-same-name-union-ambiguous.sx:32:9
|
||||
|
|
||||
32 | u : Pair = ---;
|
||||
| ^^^^
|
||||
|
||||
error: type 'Pair' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0797-modules-same-name-union-ambiguous.sx:33:16
|
||||
|
|
||||
33 | t : Type = Pair;
|
||||
| ^^^^
|
||||
|
||||
error: type 'Pair' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import
|
||||
--> examples/0797-modules-same-name-union-ambiguous.sx:24:14
|
||||
|
|
||||
24 | case Pair: 1;
|
||||
| ^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
own=5 dep=9
|
||||
244
src/ir/lower.zig
244
src/ir/lower.zig
@@ -762,9 +762,9 @@ pub const Lowering = struct {
|
||||
} else if (cd.value.data == .struct_decl) {
|
||||
self.registerStructDecl(&cd.value.data.struct_decl, decl.source_file);
|
||||
} else if (cd.value.data == .enum_decl) {
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
self.registerEnumDecl(&cd.value.data.enum_decl);
|
||||
} else if (cd.value.data == .union_decl) {
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
self.registerUnionDecl(&cd.value.data.union_decl);
|
||||
} else if (cd.value.data == .comptime_expr) {
|
||||
self.lowerComptimeGlobal(cd.name, cd.value.data.comptime_expr.expr, cd.type_annotation);
|
||||
}
|
||||
@@ -776,10 +776,10 @@ pub const Lowering = struct {
|
||||
self.registerStructDecl(&decl.data.struct_decl, decl.source_file);
|
||||
},
|
||||
.enum_decl => {
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
self.registerEnumDecl(&decl.data.enum_decl);
|
||||
},
|
||||
.union_decl => {
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
self.registerUnionDecl(&decl.data.union_decl);
|
||||
},
|
||||
.error_set_decl => {
|
||||
self.registerErrorSetDecl(decl);
|
||||
@@ -925,41 +925,44 @@ pub const Lowering = struct {
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
// Pass 0b: reserve every GENUINE same-name STRUCT shadow's DISTINCT nominal
|
||||
// slot BEFORE the registration loop resolves any fields (E2/F1). A field
|
||||
// type referencing a shadow name — self (`next: *Box`), or a forward /
|
||||
// mutual ref to a shadow declared LATER in the same module (`peer: *Node`)
|
||||
// — then binds to its OWN nominal TypeId via `type_decl_tids`, never the
|
||||
// global findByName first-author fallback (issue 0105).
|
||||
// Pass 0b: reserve every GENUINE same-name NAMED-TYPE shadow's DISTINCT
|
||||
// nominal slot BEFORE the registration loop resolves any fields (E2/F1, and
|
||||
// enum/union from E6a). A field / variant type referencing a shadow name —
|
||||
// self (`next: *Box`), or a forward / mutual ref to a shadow declared LATER
|
||||
// in the same module (`peer: *Node`) — then binds to its OWN nominal TypeId
|
||||
// via `type_decl_tids`, never the global findByName first-author fallback
|
||||
// (issue 0105).
|
||||
//
|
||||
// "Genuine" = ≥2 DISTINCT struct decls in THIS scan author the name (so it
|
||||
// needs ≥2 distinct nominal TypeIds). Gating on the scanned decls — NOT
|
||||
// `nameHasMultipleTypeAuthors` (the raw import facts, which over-count one
|
||||
// file reached via two un-normalized import spellings, e.g. `math/matrix44`
|
||||
// pulled in twice) — keeps a single-real-decl name on the legacy id-0 path,
|
||||
// byte-identical. ALL authors of a genuine shadow reserve, in declaration
|
||||
// order: the FIRST at id 0, the rest at fresh nonzero ids, matching the
|
||||
// per-decl registration order so the first-author-keeps-0 assignment holds.
|
||||
var shadow_first = std.AutoHashMap(types.StringId, *const anyopaque).init(self.alloc);
|
||||
// "Genuine" = ≥2 DISTINCT decls of the SAME KIND in THIS scan author the name
|
||||
// (so it needs ≥2 distinct nominal TypeIds). Grouping by (kind, name) keeps a
|
||||
// `struct Foo` and an `enum Foo` in separate groups — neither is a shadow of
|
||||
// the other. Gating on the scanned decls — NOT `nameHasMultipleTypeAuthors`
|
||||
// (the raw import facts, which over-count one file reached via two
|
||||
// un-normalized import spellings, e.g. `math/matrix44` pulled in twice) —
|
||||
// keeps a single-real-decl name on the legacy id-0 path, byte-identical. ALL
|
||||
// authors of a genuine shadow reserve, in declaration order: the FIRST at id
|
||||
// 0, the rest at fresh nonzero ids, matching the per-decl registration order
|
||||
// so the first-author-keeps-0 assignment holds.
|
||||
const ShadowKey = struct { kind: u8, name: types.StringId };
|
||||
var shadow_first = std.AutoHashMap(ShadowKey, *const anyopaque).init(self.alloc);
|
||||
defer shadow_first.deinit();
|
||||
var genuine_shadows = std.AutoHashMap(types.StringId, void).init(self.alloc);
|
||||
var genuine_shadows = std.AutoHashMap(ShadowKey, void).init(self.alloc);
|
||||
defer genuine_shadows.deinit();
|
||||
for (decls) |decl| {
|
||||
const sd = topLevelStructDecl(decl) orelse continue;
|
||||
if (sd.type_params.len > 0) continue;
|
||||
const nm = self.module.types.internString(sd.name);
|
||||
const key: *const anyopaque = @ptrCast(sd);
|
||||
const gop = shadow_first.getOrPut(nm) catch continue;
|
||||
const td = topLevelTypeDecl(decl) orelse continue;
|
||||
if (td.isGeneric()) continue;
|
||||
const sk = ShadowKey{ .kind = @intFromEnum(std.meta.activeTag(td)), .name = self.module.types.internString(td.name()) };
|
||||
const gop = shadow_first.getOrPut(sk) catch continue;
|
||||
if (gop.found_existing) {
|
||||
if (gop.value_ptr.* != key) genuine_shadows.put(nm, {}) catch {};
|
||||
} else gop.value_ptr.* = key;
|
||||
if (gop.value_ptr.* != td.key()) genuine_shadows.put(sk, {}) catch {};
|
||||
} else gop.value_ptr.* = td.key();
|
||||
}
|
||||
for (decls) |decl| {
|
||||
const sd = topLevelStructDecl(decl) orelse continue;
|
||||
const nm = self.module.types.internString(sd.name);
|
||||
if (!genuine_shadows.contains(nm)) continue;
|
||||
const td = topLevelTypeDecl(decl) orelse continue;
|
||||
const sk = ShadowKey{ .kind = @intFromEnum(std.meta.activeTag(td)), .name = self.module.types.internString(td.name()) };
|
||||
if (!genuine_shadows.contains(sk)) continue;
|
||||
self.setCurrentSourceFile(decl.source_file);
|
||||
self.reserveShadowStructSlot(sd);
|
||||
self.reserveShadowSlot(td);
|
||||
}
|
||||
for (decls) |decl| {
|
||||
self.setCurrentSourceFile(decl.source_file);
|
||||
@@ -999,11 +1002,11 @@ pub const Lowering = struct {
|
||||
} else if (cd.value.data == .struct_decl) {
|
||||
self.registerStructDecl(&cd.value.data.struct_decl, decl.source_file);
|
||||
} else if (cd.value.data == .enum_decl) {
|
||||
// Register enum/tagged-union types in the type table
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
// Per-decl nominal identity for enum/tagged-union types (E6a)
|
||||
self.registerEnumDecl(&cd.value.data.enum_decl);
|
||||
} else if (cd.value.data == .union_decl) {
|
||||
// Register plain union types in the type table
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
// Per-decl nominal identity for plain union types (E6a)
|
||||
self.registerUnionDecl(&cd.value.data.union_decl);
|
||||
} else if (cd.value.data == .type_expr or
|
||||
cd.value.data == .pointer_type_expr or
|
||||
cd.value.data == .many_pointer_type_expr or
|
||||
@@ -1179,12 +1182,12 @@ pub const Lowering = struct {
|
||||
self.registerStructDecl(&decl.data.struct_decl, decl.source_file);
|
||||
},
|
||||
.enum_decl => {
|
||||
// Register enum/tagged-union types in the type table
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
// Per-decl nominal identity for enum/tagged-union types (E6a)
|
||||
self.registerEnumDecl(&decl.data.enum_decl);
|
||||
},
|
||||
.union_decl => {
|
||||
// Register plain union types in the type table
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
// Per-decl nominal identity for plain union types (E6a)
|
||||
self.registerUnionDecl(&decl.data.union_decl);
|
||||
},
|
||||
.error_set_decl => {
|
||||
self.registerErrorSetDecl(decl);
|
||||
@@ -2169,14 +2172,19 @@ pub const Lowering = struct {
|
||||
/// `struct #compiler`, a protocol-backed struct, a generic instance) or before
|
||||
/// it registers; a genuine same-name SHADOW always registers through
|
||||
/// `internNamedTypeDecl` and so is in `type_decl_tids`, never reaching the
|
||||
/// fallback. enum / union / error-set / protocol / foreign-class keep the
|
||||
/// legacy `findByName` resolution (same-name shadows of those kinds are a
|
||||
/// later, orthogonal phase outside 0105's struct/alias scope).
|
||||
/// fallback. ENUM and UNION resolve the same per-decl way (E6a): registered
|
||||
/// through `internNamedTypeDecl` (`registerEnumDecl` / `registerUnionDecl`),
|
||||
/// keyed by the raw-facts decl pointer, with the `findByName` fallback for a
|
||||
/// single author registered before its slot lands. error-set / protocol /
|
||||
/// foreign-class keep the legacy `findByName` resolution (their same-name
|
||||
/// shadows are later E6 sub-steps — E6b/E6c/E6d).
|
||||
fn namedRefTid(self: *Lowering, ref: resolver_mod.RawDeclRef, name: []const u8) ?TypeId {
|
||||
const table = &self.module.types;
|
||||
return switch (ref) {
|
||||
.struct_decl => |d| (table.type_decl_tids.get(@ptrCast(d)) orelse table.findByName(table.internString(name))),
|
||||
.enum_decl, .union_decl, .error_set_decl, .protocol_decl, .foreign_class_decl => 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, .foreign_class_decl => table.findByName(table.internString(name)),
|
||||
.fn_decl, .const_decl, .namespace_decl => null,
|
||||
};
|
||||
}
|
||||
@@ -3185,9 +3193,13 @@ pub const Lowering = struct {
|
||||
self.recordLocalTypeName(sd.name);
|
||||
self.registerStructDecl(&node.data.struct_decl, node.source_file orelse self.current_source_file);
|
||||
},
|
||||
.enum_decl, .union_decl => {
|
||||
.enum_decl => {
|
||||
if (node.data.declName()) |dn| self.recordLocalTypeName(dn);
|
||||
_ = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
self.registerEnumDecl(&node.data.enum_decl);
|
||||
},
|
||||
.union_decl => {
|
||||
if (node.data.declName()) |dn| self.recordLocalTypeName(dn);
|
||||
self.registerUnionDecl(&node.data.union_decl);
|
||||
},
|
||||
.error_set_decl => {
|
||||
if (node.data.declName()) |dn| self.recordLocalTypeName(dn);
|
||||
@@ -3360,9 +3372,14 @@ pub const Lowering = struct {
|
||||
self.registerStructDecl(&cd.value.data.struct_decl, self.current_source_file);
|
||||
return;
|
||||
}
|
||||
if (cd.value.data == .enum_decl or cd.value.data == .union_decl) {
|
||||
if (cd.value.data == .enum_decl) {
|
||||
self.recordLocalTypeName(cd.name);
|
||||
_ = type_bridge.resolveAstType(cd.value, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
self.registerEnumDecl(&cd.value.data.enum_decl);
|
||||
return;
|
||||
}
|
||||
if (cd.value.data == .union_decl) {
|
||||
self.recordLocalTypeName(cd.name);
|
||||
self.registerUnionDecl(&cd.value.data.union_decl);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15034,17 +15051,6 @@ pub const Lowering = struct {
|
||||
return out;
|
||||
}
|
||||
|
||||
/// The top-level STRUCT decl a top-level node authors (a bare `struct_decl`, or
|
||||
/// a `Name :: struct {...}` const wrapper), or null. Used by the genuine-shadow
|
||||
/// scan in `scanDecls` to enumerate same-name struct authors uniformly.
|
||||
fn topLevelStructDecl(decl: *const Node) ?*const ast.StructDecl {
|
||||
return switch (decl.data) {
|
||||
.struct_decl => &decl.data.struct_decl,
|
||||
.const_decl => |cd| if (cd.value.data == .struct_decl) &cd.value.data.struct_decl else null,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Reserve a GENUINE same-name STRUCT shadow author's DISTINCT nominal slot
|
||||
/// BEFORE any field resolves, so a self / forward / mutual reference to a shadow
|
||||
/// name (`next: *Box`; `peer: *Node` where Node is a shadow declared later)
|
||||
@@ -15068,6 +15074,97 @@ pub const Lowering = struct {
|
||||
table.type_decl_tids.put(decl_key, reserved) catch {};
|
||||
}
|
||||
|
||||
/// Reserve a GENUINE same-name ENUM shadow author's DISTINCT nominal slot
|
||||
/// up-front — the enum twin of `reserveShadowStructSlot` (E6a). The reserved
|
||||
/// slot's KIND MUST match what `buildEnumInfo` will produce (a payload enum →
|
||||
/// `.tagged_union`, a payload-less enum → `.enum`), because `internNamedTypeDecl`
|
||||
/// later refreshes the body via `updatePreservingKey`, whose key-stability
|
||||
/// assert compares the FULL info tag — a struct/enum/tagged_union mismatch would
|
||||
/// trip it. The empty body and placeholder `tag_type` are not part of the intern
|
||||
/// key (name + nominal id only), so the real body fills in freely.
|
||||
fn reserveShadowEnumSlot(self: *Lowering, ed: *const ast.EnumDecl) void {
|
||||
const table = &self.module.types;
|
||||
const decl_key: *const anyopaque = @ptrCast(ed);
|
||||
if (table.type_decl_tids.contains(decl_key)) return;
|
||||
const name_id = table.internString(ed.name);
|
||||
const nominal_id = self.shadowNominalId(name_id);
|
||||
const empty: types.TypeInfo = if (ed.variant_types.len > 0)
|
||||
.{ .tagged_union = .{ .name = name_id, .fields = &.{}, .tag_type = .s64 } }
|
||||
else
|
||||
.{ .@"enum" = .{ .name = name_id, .variants = &.{} } };
|
||||
const reserved = table.internNominal(empty, nominal_id);
|
||||
table.type_decl_tids.put(decl_key, reserved) catch {};
|
||||
}
|
||||
|
||||
/// Reserve a GENUINE same-name UNION shadow author's DISTINCT nominal slot
|
||||
/// up-front — the union twin of `reserveShadowStructSlot` (E6a).
|
||||
fn reserveShadowUnionSlot(self: *Lowering, ud: *const ast.UnionDecl) void {
|
||||
const table = &self.module.types;
|
||||
const decl_key: *const anyopaque = @ptrCast(ud);
|
||||
if (table.type_decl_tids.contains(decl_key)) return;
|
||||
const name_id = table.internString(ud.name);
|
||||
const nominal_id = self.shadowNominalId(name_id);
|
||||
const reserved = table.internNominal(.{ .@"union" = .{ .name = name_id, .fields = &.{} } }, 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
|
||||
/// `decl_key` / raw-facts identity) so the scan de-dups by decl identity, and
|
||||
/// dispatches the per-kind reservation. Later E6 sub-steps add their kind here.
|
||||
const ShadowTypeDecl = union(enum) {
|
||||
@"struct": *const ast.StructDecl,
|
||||
@"enum": *const ast.EnumDecl,
|
||||
@"union": *const ast.UnionDecl,
|
||||
|
||||
fn key(self: ShadowTypeDecl) *const anyopaque {
|
||||
return switch (self) {
|
||||
inline else => |p| @ptrCast(p),
|
||||
};
|
||||
}
|
||||
fn name(self: ShadowTypeDecl) []const u8 {
|
||||
return switch (self) {
|
||||
inline else => |p| p.name,
|
||||
};
|
||||
}
|
||||
fn isGeneric(self: ShadowTypeDecl) bool {
|
||||
return switch (self) {
|
||||
.@"struct" => |p| p.type_params.len > 0,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Classify a top-level node as the NAMED type decl it authors — a bare
|
||||
/// `struct`/`enum`/`union` node, or a `const_decl` whose value is one — so the
|
||||
/// genuine-shadow scan enumerates all three kinds uniformly. Null when the node
|
||||
/// is not a struct/enum/union author. The shared infra E6b/E6c extend by adding
|
||||
/// their kind here.
|
||||
fn topLevelTypeDecl(decl: *const Node) ?ShadowTypeDecl {
|
||||
return switch (decl.data) {
|
||||
.struct_decl => .{ .@"struct" = &decl.data.struct_decl },
|
||||
.enum_decl => .{ .@"enum" = &decl.data.enum_decl },
|
||||
.union_decl => .{ .@"union" = &decl.data.union_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 },
|
||||
else => null,
|
||||
},
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Dispatch a genuine-shadow reservation to the matching per-kind reserver.
|
||||
fn reserveShadowSlot(self: *Lowering, td: ShadowTypeDecl) void {
|
||||
switch (td) {
|
||||
.@"struct" => |sd| self.reserveShadowStructSlot(sd),
|
||||
.@"enum" => |ed| self.reserveShadowEnumSlot(ed),
|
||||
.@"union" => |ud| self.reserveShadowUnionSlot(ud),
|
||||
}
|
||||
}
|
||||
|
||||
/// Register (or re-register) a top-level NAMED type decl under a per-source
|
||||
/// nominal identity (E2), returning its TypeId. `decl_key` is the decl's
|
||||
/// stable pointer (the import raw-facts identity); `info` carries the full
|
||||
@@ -15487,6 +15584,37 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a top-level ENUM decl under a per-decl nominal identity (E6a) —
|
||||
/// the enum twin of `registerStructDecl`. A GENUINE same-name shadow already
|
||||
/// reserved its DISTINCT slot up-front in `scanDecls` (the first at id 0, the
|
||||
/// rest at nonzero ids), so a forward / self / mutual reference to the shadow
|
||||
/// name already bound to ITS nominal TypeId via `type_decl_tids`: reuse that
|
||||
/// reserved id. A single-author name (or one over-counted by the raw facts but
|
||||
/// not a genuine scanned shadow) was NOT reserved — it keeps id 0 and the legacy
|
||||
/// post-build registration, byte-identical to pre-E6a. The body is built once by
|
||||
/// the shared `type_bridge.buildEnumInfo`; `internNamedTypeDecl` interns it under
|
||||
/// the computed nominal id and records `decl_key → TypeId` so `namedRefTid`
|
||||
/// resolves bare references to this exact author.
|
||||
fn registerEnumDecl(self: *Lowering, ed: *const ast.EnumDecl) void {
|
||||
const table = &self.module.types;
|
||||
const name_id = table.internString(ed.name);
|
||||
const decl_key: *const anyopaque = @ptrCast(ed);
|
||||
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.buildEnumInfo(ed, table, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
_ = self.internNamedTypeDecl(decl_key, name_id, info, nominal_id);
|
||||
}
|
||||
|
||||
/// Register a top-level UNION decl under a per-decl nominal identity (E6a) —
|
||||
/// the union twin of `registerEnumDecl` / `registerStructDecl`.
|
||||
fn registerUnionDecl(self: *Lowering, ud: *const ast.UnionDecl) void {
|
||||
const table = &self.module.types;
|
||||
const name_id = table.internString(ud.name);
|
||||
const decl_key: *const anyopaque = @ptrCast(ud);
|
||||
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.buildUnionInfo(ud, table, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
_ = self.internNamedTypeDecl(decl_key, name_id, info, nominal_id);
|
||||
}
|
||||
|
||||
/// Rename an __anon type to a qualified name: ParentStruct.field_name
|
||||
/// Also renames variant payload struct types from __anon.X to ParentStruct.field_name.X
|
||||
fn qualifyAnonType(self: *Lowering, table: *types.TypeTable, ty: TypeId, parent_name: []const u8, field_name: []const u8) void {
|
||||
|
||||
@@ -331,13 +331,31 @@ fn resolveParameterizedType(pt: *const ast.ParameterizedTypeExpr, table: *TypeTa
|
||||
|
||||
// ── Inline type declarations ─────────────────────────────────────────
|
||||
|
||||
/// Stateless inline-enum resolution for a FIELD-type position (`x: enum {...}`):
|
||||
/// the legacy `findByName` short-circuit keeps a single global slot per display
|
||||
/// name. The TOP-LEVEL per-decl nominal identity path (`Lowering.registerEnumDecl`)
|
||||
/// shares the body via `buildEnumInfo` but interns under its own nominal id.
|
||||
fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable, alias_map: AliasMap, consts: ConstMap) TypeId {
|
||||
const name_id = table.internString(ed.name);
|
||||
if (table.findByName(name_id)) |existing| return existing;
|
||||
const info = buildEnumInfo(ed, table, alias_map, consts);
|
||||
const id = table.internNominal(info, 0);
|
||||
table.updatePreservingKey(id, info);
|
||||
return id;
|
||||
}
|
||||
|
||||
/// Build the `TypeInfo` body for an enum decl WITHOUT interning the top-level
|
||||
/// nominal slot — the shared body-BUILDER behind both the stateless inline
|
||||
/// field-type path (`resolveInlineEnum`) and the stateful per-decl registration
|
||||
/// (`Lowering.registerEnumDecl`, which interns it under a per-decl nominal
|
||||
/// identity so two same-name top-level enums get DISTINCT TypeIds). A payload
|
||||
/// enum builds a `.tagged_union`; a payload-less enum a plain `.enum`. Nested
|
||||
/// payload structs / variant field types ARE interned here — they are distinct
|
||||
/// nested nominals, not the enum's own identity.
|
||||
pub fn buildEnumInfo(ed: *const ast.EnumDecl, table: *TypeTable, alias_map: AliasMap, consts: ConstMap) TypeInfo {
|
||||
const alloc = table.alloc;
|
||||
const name_id = table.internString(ed.name);
|
||||
|
||||
// Check if already registered
|
||||
if (table.findByName(name_id)) |existing| return existing;
|
||||
|
||||
// Enum with payloads → tagged union
|
||||
const has_payloads = ed.variant_types.len > 0;
|
||||
if (has_payloads) {
|
||||
@@ -417,16 +435,13 @@ fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable, alias_map: Alia
|
||||
explicit_tag_vals = vals.items;
|
||||
}
|
||||
|
||||
const info: TypeInfo = .{ .tagged_union = .{
|
||||
return .{ .tagged_union = .{
|
||||
.name = name_id,
|
||||
.fields = fields.items,
|
||||
.tag_type = tag_type orelse .s64, // enum unions are always tagged (default i64)
|
||||
.backing_type = backing_type,
|
||||
.explicit_tag_values = explicit_tag_vals,
|
||||
} };
|
||||
const id = table.internNominal(info, 0);
|
||||
table.updatePreservingKey(id, info);
|
||||
return id;
|
||||
}
|
||||
|
||||
// Plain enum (no payloads)
|
||||
@@ -475,16 +490,13 @@ fn resolveInlineEnum(ed: *const ast.EnumDecl, table: *TypeTable, alias_map: Alia
|
||||
}
|
||||
}
|
||||
|
||||
const info: TypeInfo = .{ .@"enum" = .{
|
||||
return .{ .@"enum" = .{
|
||||
.name = name_id,
|
||||
.variants = variants.items,
|
||||
.is_flags = ed.is_flags,
|
||||
.explicit_values = explicit_vals,
|
||||
.backing_type = enum_backing,
|
||||
} };
|
||||
const id = table.internNominal(info, 0);
|
||||
table.updatePreservingKey(id, info);
|
||||
return id;
|
||||
}
|
||||
|
||||
fn resolveInlineStruct(sd: *const ast.StructDecl, table: *TypeTable, alias_map: AliasMap, consts: ConstMap) TypeId {
|
||||
@@ -510,12 +522,26 @@ fn resolveInlineStruct(sd: *const ast.StructDecl, table: *TypeTable, alias_map:
|
||||
return id;
|
||||
}
|
||||
|
||||
/// Stateless inline-union resolution for a FIELD-type position. The TOP-LEVEL
|
||||
/// per-decl nominal identity path (`Lowering.registerUnionDecl`) shares the body
|
||||
/// via `buildUnionInfo` but interns under its own nominal id.
|
||||
fn resolveInlineUnion(ud: *const ast.UnionDecl, table: *TypeTable, alias_map: AliasMap, consts: ConstMap) TypeId {
|
||||
const name_id = table.internString(ud.name);
|
||||
if (table.findByName(name_id)) |existing| return existing;
|
||||
const info = buildUnionInfo(ud, table, alias_map, consts);
|
||||
const id = table.internNominal(info, 0);
|
||||
table.updatePreservingKey(id, info);
|
||||
return id;
|
||||
}
|
||||
|
||||
/// Build the `TypeInfo` body for a union decl WITHOUT interning the top-level
|
||||
/// nominal slot — the shared body-BUILDER behind both the stateless inline
|
||||
/// field-type path (`resolveInlineUnion`) and the stateful per-decl registration
|
||||
/// (`Lowering.registerUnionDecl`).
|
||||
pub fn buildUnionInfo(ud: *const ast.UnionDecl, table: *TypeTable, alias_map: AliasMap, consts: ConstMap) TypeInfo {
|
||||
const alloc = table.alloc;
|
||||
const name_id = table.internString(ud.name);
|
||||
|
||||
if (table.findByName(name_id)) |existing| return existing;
|
||||
|
||||
var fields = std.ArrayList(TypeInfo.StructInfo.Field).empty;
|
||||
for (ud.field_names, ud.field_types) |fname, ftype_node| {
|
||||
const field_ty = resolveAstType(ftype_node, table, alias_map, consts);
|
||||
@@ -524,13 +550,10 @@ fn resolveInlineUnion(ud: *const ast.UnionDecl, table: *TypeTable, alias_map: Al
|
||||
.ty = field_ty,
|
||||
}) catch unreachable;
|
||||
}
|
||||
const info: TypeInfo = .{ .@"union" = .{
|
||||
return .{ .@"union" = .{
|
||||
.name = name_id,
|
||||
.fields = fields.items,
|
||||
} };
|
||||
const id = table.internNominal(info, 0);
|
||||
table.updatePreservingKey(id, info);
|
||||
return id;
|
||||
}
|
||||
|
||||
/// `Foo :: error { A, B }` → a registered `.error_set` type. Tag names are
|
||||
|
||||
Reference in New Issue
Block a user