refactor(B4.2): move nominal-type registration to lower/nominal.zig
Verbatim relocation of the 23-method nominal-type cluster (struct/enum/ union/error-set registration, anon-type qualification, nominal-id stamping, shadow-slot reservation, named-type interning, generic struct templates + alias registration) plus the nested ShadowTypeDecl union into src/ir/lower/nominal.zig. 23 aliases on Lowering keep all call sites unchanged. Method pub-flip: instantiateGenericStruct. nominal.zig reaches VisibleStructAuthor and structDeclOfRaw (both relocated to decl.zig in B4.1) via Lowering-namespace alias consts. Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero expected/ snapshot churn.
This commit is contained in:
723
src/ir/lower.zig
723
src/ir/lower.zig
@@ -38,6 +38,7 @@ const lower_comptime = @import("lower/comptime.zig");
|
||||
const lower_stmt = @import("lower/stmt.zig");
|
||||
const lower_control_flow = @import("lower/control_flow.zig");
|
||||
const lower_decl = @import("lower/decl.zig");
|
||||
const lower_nominal = @import("lower/nominal.zig");
|
||||
|
||||
const TypeId = types.TypeId;
|
||||
const StringId = types.StringId;
|
||||
@@ -9263,7 +9264,7 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn instantiateGenericStruct(self: *Lowering, tmpl: *const StructTemplate, args: []const *const Node) TypeId {
|
||||
pub fn instantiateGenericStruct(self: *Lowering, tmpl: *const StructTemplate, args: []const *const Node) TypeId {
|
||||
const table = &self.module.types;
|
||||
|
||||
// Build mangled name dynamically: StructName__arg1_arg2
|
||||
@@ -9666,701 +9667,6 @@ pub const Lowering = struct {
|
||||
|
||||
// ── Type registration ───────────────────────────────────────────
|
||||
|
||||
/// Register a struct declaration's fields and methods in the IR type table.
|
||||
/// Register a `Foo :: error { A, B }` declaration as an error-set type.
|
||||
/// Rejects an empty set here (sema gate) since type_bridge has no
|
||||
/// diagnostics; non-empty sets are interned via type_bridge.
|
||||
pub fn registerErrorSetDecl(self: *Lowering, node: *const Node) void {
|
||||
const esd = node.data.error_set_decl;
|
||||
if (esd.tag_names.len == 0) {
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, node.span, "error set '{s}' must declare at least one tag", .{esd.name});
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
}
|
||||
|
||||
/// The `nominal_id` stamped on a nominal `TypeInfo` (0 for non-nominal /
|
||||
/// structural). Reading it back lets a re-registration preserve the slot's
|
||||
/// existing key when refreshing a forward-stubbed body.
|
||||
fn nominalIdOf(info: types.TypeInfo) u32 {
|
||||
return switch (info) {
|
||||
.@"struct" => |s| s.nominal_id,
|
||||
.@"enum" => |e| e.nominal_id,
|
||||
.@"union" => |u| u.nominal_id,
|
||||
.tagged_union => |u| u.nominal_id,
|
||||
.error_set => |e| e.nominal_id,
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/// Return `info` with its nominal arm's `nominal_id` set to `nid` (a no-op for
|
||||
/// non-nominal infos). Used to build the key-matching body for
|
||||
/// `updatePreservingKey` after a shadow author interned at a nonzero id.
|
||||
fn stampNominalId(info: types.TypeInfo, nid: u32) types.TypeInfo {
|
||||
var out = info;
|
||||
switch (out) {
|
||||
.@"struct" => |*s| s.nominal_id = nid,
|
||||
.@"enum" => |*e| e.nominal_id = nid,
|
||||
.@"union" => |*u| u.nominal_id = nid,
|
||||
.tagged_union => |*u| u.nominal_id = nid,
|
||||
.error_set => |*e| e.nominal_id = nid,
|
||||
else => {},
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// 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)
|
||||
/// binds to ITS nominal TypeId via `type_decl_tids` instead of the global
|
||||
/// findByName first-author fallback (issue 0105 / F1). Called only from the
|
||||
/// `scanDecls` genuine-shadow pass, which has already established that ≥2
|
||||
/// distinct struct decls author this name; ALL of them reserve — the FIRST at
|
||||
/// id 0, the rest at fresh nonzero ids — so none falls through to the name-only
|
||||
/// `findByName` (which, once a shadow is interned, no longer uniquely identifies
|
||||
/// the first author). Idempotent per decl key: an already-reserved decl returns
|
||||
/// before re-invoking `shadowNominalId`, so the shadow id is computed once.
|
||||
/// Generic templates resolve lazily on instantiation and are skipped.
|
||||
fn reserveShadowStructSlot(self: *Lowering, sd: *const ast.StructDecl) void {
|
||||
if (sd.type_params.len > 0) return;
|
||||
const table = &self.module.types;
|
||||
const decl_key: *const anyopaque = @ptrCast(sd);
|
||||
if (table.type_decl_tids.contains(decl_key)) return;
|
||||
const name_id = table.internString(sd.name);
|
||||
const nominal_id = self.shadowNominalId(name_id); // 0 for the first author, nonzero for the rest
|
||||
const reserved = table.internNominal(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } }, nominal_id);
|
||||
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,
|
||||
|
||||
pub fn key(self: ShadowTypeDecl) *const anyopaque {
|
||||
return switch (self) {
|
||||
inline else => |p| @ptrCast(p),
|
||||
};
|
||||
}
|
||||
pub fn name(self: ShadowTypeDecl) []const u8 {
|
||||
return switch (self) {
|
||||
inline else => |p| p.name,
|
||||
};
|
||||
}
|
||||
pub 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.
|
||||
pub 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.
|
||||
pub 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
|
||||
/// body; `nominal_id` is the slot's identity (0 for a single / first author,
|
||||
/// nonzero for a later same-name shadow) — computed once by the caller
|
||||
/// (`registerStructDecl`), which reuses the id reserved up-front in `scanDecls`
|
||||
/// for a genuine shadow (so its fields' self / forward / mutual refs already
|
||||
/// resolved against it). This stamps the id and records the `decl_key → TypeId`
|
||||
/// map (`type_decl_tids`, the `fn_decl_fids` analogue).
|
||||
///
|
||||
/// A `nominal_id == 0` author adopts any forward-reference stub (`findByName`
|
||||
/// orelse intern) — BYTE-IDENTICAL to pre-E2 registration. For a genuinely
|
||||
/// multi-authored name, the FIRST source keeps id 0 and later sources get
|
||||
/// fresh ids → DISTINCT TypeIds, so the authors no longer collapse last-wins
|
||||
/// (issue 0105). Idempotent per `decl_key`: a re-registration — OR an up-front
|
||||
/// shadow reservation — reuses the recorded slot, refreshing its body via
|
||||
/// `updatePreservingKey` (key-stable because a struct's intern key is its
|
||||
/// name + nominal id, not its fields).
|
||||
fn internNamedTypeDecl(self: *Lowering, decl_key: *const anyopaque, name_id: types.StringId, info: types.TypeInfo, nominal_id: u32) TypeId {
|
||||
const table = &self.module.types;
|
||||
// Slot already recorded (re-registration, or a reserve-before-fields shadow
|
||||
// reservation) → reuse its slot + nominal id, refresh the body.
|
||||
if (table.type_decl_tids.get(decl_key)) |existing_id| {
|
||||
table.updatePreservingKey(existing_id, stampNominalId(info, nominalIdOf(table.get(existing_id))));
|
||||
return existing_id;
|
||||
}
|
||||
const id = if (nominal_id == 0)
|
||||
(table.findByName(name_id) orelse table.internNominal(info, 0))
|
||||
else
|
||||
table.internNominal(info, nominal_id);
|
||||
const stamped = stampNominalId(info, nominal_id);
|
||||
// A self / mutual `*Name` field in an enum/union body forward-creates a
|
||||
// STRUCT placeholder under `Name` (the stateless resolver has no kind
|
||||
// context — `type_resolver.resolveNamed` always stubs a struct), which the
|
||||
// `findByName` above then returns. Adopting a wrong-kind stub needs a
|
||||
// re-key, NOT the in-place `updatePreservingKey` body-fill — whose
|
||||
// kind-stability assert trips on struct→enum/union.
|
||||
if (adoptsForwardStructStub(table.get(id), stamped))
|
||||
table.replaceKeyedInfo(id, stamped)
|
||||
else
|
||||
table.updatePreservingKey(id, stamped);
|
||||
table.type_decl_tids.put(decl_key, id) catch {};
|
||||
return id;
|
||||
}
|
||||
|
||||
/// TRUE when `existing` is a forward-reference STRUCT placeholder (empty
|
||||
/// fields — the stateless resolver's stub for an as-yet-unregistered name) and
|
||||
/// `incoming` is a NON-struct nominal (enum / union / tagged_union): the one
|
||||
/// case where `internNamedTypeDecl` must re-key the slot rather than fill its
|
||||
/// body in place. A struct adopting its own struct stub is same-kind and stays
|
||||
/// on `updatePreservingKey`; a fresh-interned slot has no stub to adopt.
|
||||
fn adoptsForwardStructStub(existing: types.TypeInfo, incoming: types.TypeInfo) bool {
|
||||
if (existing != .@"struct" or existing.@"struct".fields.len != 0) return false;
|
||||
return switch (incoming) {
|
||||
.@"enum", .@"union", .tagged_union => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// The `nominal_id` to register a NAMED type author of `name_id` under. 0
|
||||
/// unless `name_id` is authored as a named type by ≥2 distinct modules (a real
|
||||
/// same-name shadow per the import facts): the FIRST source to register keeps
|
||||
/// 0, each later source gets a fresh monotonic id. Gating on the import facts
|
||||
/// keeps the single-author path at id 0 (byte-identical) even when one logical
|
||||
/// type is re-registered from several `current_source_file` contexts.
|
||||
fn shadowNominalId(self: *Lowering, name_id: types.StringId) u32 {
|
||||
if (!self.nameHasMultipleTypeAuthors(self.module.types.getString(name_id))) return 0;
|
||||
const src = self.current_source_file orelse self.main_file orelse "";
|
||||
const gop = self.nominal_name_authors.getOrPut(name_id) catch return 0;
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = src;
|
||||
return 0;
|
||||
}
|
||||
if (std.mem.eql(u8, gop.value_ptr.*, src)) return 0;
|
||||
self.next_nominal_id += 1;
|
||||
return self.next_nominal_id;
|
||||
}
|
||||
|
||||
/// TRUE iff `name` is authored AS A NAMED TYPE (struct / enum / union /
|
||||
/// error-set / protocol / foreign class) by ≥2 DISTINCT modules in the import
|
||||
/// raw facts — the authoritative same-name-shadow signal (the only case where
|
||||
/// distinct `nominal_id`s are needed). Module distinctness is by LEXICALLY
|
||||
/// NORMALIZED path: one logical file reached through several spellings
|
||||
/// (`testpkg/../allocators.sx` vs `allocators.sx`) is cached — and so parsed —
|
||||
/// twice, landing two `module_decls` entries with two decl pointers for the
|
||||
/// SAME source; normalizing collapses them to one author, NOT a false shadow.
|
||||
/// False when the facts are unwired (comptime / registration host with no
|
||||
/// `module_decls`): the single-author path applies, correct there.
|
||||
fn nameHasMultipleTypeAuthors(self: *Lowering, name: []const u8) bool {
|
||||
const decls = self.program_index.module_decls orelse return false;
|
||||
var first_norm: ?[]const u8 = null;
|
||||
defer if (first_norm) |f| self.alloc.free(f);
|
||||
var it = decls.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const m = entry.value_ptr;
|
||||
const ref = m.names.get(name) orelse continue;
|
||||
if (rawNamedTypePtr(ref) == null) continue;
|
||||
const norm = std.fs.path.resolvePosix(self.alloc, &.{entry.key_ptr.*}) catch continue;
|
||||
if (first_norm) |f| {
|
||||
defer self.alloc.free(norm);
|
||||
if (!std.mem.eql(u8, f, norm)) return true;
|
||||
} else {
|
||||
first_norm = norm;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// The opaque decl-pointer identity of a NAMED-type `RawDeclRef`, or null when
|
||||
/// the ref is not a named type (fn / value-const / namespace alias). Used to
|
||||
/// de-dup same-name authors by decl identity.
|
||||
fn rawNamedTypePtr(ref: resolver_mod.RawDeclRef) ?*const anyopaque {
|
||||
return switch (ref) {
|
||||
.struct_decl => |d| @ptrCast(d),
|
||||
.enum_decl => |d| @ptrCast(d),
|
||||
.union_decl => |d| @ptrCast(d),
|
||||
.error_set_decl => |d| @ptrCast(d),
|
||||
.protocol_decl => |d| @ptrCast(d),
|
||||
.foreign_class_decl => |d| @ptrCast(d),
|
||||
.fn_decl, .const_decl, .namespace_decl => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Build an owned generic-struct template (type params, field names, field
|
||||
/// type nodes) for `sd`, pinned to its declaring `source_file`. The returned
|
||||
/// template is heap-owned via `self.alloc`; callers register it under a bare
|
||||
/// or namespace-qualified key. Null on OOM.
|
||||
fn buildGenericStructTemplate(self: *Lowering, sd: *const ast.StructDecl, source_file: ?[]const u8) ?StructTemplate {
|
||||
const owned_name = self.alloc.dupe(u8, sd.name) catch return null;
|
||||
|
||||
const tps = self.alloc.alloc(TemplateParam, sd.type_params.len) catch return null;
|
||||
for (sd.type_params, 0..) |tp, i| {
|
||||
const is_type_param = tp.is_variadic or (if (tp.constraint.data == .type_expr) blk: {
|
||||
const cname = tp.constraint.data.type_expr.name;
|
||||
// "Type" or a protocol name → type param
|
||||
break :blk std.mem.eql(u8, cname, "Type") or
|
||||
self.program_index.protocol_decl_map.contains(cname) or
|
||||
self.program_index.protocol_ast_map.contains(cname);
|
||||
} else false);
|
||||
tps[i] = .{
|
||||
.name = self.alloc.dupe(u8, tp.name) catch return null,
|
||||
// $T: Type, $T: Lerpable, $T: Type/Eq — all are type params.
|
||||
// `..$Ts: []Type` (variadic) is a type-pack param. Only value
|
||||
// params like $N: u32 are non-type.
|
||||
.is_type_param = is_type_param,
|
||||
.is_variadic = tp.is_variadic,
|
||||
// Capture a value param's declared type name (`$K: u32` →
|
||||
// "u32") so instantiation can range-check the folded arg.
|
||||
.value_type = if (!is_type_param and tp.constraint.data == .type_expr)
|
||||
(self.alloc.dupe(u8, tp.constraint.data.type_expr.name) catch null)
|
||||
else
|
||||
null,
|
||||
};
|
||||
}
|
||||
|
||||
const fnames = self.alloc.alloc([]const u8, sd.field_names.len) catch return null;
|
||||
for (sd.field_names, 0..) |fn_str, i| {
|
||||
fnames[i] = self.alloc.dupe(u8, fn_str) catch return null;
|
||||
}
|
||||
|
||||
// Field type nodes are *Node pointers into the AST; copy the slice of
|
||||
// pointers (the nodes themselves are heap-allocated).
|
||||
const ftype_nodes = self.alloc.dupe(*const Node, sd.field_types) catch return null;
|
||||
|
||||
return .{
|
||||
.name = owned_name,
|
||||
.type_params = tps,
|
||||
.field_names = fnames,
|
||||
.field_type_nodes = ftype_nodes,
|
||||
.source_file = source_file,
|
||||
.decl = sd,
|
||||
};
|
||||
}
|
||||
|
||||
/// Select the generic struct template AUTHORED by namespace `alias`'s target
|
||||
/// module (the `importer → alias → NamespaceTarget` edge), not the bare
|
||||
/// last-wins `struct_template_map`. A qualified head `ns.Box(..)` must
|
||||
/// instantiate ns's OWN `Box`, even when another module's same-name `Box` won
|
||||
/// the bare map. Null when the alias is unknown in the current source or its
|
||||
/// module authors no such generic struct — the caller then falls back to the
|
||||
/// legacy bare lookup.
|
||||
fn qualifiedStructTemplate(self: *Lowering, alias: []const u8, member: []const u8) ?StructTemplate {
|
||||
const edges = self.program_index.namespace_edges orelse return null;
|
||||
const from = self.current_source_file orelse return null;
|
||||
const alias_map = edges.getPtr(from) orelse return null;
|
||||
const target = alias_map.get(alias) orelse return null;
|
||||
for (target.own_decls) |decl| {
|
||||
// A top-level struct is authored either as a bare `struct_decl` node
|
||||
// or a `const_decl` whose value is one (`Box :: struct($T){...}`).
|
||||
const sd: *const ast.StructDecl = switch (decl.data) {
|
||||
.struct_decl => |*s| s,
|
||||
.const_decl => |cd| if (cd.value.data == .struct_decl) &cd.value.data.struct_decl else continue,
|
||||
else => continue,
|
||||
};
|
||||
if (!std.mem.eql(u8, sd.name, member)) continue;
|
||||
if (sd.type_params.len == 0) continue;
|
||||
return self.buildGenericStructTemplate(sd, decl.source_file orelse target.target_module_path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// TRUE iff `alias` is a KNOWN namespace in the current source but its target
|
||||
/// module authors NO member named `member` at all. A qualified generic head
|
||||
/// `a.Box(..)` whose namespace lacks `Box` must diagnose the missing member —
|
||||
/// never silently fall back to the bare last-wins `struct_template_map` (which
|
||||
/// would instantiate an unrelated module's same-name `Box`, E4 finding #2).
|
||||
/// FALSE when `alias` is not a namespace at all (leave the caller's existing
|
||||
/// non-namespace handling), or when the namespace DOES author `member` (a
|
||||
/// generic struct → `qualifiedStructTemplate` already selected it; any other
|
||||
/// kind → the type-fn / named-type arms handle it).
|
||||
fn qualifiedMemberMissing(self: *Lowering, alias: []const u8, member: []const u8) bool {
|
||||
const edges = self.program_index.namespace_edges orelse return false;
|
||||
const from = self.current_source_file orelse return false;
|
||||
const alias_map = edges.getPtr(from) orelse return false;
|
||||
const target = alias_map.get(alias) orelse return false;
|
||||
for (target.own_decls) |decl| {
|
||||
const dn = decl.data.declName() orelse continue;
|
||||
if (std.mem.eql(u8, dn, member)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// The bare-VISIBLE single generic-struct author of `name` (its `StructDecl` +
|
||||
/// defining source) when that author is NOT the one the global last-wins
|
||||
/// `struct_template_map` already holds — the E4 non-transitive selection for a
|
||||
/// bare generic head / alias / static-method head whose visible author (own or
|
||||
/// a single 1-hop flat import) is shadowed in the global map by a NON-visible
|
||||
/// (≥2-flat-hop) same-name template (finding #1). Exposing the decl (not just a
|
||||
/// rebuilt template) lets a static-method head source-pin the METHOD body too,
|
||||
/// not only the type layout. Null — caller uses the global map unchanged
|
||||
/// (byte-identical) — when: no source context; the single visible author IS the
|
||||
/// canonical map author (the common single-author case, matched by source
|
||||
/// file); or the visible picture is not a clean single generic-struct author
|
||||
/// (own non-generic shadow, or ≥2 flat authors whose ambiguity `headTypeLeak`
|
||||
/// has already diagnosed + poisoned before this is consulted).
|
||||
fn bareVisibleStructDecl(self: *Lowering, name: []const u8) ?VisibleStructAuthor {
|
||||
if (self.emitting_default_context) return null;
|
||||
const from = self.current_source_file orelse return null;
|
||||
const canon = self.program_index.struct_template_map.get(name) orelse return null;
|
||||
const canon_src = canon.source_file orelse "";
|
||||
|
||||
var res_walk = self.resolver();
|
||||
const set = res_walk.collectVisibleAuthors(name, from, .user_bare_flat);
|
||||
defer if (set.flat.len > 0) self.alloc.free(set.flat);
|
||||
|
||||
// Own author wins — must be a generic struct to count.
|
||||
if (set.own) |own| {
|
||||
const sd = structDeclOfRaw(own.raw) orelse return null; // alias / fn / other → skip
|
||||
if (sd.type_params.len == 0) return null;
|
||||
if (std.mem.eql(u8, from, canon_src)) return null;
|
||||
return .{ .sd = sd, .source = from };
|
||||
}
|
||||
|
||||
// Single flat-import generic-struct author.
|
||||
var picked: ?*const ast.StructDecl = null;
|
||||
var picked_src: []const u8 = "";
|
||||
for (set.flat) |fa| {
|
||||
const sd = structDeclOfRaw(fa.raw) orelse continue;
|
||||
if (sd.type_params.len == 0) continue;
|
||||
if (picked != null) return null; // ≥2 visible authors
|
||||
picked = sd;
|
||||
picked_src = fa.source;
|
||||
}
|
||||
const sd = picked orelse return null;
|
||||
if (std.mem.eql(u8, picked_src, canon_src)) return null;
|
||||
return .{ .sd = sd, .source = picked_src };
|
||||
}
|
||||
|
||||
/// The rebuilt, source-pinned generic struct TEMPLATE of the single bare-VISIBLE
|
||||
/// author (`bareVisibleStructDecl`) — instantiate this INSTEAD of the global
|
||||
/// last-wins map entry. Null under the same conditions `bareVisibleStructDecl`
|
||||
/// returns null (caller keeps the global map, byte-identical).
|
||||
fn bareVisibleStructTemplate(self: *Lowering, name: []const u8) ?StructTemplate {
|
||||
const v = self.bareVisibleStructDecl(name) orelse return null;
|
||||
return self.buildGenericStructTemplate(v.sd, v.source);
|
||||
}
|
||||
|
||||
/// Instantiate a generic struct template and register the result under an
|
||||
/// alias name (`Vec3 :: Vec(3, f32)` / `ABox :: a.Box(s64)`). Shared by the
|
||||
/// `.call` and `.parameterized_type_expr` const-decl alias branches and the
|
||||
/// qualified-head selection that precedes the bare `struct_template_map`
|
||||
/// fallback in each.
|
||||
pub fn registerGenericStructAlias(self: *Lowering, alias_name: []const u8, tmpl: *const StructTemplate, args: []const *const Node) void {
|
||||
const inst_id = self.instantiateGenericStruct(tmpl, args);
|
||||
const alias_name_id = self.module.types.internString(alias_name);
|
||||
const inst_info = self.module.types.get(inst_id);
|
||||
if (inst_info != .@"struct") return;
|
||||
const alias_info: types.TypeInfo = .{ .@"struct" = .{
|
||||
.name = alias_name_id,
|
||||
.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.updatePreservingKey(alias_id, alias_info);
|
||||
// A generic-struct instantiation alias IS a type author: route it through
|
||||
// the unified writer so it lands in `type_aliases_by_source` and the
|
||||
// bare-TYPE gate treats it like any other alias.
|
||||
self.putTypeAlias(self.current_source_file, alias_name, alias_id);
|
||||
// CP-3: the alias display name (`ABox`) is the struct type name a receiver
|
||||
// typed `x: ABox` reports, so method dispatch on it looks up the instance
|
||||
// maps under `ABox`. Mirror the mangled instance's template/bindings/author
|
||||
// onto the alias name so an alias-typed receiver is a first-class dispatch
|
||||
// instance (runs the selected author's body + bindings), not a dead end.
|
||||
const inst_name = self.formatTypeName(inst_id);
|
||||
if (self.struct_instance_author.get(inst_name)) |author_decl| {
|
||||
const tmpl_name = self.struct_instance_template.get(inst_name) orelse return;
|
||||
const bindings = self.struct_instance_bindings.getPtr(inst_name) orelse return;
|
||||
self.struct_instance_template.put(self.alloc.dupe(u8, alias_name) catch return, tmpl_name) catch {};
|
||||
self.struct_instance_bindings.put(self.alloc.dupe(u8, alias_name) catch return, bindings.*) catch {};
|
||||
self.struct_instance_author.put(self.alloc.dupe(u8, alias_name) catch return, author_decl) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn registerStructDecl(self: *Lowering, sd: *const ast.StructDecl, source_file: ?[]const u8) void {
|
||||
const table = &self.module.types;
|
||||
const name_id = table.internString(sd.name);
|
||||
|
||||
// Generic structs: store as owned template, don't resolve fields yet
|
||||
if (sd.type_params.len > 0) {
|
||||
const tmpl = self.buildGenericStructTemplate(sd, source_file) orelse return;
|
||||
self.program_index.struct_template_map.put(tmpl.name, tmpl) catch {};
|
||||
|
||||
// S1.1 (additive): key the template by DeclId in parallel. Nothing
|
||||
// reads this for selection yet; `struct_template_map` stays the live
|
||||
// consumer. A template whose decl is not in the table (comptime /
|
||||
// block-local registration with facts unwired) keeps only the
|
||||
// name-keyed entry.
|
||||
if (self.program_index.decl_table) |dt| {
|
||||
if (dt.declIdForStructDecl(sd)) |id| {
|
||||
self.program_index.struct_template_by_decl.put(id, tmpl) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
// Register methods under "TemplateName.method" in fn_ast_map
|
||||
for (sd.methods) |method_node| {
|
||||
if (method_node.data == .fn_decl) {
|
||||
const method_fd = &method_node.data.fn_decl;
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sd.name, method_fd.name }) catch continue;
|
||||
self.program_index.fn_ast_map.put(qualified, method_fd) catch {};
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Per-decl nominal identity (E2). EACH author of a GENUINE same-name STRUCT
|
||||
// shadow already reserved its distinct slot up-front in `scanDecls` (the
|
||||
// first at id 0, the rest at nonzero ids), so a self / forward / mutual
|
||||
// reference to the shadow name bound to ITS nominal TypeId via
|
||||
// `type_decl_tids`, not the global findByName first-author fallback (issue
|
||||
// 0105 / F1): reuse that reserved id. A single-author name (or a phantom
|
||||
// over-counted by the raw import facts) was NOT reserved — it keeps id 0 and
|
||||
// the legacy post-field registration, byte-identical to pre-F1.
|
||||
// `shadowNominalId` here only fires for the non-scanDecls registration paths
|
||||
// (comptime `lowerDecls`, block-local), where module facts are unwired so it
|
||||
// returns 0.
|
||||
const decl_key: *const anyopaque = @ptrCast(sd);
|
||||
const nominal_id: u32 = if (table.type_decl_tids.get(decl_key)) |id| nominalIdOf(table.get(id)) else self.shadowNominalId(name_id);
|
||||
|
||||
// Build field list, expanding #using entries
|
||||
var fields = std.ArrayList(types.TypeInfo.StructInfo.Field).empty;
|
||||
var field_idx: usize = 0;
|
||||
var using_idx: usize = 0;
|
||||
const total_explicit = sd.field_names.len;
|
||||
while (field_idx < total_explicit or using_idx < sd.using_entries.len) {
|
||||
// Insert #using fields at their declared positions
|
||||
while (using_idx < sd.using_entries.len and sd.using_entries[using_idx].insert_index == fields.items.len) {
|
||||
const ue = sd.using_entries[using_idx];
|
||||
const used_name_id = table.internString(ue.type_name);
|
||||
if (table.findByName(used_name_id)) |used_ty| {
|
||||
const used_info = table.get(used_ty);
|
||||
if (used_info == .@"struct") {
|
||||
for (used_info.@"struct".fields) |f| {
|
||||
fields.append(self.alloc, f) catch unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
using_idx += 1;
|
||||
}
|
||||
if (field_idx < total_explicit) {
|
||||
const field_ty = self.resolveType(sd.field_types[field_idx]);
|
||||
fields.append(self.alloc, .{
|
||||
.name = table.internString(sd.field_names[field_idx]),
|
||||
.ty = field_ty,
|
||||
}) catch unreachable;
|
||||
field_idx += 1;
|
||||
} else break;
|
||||
}
|
||||
// Append remaining #using entries after all explicit fields
|
||||
while (using_idx < sd.using_entries.len) {
|
||||
const ue = sd.using_entries[using_idx];
|
||||
const used_name_id = table.internString(ue.type_name);
|
||||
if (table.findByName(used_name_id)) |used_ty| {
|
||||
const used_info = table.get(used_ty);
|
||||
if (used_info == .@"struct") {
|
||||
for (used_info.@"struct".fields) |f| {
|
||||
fields.append(self.alloc, f) catch unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
using_idx += 1;
|
||||
}
|
||||
|
||||
// Qualify inline __anon type names: __anon → StructName.field_name
|
||||
for (sd.field_names, 0..) |fname, fi| {
|
||||
if (fi < fields.items.len) {
|
||||
const field_ty = fields.items[fi].ty;
|
||||
if (!field_ty.isBuiltin()) {
|
||||
self.qualifyAnonType(table, field_ty, sd.name, fname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register under the per-decl nominal identity computed above. A non-first
|
||||
// shadow author's slot was already reserved before fields resolved, so this
|
||||
// fills it (key-stable updatePreservingKey); a first / single author adopts
|
||||
// any forward-reference stub. Same-name structs in DIFFERENT sources get
|
||||
// distinct TypeIds instead of last-wins clobbering the first (issue 0105).
|
||||
const info: types.TypeInfo = .{ .@"struct" = .{ .name = name_id, .fields = fields.items } };
|
||||
_ = self.internNamedTypeDecl(decl_key, name_id, info, nominal_id);
|
||||
|
||||
// Store field defaults for struct literal lowering
|
||||
if (sd.field_defaults.len > 0) {
|
||||
var has_any_default = false;
|
||||
for (sd.field_defaults) |d| {
|
||||
if (d != null) { has_any_default = true; break; }
|
||||
}
|
||||
if (has_any_default) {
|
||||
self.struct_defaults_map.put(sd.name, sd.field_defaults) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
// Register struct methods as StructName.method in fn_ast_map
|
||||
for (sd.methods) |method_node| {
|
||||
if (method_node.data == .fn_decl) {
|
||||
const method_fd = &method_node.data.fn_decl;
|
||||
// Build qualified name: StructName.method
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sd.name, method_fd.name }) catch continue;
|
||||
self.program_index.fn_ast_map.put(qualified, method_fd) catch {};
|
||||
// Declare extern stub (body is lowered lazily on demand)
|
||||
self.declareFunction(method_fd, qualified);
|
||||
}
|
||||
}
|
||||
|
||||
// Register struct-level constants (e.g., GRAVITY :f32: 9.81)
|
||||
for (sd.constants) |const_node| {
|
||||
if (const_node.data == .const_decl) {
|
||||
const cd = const_node.data.const_decl;
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sd.name, cd.name }) catch continue;
|
||||
const ty: ?TypeId = if (cd.type_annotation) |ta| type_bridge.resolveAstType(ta, table, &self.program_index.type_alias_map, &self.program_index.module_const_map) else null;
|
||||
self.struct_const_map.put(qualified, .{ .value = cd.value, .ty = ty }) catch {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub 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`.
|
||||
pub 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 {
|
||||
const ti = table.get(ty);
|
||||
switch (ti) {
|
||||
.@"union" => |u| {
|
||||
const old_name = table.getString(u.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.replaceKeyedInfo(ty, .{ .@"union" = .{ .name = qname_id, .fields = u.fields } });
|
||||
},
|
||||
.tagged_union => |u| {
|
||||
const old_name = table.getString(u.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);
|
||||
// Rename variant payload structs: __anon.X → ParentStruct.field.X
|
||||
for (u.fields) |f| {
|
||||
if (!f.ty.isBuiltin()) {
|
||||
const finfo = table.get(f.ty);
|
||||
if (finfo == .@"struct") {
|
||||
const sname = table.getString(finfo.@"struct".name);
|
||||
if (std.mem.startsWith(u8, sname, "__anon.")) {
|
||||
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.replaceKeyedInfo(f.ty, .{ .@"struct" = .{ .name = sq_id, .fields = finfo.@"struct".fields } });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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.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.replaceKeyedInfo(ty, .{ .@"struct" = .{ .name = qname_id, .fields = s.fields } });
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a protocol declaration as a struct type in the IR type table.
|
||||
/// Inline protocols: { ctx: *void, method1: *void, method2: *void, ... }
|
||||
/// Non-inline protocols: { ctx: *void, __vtable: *void }
|
||||
@@ -13827,6 +13133,31 @@ pub const Lowering = struct {
|
||||
pub const dropModuleConst = lower_decl.dropModuleConst;
|
||||
pub const emitModuleConst = lower_decl.emitModuleConst;
|
||||
pub const emitPlaceholder = lower_decl.emitPlaceholder;
|
||||
|
||||
// --- moved to lower/nominal.zig (lower_nominal) ---
|
||||
pub const registerErrorSetDecl = lower_nominal.registerErrorSetDecl;
|
||||
pub const registerStructDecl = lower_nominal.registerStructDecl;
|
||||
pub const registerEnumDecl = lower_nominal.registerEnumDecl;
|
||||
pub const registerUnionDecl = lower_nominal.registerUnionDecl;
|
||||
pub const qualifyAnonType = lower_nominal.qualifyAnonType;
|
||||
pub const nominalIdOf = lower_nominal.nominalIdOf;
|
||||
pub const stampNominalId = lower_nominal.stampNominalId;
|
||||
pub const reserveShadowStructSlot = lower_nominal.reserveShadowStructSlot;
|
||||
pub const reserveShadowEnumSlot = lower_nominal.reserveShadowEnumSlot;
|
||||
pub const reserveShadowUnionSlot = lower_nominal.reserveShadowUnionSlot;
|
||||
pub const topLevelTypeDecl = lower_nominal.topLevelTypeDecl;
|
||||
pub const reserveShadowSlot = lower_nominal.reserveShadowSlot;
|
||||
pub const internNamedTypeDecl = lower_nominal.internNamedTypeDecl;
|
||||
pub const adoptsForwardStructStub = lower_nominal.adoptsForwardStructStub;
|
||||
pub const shadowNominalId = lower_nominal.shadowNominalId;
|
||||
pub const nameHasMultipleTypeAuthors = lower_nominal.nameHasMultipleTypeAuthors;
|
||||
pub const rawNamedTypePtr = lower_nominal.rawNamedTypePtr;
|
||||
pub const buildGenericStructTemplate = lower_nominal.buildGenericStructTemplate;
|
||||
pub const qualifiedStructTemplate = lower_nominal.qualifiedStructTemplate;
|
||||
pub const qualifiedMemberMissing = lower_nominal.qualifiedMemberMissing;
|
||||
pub const bareVisibleStructDecl = lower_nominal.bareVisibleStructDecl;
|
||||
pub const bareVisibleStructTemplate = lower_nominal.bareVisibleStructTemplate;
|
||||
pub const registerGenericStructAlias = lower_nominal.registerGenericStructAlias;
|
||||
};
|
||||
|
||||
/// JNI param/return type resolution: user-declared types pass through
|
||||
|
||||
746
src/ir/lower/nominal.zig
Normal file
746
src/ir/lower/nominal.zig
Normal file
@@ -0,0 +1,746 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ast = @import("../../ast.zig");
|
||||
const Node = ast.Node;
|
||||
const types = @import("../types.zig");
|
||||
const inst_mod = @import("../inst.zig");
|
||||
const mod_mod = @import("../module.zig");
|
||||
const type_bridge = @import("../type_bridge.zig");
|
||||
const unescape = @import("../../unescape.zig");
|
||||
const parser_mod = @import("../../parser.zig");
|
||||
const interp_mod = @import("../interp.zig");
|
||||
const errors = @import("../../errors.zig");
|
||||
const jni_descriptor = @import("../jni_descriptor.zig");
|
||||
const program_index_mod = @import("../program_index.zig");
|
||||
const resolver_mod = @import("../resolver.zig");
|
||||
const imports_mod = @import("../../imports.zig");
|
||||
const ProgramIndex = program_index_mod.ProgramIndex;
|
||||
const GlobalInfo = program_index_mod.GlobalInfo;
|
||||
const StructTemplate = program_index_mod.StructTemplate;
|
||||
const TemplateParam = program_index_mod.TemplateParam;
|
||||
const ProtocolDeclInfo = program_index_mod.ProtocolDeclInfo;
|
||||
const ProtocolMethodInfo = program_index_mod.ProtocolMethodInfo;
|
||||
const ModuleConstInfo = program_index_mod.ModuleConstInfo;
|
||||
const TypeResolver = @import("../type_resolver.zig").TypeResolver;
|
||||
const ResolveEnv = @import("../type_resolver.zig").ResolveEnv;
|
||||
const PackResolver = @import("../packs.zig").PackResolver;
|
||||
const ExprTyper = @import("../expr_typer.zig").ExprTyper;
|
||||
const CallResolver = @import("../calls.zig").CallResolver;
|
||||
const GenericResolver = @import("../generics.zig").GenericResolver;
|
||||
const ProtocolResolver = @import("../protocols.zig").ProtocolResolver;
|
||||
const CoercionResolver = @import("../conversions.zig").CoercionResolver;
|
||||
const ErrorAnalysis = @import("../error_analysis.zig").ErrorAnalysis;
|
||||
const ErrorFlow = @import("../error_flow.zig").ErrorFlow;
|
||||
const ObjcLowering = @import("../ffi_objc.zig").ObjcLowering;
|
||||
const semantic_diagnostics = @import("../semantic_diagnostics.zig");
|
||||
|
||||
const TypeId = types.TypeId;
|
||||
const StringId = types.StringId;
|
||||
const Ref = inst_mod.Ref;
|
||||
const BlockId = inst_mod.BlockId;
|
||||
const FuncId = inst_mod.FuncId;
|
||||
const Function = inst_mod.Function;
|
||||
const Module = mod_mod.Module;
|
||||
const Builder = mod_mod.Builder;
|
||||
|
||||
|
||||
const lower = @import("../lower.zig");
|
||||
const Lowering = lower.Lowering;
|
||||
const Scope = lower.Scope;
|
||||
const VisibleStructAuthor = Lowering.VisibleStructAuthor;
|
||||
const structDeclOfRaw = Lowering.structDeclOfRaw;
|
||||
|
||||
/// Register a struct declaration's fields and methods in the IR type table.
|
||||
/// Register a `Foo :: error { A, B }` declaration as an error-set type.
|
||||
/// Rejects an empty set here (sema gate) since type_bridge has no
|
||||
/// diagnostics; non-empty sets are interned via type_bridge.
|
||||
pub fn registerErrorSetDecl(self: *Lowering, node: *const Node) void {
|
||||
const esd = node.data.error_set_decl;
|
||||
if (esd.tag_names.len == 0) {
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, node.span, "error set '{s}' must declare at least one tag", .{esd.name});
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ = type_bridge.resolveAstType(node, &self.module.types, &self.program_index.type_alias_map, &self.program_index.module_const_map);
|
||||
}
|
||||
|
||||
/// The `nominal_id` stamped on a nominal `TypeInfo` (0 for non-nominal /
|
||||
/// structural). Reading it back lets a re-registration preserve the slot's
|
||||
/// existing key when refreshing a forward-stubbed body.
|
||||
pub fn nominalIdOf(info: types.TypeInfo) u32 {
|
||||
return switch (info) {
|
||||
.@"struct" => |s| s.nominal_id,
|
||||
.@"enum" => |e| e.nominal_id,
|
||||
.@"union" => |u| u.nominal_id,
|
||||
.tagged_union => |u| u.nominal_id,
|
||||
.error_set => |e| e.nominal_id,
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/// Return `info` with its nominal arm's `nominal_id` set to `nid` (a no-op for
|
||||
/// non-nominal infos). Used to build the key-matching body for
|
||||
/// `updatePreservingKey` after a shadow author interned at a nonzero id.
|
||||
pub fn stampNominalId(info: types.TypeInfo, nid: u32) types.TypeInfo {
|
||||
var out = info;
|
||||
switch (out) {
|
||||
.@"struct" => |*s| s.nominal_id = nid,
|
||||
.@"enum" => |*e| e.nominal_id = nid,
|
||||
.@"union" => |*u| u.nominal_id = nid,
|
||||
.tagged_union => |*u| u.nominal_id = nid,
|
||||
.error_set => |*e| e.nominal_id = nid,
|
||||
else => {},
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// 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)
|
||||
/// binds to ITS nominal TypeId via `type_decl_tids` instead of the global
|
||||
/// findByName first-author fallback (issue 0105 / F1). Called only from the
|
||||
/// `scanDecls` genuine-shadow pass, which has already established that ≥2
|
||||
/// distinct struct decls author this name; ALL of them reserve — the FIRST at
|
||||
/// id 0, the rest at fresh nonzero ids — so none falls through to the name-only
|
||||
/// `findByName` (which, once a shadow is interned, no longer uniquely identifies
|
||||
/// the first author). Idempotent per decl key: an already-reserved decl returns
|
||||
/// before re-invoking `shadowNominalId`, so the shadow id is computed once.
|
||||
/// Generic templates resolve lazily on instantiation and are skipped.
|
||||
pub fn reserveShadowStructSlot(self: *Lowering, sd: *const ast.StructDecl) void {
|
||||
if (sd.type_params.len > 0) return;
|
||||
const table = &self.module.types;
|
||||
const decl_key: *const anyopaque = @ptrCast(sd);
|
||||
if (table.type_decl_tids.contains(decl_key)) return;
|
||||
const name_id = table.internString(sd.name);
|
||||
const nominal_id = self.shadowNominalId(name_id); // 0 for the first author, nonzero for the rest
|
||||
const reserved = table.internNominal(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } }, nominal_id);
|
||||
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.
|
||||
pub 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).
|
||||
pub 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,
|
||||
|
||||
pub fn key(self: ShadowTypeDecl) *const anyopaque {
|
||||
return switch (self) {
|
||||
inline else => |p| @ptrCast(p),
|
||||
};
|
||||
}
|
||||
pub fn name(self: ShadowTypeDecl) []const u8 {
|
||||
return switch (self) {
|
||||
inline else => |p| p.name,
|
||||
};
|
||||
}
|
||||
pub 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.
|
||||
pub 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.
|
||||
pub 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
|
||||
/// body; `nominal_id` is the slot's identity (0 for a single / first author,
|
||||
/// nonzero for a later same-name shadow) — computed once by the caller
|
||||
/// (`registerStructDecl`), which reuses the id reserved up-front in `scanDecls`
|
||||
/// for a genuine shadow (so its fields' self / forward / mutual refs already
|
||||
/// resolved against it). This stamps the id and records the `decl_key → TypeId`
|
||||
/// map (`type_decl_tids`, the `fn_decl_fids` analogue).
|
||||
///
|
||||
/// A `nominal_id == 0` author adopts any forward-reference stub (`findByName`
|
||||
/// orelse intern) — BYTE-IDENTICAL to pre-E2 registration. For a genuinely
|
||||
/// multi-authored name, the FIRST source keeps id 0 and later sources get
|
||||
/// fresh ids → DISTINCT TypeIds, so the authors no longer collapse last-wins
|
||||
/// (issue 0105). Idempotent per `decl_key`: a re-registration — OR an up-front
|
||||
/// shadow reservation — reuses the recorded slot, refreshing its body via
|
||||
/// `updatePreservingKey` (key-stable because a struct's intern key is its
|
||||
/// name + nominal id, not its fields).
|
||||
pub fn internNamedTypeDecl(self: *Lowering, decl_key: *const anyopaque, name_id: types.StringId, info: types.TypeInfo, nominal_id: u32) TypeId {
|
||||
const table = &self.module.types;
|
||||
// Slot already recorded (re-registration, or a reserve-before-fields shadow
|
||||
// reservation) → reuse its slot + nominal id, refresh the body.
|
||||
if (table.type_decl_tids.get(decl_key)) |existing_id| {
|
||||
table.updatePreservingKey(existing_id, stampNominalId(info, nominalIdOf(table.get(existing_id))));
|
||||
return existing_id;
|
||||
}
|
||||
const id = if (nominal_id == 0)
|
||||
(table.findByName(name_id) orelse table.internNominal(info, 0))
|
||||
else
|
||||
table.internNominal(info, nominal_id);
|
||||
const stamped = stampNominalId(info, nominal_id);
|
||||
// A self / mutual `*Name` field in an enum/union body forward-creates a
|
||||
// STRUCT placeholder under `Name` (the stateless resolver has no kind
|
||||
// context — `type_resolver.resolveNamed` always stubs a struct), which the
|
||||
// `findByName` above then returns. Adopting a wrong-kind stub needs a
|
||||
// re-key, NOT the in-place `updatePreservingKey` body-fill — whose
|
||||
// kind-stability assert trips on struct→enum/union.
|
||||
if (adoptsForwardStructStub(table.get(id), stamped))
|
||||
table.replaceKeyedInfo(id, stamped)
|
||||
else
|
||||
table.updatePreservingKey(id, stamped);
|
||||
table.type_decl_tids.put(decl_key, id) catch {};
|
||||
return id;
|
||||
}
|
||||
|
||||
/// TRUE when `existing` is a forward-reference STRUCT placeholder (empty
|
||||
/// fields — the stateless resolver's stub for an as-yet-unregistered name) and
|
||||
/// `incoming` is a NON-struct nominal (enum / union / tagged_union): the one
|
||||
/// case where `internNamedTypeDecl` must re-key the slot rather than fill its
|
||||
/// body in place. A struct adopting its own struct stub is same-kind and stays
|
||||
/// on `updatePreservingKey`; a fresh-interned slot has no stub to adopt.
|
||||
pub fn adoptsForwardStructStub(existing: types.TypeInfo, incoming: types.TypeInfo) bool {
|
||||
if (existing != .@"struct" or existing.@"struct".fields.len != 0) return false;
|
||||
return switch (incoming) {
|
||||
.@"enum", .@"union", .tagged_union => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// The `nominal_id` to register a NAMED type author of `name_id` under. 0
|
||||
/// unless `name_id` is authored as a named type by ≥2 distinct modules (a real
|
||||
/// same-name shadow per the import facts): the FIRST source to register keeps
|
||||
/// 0, each later source gets a fresh monotonic id. Gating on the import facts
|
||||
/// keeps the single-author path at id 0 (byte-identical) even when one logical
|
||||
/// type is re-registered from several `current_source_file` contexts.
|
||||
pub fn shadowNominalId(self: *Lowering, name_id: types.StringId) u32 {
|
||||
if (!self.nameHasMultipleTypeAuthors(self.module.types.getString(name_id))) return 0;
|
||||
const src = self.current_source_file orelse self.main_file orelse "";
|
||||
const gop = self.nominal_name_authors.getOrPut(name_id) catch return 0;
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = src;
|
||||
return 0;
|
||||
}
|
||||
if (std.mem.eql(u8, gop.value_ptr.*, src)) return 0;
|
||||
self.next_nominal_id += 1;
|
||||
return self.next_nominal_id;
|
||||
}
|
||||
|
||||
/// TRUE iff `name` is authored AS A NAMED TYPE (struct / enum / union /
|
||||
/// error-set / protocol / foreign class) by ≥2 DISTINCT modules in the import
|
||||
/// raw facts — the authoritative same-name-shadow signal (the only case where
|
||||
/// distinct `nominal_id`s are needed). Module distinctness is by LEXICALLY
|
||||
/// NORMALIZED path: one logical file reached through several spellings
|
||||
/// (`testpkg/../allocators.sx` vs `allocators.sx`) is cached — and so parsed —
|
||||
/// twice, landing two `module_decls` entries with two decl pointers for the
|
||||
/// SAME source; normalizing collapses them to one author, NOT a false shadow.
|
||||
/// False when the facts are unwired (comptime / registration host with no
|
||||
/// `module_decls`): the single-author path applies, correct there.
|
||||
pub fn nameHasMultipleTypeAuthors(self: *Lowering, name: []const u8) bool {
|
||||
const decls = self.program_index.module_decls orelse return false;
|
||||
var first_norm: ?[]const u8 = null;
|
||||
defer if (first_norm) |f| self.alloc.free(f);
|
||||
var it = decls.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const m = entry.value_ptr;
|
||||
const ref = m.names.get(name) orelse continue;
|
||||
if (rawNamedTypePtr(ref) == null) continue;
|
||||
const norm = std.fs.path.resolvePosix(self.alloc, &.{entry.key_ptr.*}) catch continue;
|
||||
if (first_norm) |f| {
|
||||
defer self.alloc.free(norm);
|
||||
if (!std.mem.eql(u8, f, norm)) return true;
|
||||
} else {
|
||||
first_norm = norm;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// The opaque decl-pointer identity of a NAMED-type `RawDeclRef`, or null when
|
||||
/// the ref is not a named type (fn / value-const / namespace alias). Used to
|
||||
/// de-dup same-name authors by decl identity.
|
||||
pub fn rawNamedTypePtr(ref: resolver_mod.RawDeclRef) ?*const anyopaque {
|
||||
return switch (ref) {
|
||||
.struct_decl => |d| @ptrCast(d),
|
||||
.enum_decl => |d| @ptrCast(d),
|
||||
.union_decl => |d| @ptrCast(d),
|
||||
.error_set_decl => |d| @ptrCast(d),
|
||||
.protocol_decl => |d| @ptrCast(d),
|
||||
.foreign_class_decl => |d| @ptrCast(d),
|
||||
.fn_decl, .const_decl, .namespace_decl => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Build an owned generic-struct template (type params, field names, field
|
||||
/// type nodes) for `sd`, pinned to its declaring `source_file`. The returned
|
||||
/// template is heap-owned via `self.alloc`; callers register it under a bare
|
||||
/// or namespace-qualified key. Null on OOM.
|
||||
pub fn buildGenericStructTemplate(self: *Lowering, sd: *const ast.StructDecl, source_file: ?[]const u8) ?StructTemplate {
|
||||
const owned_name = self.alloc.dupe(u8, sd.name) catch return null;
|
||||
|
||||
const tps = self.alloc.alloc(TemplateParam, sd.type_params.len) catch return null;
|
||||
for (sd.type_params, 0..) |tp, i| {
|
||||
const is_type_param = tp.is_variadic or (if (tp.constraint.data == .type_expr) blk: {
|
||||
const cname = tp.constraint.data.type_expr.name;
|
||||
// "Type" or a protocol name → type param
|
||||
break :blk std.mem.eql(u8, cname, "Type") or
|
||||
self.program_index.protocol_decl_map.contains(cname) or
|
||||
self.program_index.protocol_ast_map.contains(cname);
|
||||
} else false);
|
||||
tps[i] = .{
|
||||
.name = self.alloc.dupe(u8, tp.name) catch return null,
|
||||
// $T: Type, $T: Lerpable, $T: Type/Eq — all are type params.
|
||||
// `..$Ts: []Type` (variadic) is a type-pack param. Only value
|
||||
// params like $N: u32 are non-type.
|
||||
.is_type_param = is_type_param,
|
||||
.is_variadic = tp.is_variadic,
|
||||
// Capture a value param's declared type name (`$K: u32` →
|
||||
// "u32") so instantiation can range-check the folded arg.
|
||||
.value_type = if (!is_type_param and tp.constraint.data == .type_expr)
|
||||
(self.alloc.dupe(u8, tp.constraint.data.type_expr.name) catch null)
|
||||
else
|
||||
null,
|
||||
};
|
||||
}
|
||||
|
||||
const fnames = self.alloc.alloc([]const u8, sd.field_names.len) catch return null;
|
||||
for (sd.field_names, 0..) |fn_str, i| {
|
||||
fnames[i] = self.alloc.dupe(u8, fn_str) catch return null;
|
||||
}
|
||||
|
||||
// Field type nodes are *Node pointers into the AST; copy the slice of
|
||||
// pointers (the nodes themselves are heap-allocated).
|
||||
const ftype_nodes = self.alloc.dupe(*const Node, sd.field_types) catch return null;
|
||||
|
||||
return .{
|
||||
.name = owned_name,
|
||||
.type_params = tps,
|
||||
.field_names = fnames,
|
||||
.field_type_nodes = ftype_nodes,
|
||||
.source_file = source_file,
|
||||
.decl = sd,
|
||||
};
|
||||
}
|
||||
|
||||
/// Select the generic struct template AUTHORED by namespace `alias`'s target
|
||||
/// module (the `importer → alias → NamespaceTarget` edge), not the bare
|
||||
/// last-wins `struct_template_map`. A qualified head `ns.Box(..)` must
|
||||
/// instantiate ns's OWN `Box`, even when another module's same-name `Box` won
|
||||
/// the bare map. Null when the alias is unknown in the current source or its
|
||||
/// module authors no such generic struct — the caller then falls back to the
|
||||
/// legacy bare lookup.
|
||||
pub fn qualifiedStructTemplate(self: *Lowering, alias: []const u8, member: []const u8) ?StructTemplate {
|
||||
const edges = self.program_index.namespace_edges orelse return null;
|
||||
const from = self.current_source_file orelse return null;
|
||||
const alias_map = edges.getPtr(from) orelse return null;
|
||||
const target = alias_map.get(alias) orelse return null;
|
||||
for (target.own_decls) |decl| {
|
||||
// A top-level struct is authored either as a bare `struct_decl` node
|
||||
// or a `const_decl` whose value is one (`Box :: struct($T){...}`).
|
||||
const sd: *const ast.StructDecl = switch (decl.data) {
|
||||
.struct_decl => |*s| s,
|
||||
.const_decl => |cd| if (cd.value.data == .struct_decl) &cd.value.data.struct_decl else continue,
|
||||
else => continue,
|
||||
};
|
||||
if (!std.mem.eql(u8, sd.name, member)) continue;
|
||||
if (sd.type_params.len == 0) continue;
|
||||
return self.buildGenericStructTemplate(sd, decl.source_file orelse target.target_module_path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// TRUE iff `alias` is a KNOWN namespace in the current source but its target
|
||||
/// module authors NO member named `member` at all. A qualified generic head
|
||||
/// `a.Box(..)` whose namespace lacks `Box` must diagnose the missing member —
|
||||
/// never silently fall back to the bare last-wins `struct_template_map` (which
|
||||
/// would instantiate an unrelated module's same-name `Box`, E4 finding #2).
|
||||
/// FALSE when `alias` is not a namespace at all (leave the caller's existing
|
||||
/// non-namespace handling), or when the namespace DOES author `member` (a
|
||||
/// generic struct → `qualifiedStructTemplate` already selected it; any other
|
||||
/// kind → the type-fn / named-type arms handle it).
|
||||
pub fn qualifiedMemberMissing(self: *Lowering, alias: []const u8, member: []const u8) bool {
|
||||
const edges = self.program_index.namespace_edges orelse return false;
|
||||
const from = self.current_source_file orelse return false;
|
||||
const alias_map = edges.getPtr(from) orelse return false;
|
||||
const target = alias_map.get(alias) orelse return false;
|
||||
for (target.own_decls) |decl| {
|
||||
const dn = decl.data.declName() orelse continue;
|
||||
if (std.mem.eql(u8, dn, member)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// The bare-VISIBLE single generic-struct author of `name` (its `StructDecl` +
|
||||
/// defining source) when that author is NOT the one the global last-wins
|
||||
/// `struct_template_map` already holds — the E4 non-transitive selection for a
|
||||
/// bare generic head / alias / static-method head whose visible author (own or
|
||||
/// a single 1-hop flat import) is shadowed in the global map by a NON-visible
|
||||
/// (≥2-flat-hop) same-name template (finding #1). Exposing the decl (not just a
|
||||
/// rebuilt template) lets a static-method head source-pin the METHOD body too,
|
||||
/// not only the type layout. Null — caller uses the global map unchanged
|
||||
/// (byte-identical) — when: no source context; the single visible author IS the
|
||||
/// canonical map author (the common single-author case, matched by source
|
||||
/// file); or the visible picture is not a clean single generic-struct author
|
||||
/// (own non-generic shadow, or ≥2 flat authors whose ambiguity `headTypeLeak`
|
||||
/// has already diagnosed + poisoned before this is consulted).
|
||||
pub fn bareVisibleStructDecl(self: *Lowering, name: []const u8) ?VisibleStructAuthor {
|
||||
if (self.emitting_default_context) return null;
|
||||
const from = self.current_source_file orelse return null;
|
||||
const canon = self.program_index.struct_template_map.get(name) orelse return null;
|
||||
const canon_src = canon.source_file orelse "";
|
||||
|
||||
var res_walk = self.resolver();
|
||||
const set = res_walk.collectVisibleAuthors(name, from, .user_bare_flat);
|
||||
defer if (set.flat.len > 0) self.alloc.free(set.flat);
|
||||
|
||||
// Own author wins — must be a generic struct to count.
|
||||
if (set.own) |own| {
|
||||
const sd = structDeclOfRaw(own.raw) orelse return null; // alias / fn / other → skip
|
||||
if (sd.type_params.len == 0) return null;
|
||||
if (std.mem.eql(u8, from, canon_src)) return null;
|
||||
return .{ .sd = sd, .source = from };
|
||||
}
|
||||
|
||||
// Single flat-import generic-struct author.
|
||||
var picked: ?*const ast.StructDecl = null;
|
||||
var picked_src: []const u8 = "";
|
||||
for (set.flat) |fa| {
|
||||
const sd = structDeclOfRaw(fa.raw) orelse continue;
|
||||
if (sd.type_params.len == 0) continue;
|
||||
if (picked != null) return null; // ≥2 visible authors
|
||||
picked = sd;
|
||||
picked_src = fa.source;
|
||||
}
|
||||
const sd = picked orelse return null;
|
||||
if (std.mem.eql(u8, picked_src, canon_src)) return null;
|
||||
return .{ .sd = sd, .source = picked_src };
|
||||
}
|
||||
|
||||
/// The rebuilt, source-pinned generic struct TEMPLATE of the single bare-VISIBLE
|
||||
/// author (`bareVisibleStructDecl`) — instantiate this INSTEAD of the global
|
||||
/// last-wins map entry. Null under the same conditions `bareVisibleStructDecl`
|
||||
/// returns null (caller keeps the global map, byte-identical).
|
||||
pub fn bareVisibleStructTemplate(self: *Lowering, name: []const u8) ?StructTemplate {
|
||||
const v = self.bareVisibleStructDecl(name) orelse return null;
|
||||
return self.buildGenericStructTemplate(v.sd, v.source);
|
||||
}
|
||||
|
||||
/// Instantiate a generic struct template and register the result under an
|
||||
/// alias name (`Vec3 :: Vec(3, f32)` / `ABox :: a.Box(s64)`). Shared by the
|
||||
/// `.call` and `.parameterized_type_expr` const-decl alias branches and the
|
||||
/// qualified-head selection that precedes the bare `struct_template_map`
|
||||
/// fallback in each.
|
||||
pub fn registerGenericStructAlias(self: *Lowering, alias_name: []const u8, tmpl: *const StructTemplate, args: []const *const Node) void {
|
||||
const inst_id = self.instantiateGenericStruct(tmpl, args);
|
||||
const alias_name_id = self.module.types.internString(alias_name);
|
||||
const inst_info = self.module.types.get(inst_id);
|
||||
if (inst_info != .@"struct") return;
|
||||
const alias_info: types.TypeInfo = .{ .@"struct" = .{
|
||||
.name = alias_name_id,
|
||||
.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.updatePreservingKey(alias_id, alias_info);
|
||||
// A generic-struct instantiation alias IS a type author: route it through
|
||||
// the unified writer so it lands in `type_aliases_by_source` and the
|
||||
// bare-TYPE gate treats it like any other alias.
|
||||
self.putTypeAlias(self.current_source_file, alias_name, alias_id);
|
||||
// CP-3: the alias display name (`ABox`) is the struct type name a receiver
|
||||
// typed `x: ABox` reports, so method dispatch on it looks up the instance
|
||||
// maps under `ABox`. Mirror the mangled instance's template/bindings/author
|
||||
// onto the alias name so an alias-typed receiver is a first-class dispatch
|
||||
// instance (runs the selected author's body + bindings), not a dead end.
|
||||
const inst_name = self.formatTypeName(inst_id);
|
||||
if (self.struct_instance_author.get(inst_name)) |author_decl| {
|
||||
const tmpl_name = self.struct_instance_template.get(inst_name) orelse return;
|
||||
const bindings = self.struct_instance_bindings.getPtr(inst_name) orelse return;
|
||||
self.struct_instance_template.put(self.alloc.dupe(u8, alias_name) catch return, tmpl_name) catch {};
|
||||
self.struct_instance_bindings.put(self.alloc.dupe(u8, alias_name) catch return, bindings.*) catch {};
|
||||
self.struct_instance_author.put(self.alloc.dupe(u8, alias_name) catch return, author_decl) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn registerStructDecl(self: *Lowering, sd: *const ast.StructDecl, source_file: ?[]const u8) void {
|
||||
const table = &self.module.types;
|
||||
const name_id = table.internString(sd.name);
|
||||
|
||||
// Generic structs: store as owned template, don't resolve fields yet
|
||||
if (sd.type_params.len > 0) {
|
||||
const tmpl = self.buildGenericStructTemplate(sd, source_file) orelse return;
|
||||
self.program_index.struct_template_map.put(tmpl.name, tmpl) catch {};
|
||||
|
||||
// S1.1 (additive): key the template by DeclId in parallel. Nothing
|
||||
// reads this for selection yet; `struct_template_map` stays the live
|
||||
// consumer. A template whose decl is not in the table (comptime /
|
||||
// block-local registration with facts unwired) keeps only the
|
||||
// name-keyed entry.
|
||||
if (self.program_index.decl_table) |dt| {
|
||||
if (dt.declIdForStructDecl(sd)) |id| {
|
||||
self.program_index.struct_template_by_decl.put(id, tmpl) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
// Register methods under "TemplateName.method" in fn_ast_map
|
||||
for (sd.methods) |method_node| {
|
||||
if (method_node.data == .fn_decl) {
|
||||
const method_fd = &method_node.data.fn_decl;
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sd.name, method_fd.name }) catch continue;
|
||||
self.program_index.fn_ast_map.put(qualified, method_fd) catch {};
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Per-decl nominal identity (E2). EACH author of a GENUINE same-name STRUCT
|
||||
// shadow already reserved its distinct slot up-front in `scanDecls` (the
|
||||
// first at id 0, the rest at nonzero ids), so a self / forward / mutual
|
||||
// reference to the shadow name bound to ITS nominal TypeId via
|
||||
// `type_decl_tids`, not the global findByName first-author fallback (issue
|
||||
// 0105 / F1): reuse that reserved id. A single-author name (or a phantom
|
||||
// over-counted by the raw import facts) was NOT reserved — it keeps id 0 and
|
||||
// the legacy post-field registration, byte-identical to pre-F1.
|
||||
// `shadowNominalId` here only fires for the non-scanDecls registration paths
|
||||
// (comptime `lowerDecls`, block-local), where module facts are unwired so it
|
||||
// returns 0.
|
||||
const decl_key: *const anyopaque = @ptrCast(sd);
|
||||
const nominal_id: u32 = if (table.type_decl_tids.get(decl_key)) |id| nominalIdOf(table.get(id)) else self.shadowNominalId(name_id);
|
||||
|
||||
// Build field list, expanding #using entries
|
||||
var fields = std.ArrayList(types.TypeInfo.StructInfo.Field).empty;
|
||||
var field_idx: usize = 0;
|
||||
var using_idx: usize = 0;
|
||||
const total_explicit = sd.field_names.len;
|
||||
while (field_idx < total_explicit or using_idx < sd.using_entries.len) {
|
||||
// Insert #using fields at their declared positions
|
||||
while (using_idx < sd.using_entries.len and sd.using_entries[using_idx].insert_index == fields.items.len) {
|
||||
const ue = sd.using_entries[using_idx];
|
||||
const used_name_id = table.internString(ue.type_name);
|
||||
if (table.findByName(used_name_id)) |used_ty| {
|
||||
const used_info = table.get(used_ty);
|
||||
if (used_info == .@"struct") {
|
||||
for (used_info.@"struct".fields) |f| {
|
||||
fields.append(self.alloc, f) catch unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
using_idx += 1;
|
||||
}
|
||||
if (field_idx < total_explicit) {
|
||||
const field_ty = self.resolveType(sd.field_types[field_idx]);
|
||||
fields.append(self.alloc, .{
|
||||
.name = table.internString(sd.field_names[field_idx]),
|
||||
.ty = field_ty,
|
||||
}) catch unreachable;
|
||||
field_idx += 1;
|
||||
} else break;
|
||||
}
|
||||
// Append remaining #using entries after all explicit fields
|
||||
while (using_idx < sd.using_entries.len) {
|
||||
const ue = sd.using_entries[using_idx];
|
||||
const used_name_id = table.internString(ue.type_name);
|
||||
if (table.findByName(used_name_id)) |used_ty| {
|
||||
const used_info = table.get(used_ty);
|
||||
if (used_info == .@"struct") {
|
||||
for (used_info.@"struct".fields) |f| {
|
||||
fields.append(self.alloc, f) catch unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
using_idx += 1;
|
||||
}
|
||||
|
||||
// Qualify inline __anon type names: __anon → StructName.field_name
|
||||
for (sd.field_names, 0..) |fname, fi| {
|
||||
if (fi < fields.items.len) {
|
||||
const field_ty = fields.items[fi].ty;
|
||||
if (!field_ty.isBuiltin()) {
|
||||
self.qualifyAnonType(table, field_ty, sd.name, fname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register under the per-decl nominal identity computed above. A non-first
|
||||
// shadow author's slot was already reserved before fields resolved, so this
|
||||
// fills it (key-stable updatePreservingKey); a first / single author adopts
|
||||
// any forward-reference stub. Same-name structs in DIFFERENT sources get
|
||||
// distinct TypeIds instead of last-wins clobbering the first (issue 0105).
|
||||
const info: types.TypeInfo = .{ .@"struct" = .{ .name = name_id, .fields = fields.items } };
|
||||
_ = self.internNamedTypeDecl(decl_key, name_id, info, nominal_id);
|
||||
|
||||
// Store field defaults for struct literal lowering
|
||||
if (sd.field_defaults.len > 0) {
|
||||
var has_any_default = false;
|
||||
for (sd.field_defaults) |d| {
|
||||
if (d != null) { has_any_default = true; break; }
|
||||
}
|
||||
if (has_any_default) {
|
||||
self.struct_defaults_map.put(sd.name, sd.field_defaults) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
// Register struct methods as StructName.method in fn_ast_map
|
||||
for (sd.methods) |method_node| {
|
||||
if (method_node.data == .fn_decl) {
|
||||
const method_fd = &method_node.data.fn_decl;
|
||||
// Build qualified name: StructName.method
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sd.name, method_fd.name }) catch continue;
|
||||
self.program_index.fn_ast_map.put(qualified, method_fd) catch {};
|
||||
// Declare extern stub (body is lowered lazily on demand)
|
||||
self.declareFunction(method_fd, qualified);
|
||||
}
|
||||
}
|
||||
|
||||
// Register struct-level constants (e.g., GRAVITY :f32: 9.81)
|
||||
for (sd.constants) |const_node| {
|
||||
if (const_node.data == .const_decl) {
|
||||
const cd = const_node.data.const_decl;
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sd.name, cd.name }) catch continue;
|
||||
const ty: ?TypeId = if (cd.type_annotation) |ta| type_bridge.resolveAstType(ta, table, &self.program_index.type_alias_map, &self.program_index.module_const_map) else null;
|
||||
self.struct_const_map.put(qualified, .{ .value = cd.value, .ty = ty }) catch {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub 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`.
|
||||
pub 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
|
||||
pub fn qualifyAnonType(self: *Lowering, table: *types.TypeTable, ty: TypeId, parent_name: []const u8, field_name: []const u8) void {
|
||||
const ti = table.get(ty);
|
||||
switch (ti) {
|
||||
.@"union" => |u| {
|
||||
const old_name = table.getString(u.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.replaceKeyedInfo(ty, .{ .@"union" = .{ .name = qname_id, .fields = u.fields } });
|
||||
},
|
||||
.tagged_union => |u| {
|
||||
const old_name = table.getString(u.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);
|
||||
// Rename variant payload structs: __anon.X → ParentStruct.field.X
|
||||
for (u.fields) |f| {
|
||||
if (!f.ty.isBuiltin()) {
|
||||
const finfo = table.get(f.ty);
|
||||
if (finfo == .@"struct") {
|
||||
const sname = table.getString(finfo.@"struct".name);
|
||||
if (std.mem.startsWith(u8, sname, "__anon.")) {
|
||||
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.replaceKeyedInfo(f.ty, .{ .@"struct" = .{ .name = sq_id, .fields = finfo.@"struct".fields } });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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.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.replaceKeyedInfo(ty, .{ .@"struct" = .{ .name = qname_id, .fields = s.fields } });
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user