feat(types): nominal identity + key-safe TypeTable mutation, ban raw update [stdlib D]

Phase D of the unified resolver: make the TypeTable safe to key by nominal
identity before same-name type shadows land (Phase E). Behavior-preserving —
nominal_id=0 means structural (today's keying, byte-identical); single-author
names intern to the same TypeId as before.

types.zig:
- StructInfo/EnumInfo/UnionInfo/TaggedUnionInfo/ErrorSetInfo gain
  `nominal_id: u32 = 0`. hash/eql fold it into the nominal arms ONLY, and only
  when nonzero, so legacy (structural) interning hashes/compares byte-identically.
- internNominal(info, nominal_id): stamps the id into the nominal arm then
  interns; nonzero id on a non-nominal info trips an assert.
- updatePreservingKey(id, info): field-fill that asserts the intern key is
  unchanged (replaces the forward-decl stub→full pattern).
- replaceKeyedInfo(id, info): the one legitimate re-key (anon rename
  __anon → Parent.field); removes the stale key and installs the new one.
- findUniqueByName: quarantined findByName that asserts ≤1 match.
- type_decl_tids: decl-node → TypeId identity map (the fn_decl_fids analogue),
  consumed by the resolver in Phase E.

Ban raw TypeTable.update outside types.zig (the acceptance bar): every caller
in lower.zig / type_bridge.zig / protocols.zig is reclassified — forward-decl
field fills route through updatePreservingKey, qualifyAnonType's rename through
replaceKeyedInfo. The raw `update` method is removed. Inline named type-decl
registration ("current winners") routes through internNominal(info, 0).

Tests (types.test.zig): forward-decl field fill (stable key), anon rename
(re-key), generic struct instantiation, type-returning function, parameterized
protocol value struct, same display-name → distinct nominal ids, plus an
old==new assertion (internNominal(.,0) byte-identical to legacy intern),
findUniqueByName, and the type_decl_tids identity map.

Gate: zig build (0), zig build test (421/421), run_examples (477, byte-identical),
m3te ios-sim build via worktree binary (0). No shadows registered; stubs intact.
This commit is contained in:
agra
2026-06-07 13:27:10 +03:00
parent e4d58b2abb
commit 09666cb90e
5 changed files with 333 additions and 42 deletions

View File

@@ -914,7 +914,7 @@ pub const Lowering = struct {
.fields = inst_info.@"struct".fields,
} };
const alias_id = if (self.module.types.findByName(alias_name_id)) |existing| existing else self.module.types.intern(alias_info);
self.module.types.update(alias_id, alias_info);
self.module.types.updatePreservingKey(alias_id, alias_info);
}
} else if (std.mem.eql(u8, callee_name, "Vector")) {
// Builtin type constructor — checked BEFORE
@@ -951,7 +951,7 @@ pub const Lowering = struct {
.fields = inst_info.@"struct".fields,
} };
const alias_id = if (self.module.types.findByName(alias_name_id)) |existing| existing else self.module.types.intern(alias_info);
self.module.types.update(alias_id, alias_info);
self.module.types.updatePreservingKey(alias_id, alias_info);
}
} else {
// Builtin parameterised type (Vector(N, T) etc) —
@@ -13242,7 +13242,7 @@ pub const Lowering = struct {
// Register the monomorphized struct
const info: types.TypeInfo = .{ .@"struct" = .{ .name = name_id, .fields = fields.items } };
const id = if (table.findByName(name_id)) |existing| existing else table.intern(info);
table.update(id, info);
table.updatePreservingKey(id, info);
// Bind the template name to this concrete instance so a method's
// `self: *Combined` (the template name) resolves to `*Combined__s64_s64`
@@ -13347,7 +13347,7 @@ pub const Lowering = struct {
.fields = struct_fields.items,
} };
const mangled_id = if (table.findByName(mangled_name_id)) |existing| existing else table.intern(mangled_info);
table.update(mangled_id, mangled_info);
table.updatePreservingKey(mangled_id, mangled_info);
// If there's a real alias, also register under alias name and in alias map
if (has_alias) {
@@ -13357,7 +13357,7 @@ pub const Lowering = struct {
.fields = struct_fields.items,
} };
const alias_id = if (table.findByName(alias_name_id)) |existing| existing else table.intern(alias_info);
table.update(alias_id, alias_info);
table.updatePreservingKey(alias_id, alias_info);
// Store defaults if any
if (struct_decl.field_defaults.len > 0) {
@@ -13429,7 +13429,7 @@ pub const Lowering = struct {
.tag_type = .s64,
} };
const id = if (table.findByName(alias_name_id)) |existing| existing else table.intern(info);
table.update(id, info);
table.updatePreservingKey(id, info);
// Also register under mangled name
if (!std.mem.eql(u8, alias_name, mangled_name)) {
@@ -13440,7 +13440,7 @@ pub const Lowering = struct {
.tag_type = .s64,
} };
const mid = if (table.findByName(mangled_name_id)) |existing| existing else table.intern(mangled_info);
table.update(mid, mangled_info);
table.updatePreservingKey(mid, mangled_info);
}
return id;
@@ -13621,8 +13621,8 @@ pub const Lowering = struct {
// Check if a forward-reference placeholder already exists (with empty fields)
// If so, update it in-place rather than creating a duplicate
const info: types.TypeInfo = .{ .@"struct" = .{ .name = name_id, .fields = fields.items } };
const id = if (table.findByName(name_id)) |existing| existing else table.intern(info);
table.update(id, info);
const id = if (table.findByName(name_id)) |existing| existing else table.internNominal(info, 0);
table.updatePreservingKey(id, info);
// Store field defaults for struct literal lowering
if (sd.field_defaults.len > 0) {
@@ -13668,7 +13668,7 @@ pub const Lowering = struct {
if (!std.mem.eql(u8, old_name, "__anon")) return;
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ parent_name, field_name }) catch return;
const qname_id = table.internString(qualified);
table.update(ty, .{ .@"union" = .{ .name = qname_id, .fields = u.fields } });
table.replaceKeyedInfo(ty, .{ .@"union" = .{ .name = qname_id, .fields = u.fields } });
},
.tagged_union => |u| {
const old_name = table.getString(u.name);
@@ -13685,26 +13685,26 @@ pub const Lowering = struct {
const suffix = sname["__anon".len..]; // .VariantName
const sq = std.fmt.allocPrint(self.alloc, "{s}{s}", .{ qualified, suffix }) catch continue;
const sq_id = table.internString(sq);
table.update(f.ty, .{ .@"struct" = .{ .name = sq_id, .fields = finfo.@"struct".fields } });
table.replaceKeyedInfo(f.ty, .{ .@"struct" = .{ .name = sq_id, .fields = finfo.@"struct".fields } });
}
}
}
}
table.update(ty, .{ .tagged_union = .{ .name = qname_id, .fields = u.fields, .tag_type = u.tag_type, .backing_type = u.backing_type, .explicit_tag_values = u.explicit_tag_values } });
table.replaceKeyedInfo(ty, .{ .tagged_union = .{ .name = qname_id, .fields = u.fields, .tag_type = u.tag_type, .backing_type = u.backing_type, .explicit_tag_values = u.explicit_tag_values } });
},
.@"enum" => |e| {
const old_name = table.getString(e.name);
if (!std.mem.eql(u8, old_name, "__anon")) return;
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ parent_name, field_name }) catch return;
const qname_id = table.internString(qualified);
table.update(ty, .{ .@"enum" = .{ .name = qname_id, .variants = e.variants, .explicit_values = e.explicit_values } });
table.replaceKeyedInfo(ty, .{ .@"enum" = .{ .name = qname_id, .variants = e.variants, .explicit_values = e.explicit_values } });
},
.@"struct" => |s| {
const old_name = table.getString(s.name);
if (!std.mem.eql(u8, old_name, "__anon")) return;
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ parent_name, field_name }) catch return;
const qname_id = table.internString(qualified);
table.update(ty, .{ .@"struct" = .{ .name = qname_id, .fields = s.fields } });
table.replaceKeyedInfo(ty, .{ .@"struct" = .{ .name = qname_id, .fields = s.fields } });
},
else => {},
}
@@ -13758,7 +13758,7 @@ pub const Lowering = struct {
}
const struct_info: types.TypeInfo = .{ .@"struct" = .{ .name = name_id, .fields = fields.items, .is_protocol = true } };
const id = if (table.findByName(name_id)) |existing| existing else table.intern(struct_info);
table.update(id, struct_info);
table.updatePreservingKey(id, struct_info);
// Method infos resolved with the type-arg binding (T → s64).
const saved_tb = self.type_bindings;