refactor(R3): migrate headTypeGate + bareVisibleStructDecl to single walk

headTypeGate and bareVisibleStructDecl were using the same
moduleTypeAuthor + flatTypeAuthorCount pattern that selectNominalLeaf
used before R2. Migrated both to a single collectVisibleAuthors call
with inline type-specific resolution, matching the R2 pattern.

Deleted now-unused helpers: moduleTypeAuthor, FlatTypeAuthor,
moduleTypeAuthorTid, FlatTypeAuthorCount, flatTypeAuthorCount.
Net: -76 lines.

541/541 regression tests pass. 426/426 unit tests pass.
This commit is contained in:
agra
2026-06-09 22:37:18 +03:00
parent 3b4df4ab8d
commit 2d68beb053

View File

@@ -2123,10 +2123,8 @@ pub const Lowering = struct {
/// TRUE iff `raw` declares a NAMED TYPE struct / enum / union / error-set /
/// protocol / foreign class. A `fn_decl`, a value-or-alias `const_decl`, and a
/// `namespace_decl` are NOT named types. A type ALIAS is a `const_decl`; it is
/// recognised as a type author NOT here but via `type_aliases_by_source`
/// (E0's source-keyed cache) in `moduleTypeAuthor`, so the two type-author
/// kinds named type and alias gate identically (R4).
/// `namespace_decl` are NOT named types. A type ALIAS is a `const_decl`;
/// it is recognised via `type_aliases_by_source` separately from named types.
fn isNamedTypeKind(raw: resolver_mod.RawDeclRef) bool {
return switch (raw) {
.struct_decl, .enum_decl, .union_decl, .error_set_decl, .protocol_decl, .foreign_class_decl => true,
@@ -2134,37 +2132,6 @@ pub const Lowering = struct {
};
}
/// A module's authorship of a bare type `name`: an ALIAS (carrying the
/// resolved target `TypeId` from `type_aliases_by_source`) or a NAMED type
/// (struct/enum/union/error-set/protocol/foreign class carrying its
/// `RawDeclRef` so the use site can resolve its PER-DECL nominal TypeId via
/// `type_decl_tids`, decoupled from registration timing so a not-yet-registered
/// forward / self reference is still recognised as an author).
const FlatTypeAuthor = union(enum) {
alias: TypeId,
named: resolver_mod.RawDeclRef,
};
/// How module `path` authors `name` AS A TYPE, or null if it does not. A type
/// author is EITHER a type ALIAS (`Name :: <type>`, recorded in E0's
/// `type_aliases_by_source` checked first via the source-keyed cache) OR a
/// NAMED type (recognised by its `module_decls` decl KIND; the `RawDeclRef` is
/// carried so the use site resolves its per-decl nominal TypeId). A same-name
/// VALUE/FUNCTION is NOT a type author (R1); a value-const (`N :: 7`) lives in
/// `module_consts_by_source`, never `type_aliases_by_source`, so it returns
/// null too. THE per-module "is `name` a type author here?" predicate the
/// single source of truth for the visibility walk (R4).
fn moduleTypeAuthor(self: *Lowering, path: []const u8, name: []const u8) ?FlatTypeAuthor {
if (self.program_index.type_aliases_by_source.get(path)) |inner| {
if (inner.get(name)) |tid| return .{ .alias = tid };
}
const decls = self.program_index.module_decls orelse return null;
const m = decls.get(path) orelse return null;
const ref = m.names.get(name) orelse return null;
if (!isNamedTypeKind(ref)) return null;
return .{ .named = ref };
}
/// The per-decl nominal TypeId of a NAMED-type `RawDeclRef` author, or null
/// when its slot is not registered yet (a forward / self reference resolved
/// mid-registration the caller yields the legacy empty-struct stub). A
@@ -2193,79 +2160,6 @@ pub const Lowering = struct {
};
}
/// The per-source TypeId module `path` authors for bare `name`, or null. The
/// alias-or-named resolution behind the ambiguity walk: an ALIAS yields its
/// source-keyed target; a NAMED type yields its per-decl nominal TypeId (null
/// while still unregistered, so it does not count toward ambiguity mid-scan).
fn moduleTypeAuthorTid(self: *Lowering, path: []const u8, name: []const u8) ?TypeId {
return switch (self.moduleTypeAuthor(path, name) orelse return null) {
.alias => |tid| tid,
.named => |ref| self.namedRefTid(ref, name),
};
}
/// What bare `name`'s type authors look like across the SINGLE-HOP flat-import
/// set of `from` its DIRECT bare `#import` edges only, NOT the transitive
/// closure (E4: consistent with the bare VALUE/FUNCTION/CONST leaves and
/// example 0706; the interim transitive closure E1 shipped is gone). The
/// querying source's OWN author is consulted by `selectNominalLeaf` first
/// (own-wins), so this surveys only the cross-module direct-flat authors:
/// - `.ambiguous` 2 DISTINCT resolved TypeIds (issue 0105 case 4);
/// - `.one` exactly one distinct resolved TypeId;
/// - `.unregistered` exactly ONE flat author found and it does not resolve
/// to a TypeId yet (a forward reference, or a foreign/lazily-registered
/// author with no `findByName` slot) the caller yields the legacy stub,
/// NOT a leak;
/// - `.none` no flat author at all the caller proceeds to the
/// local / leak / forward-alias arms.
/// Distinctness is BY TypeId: each distinct author holds a distinct
/// `nominal_id` TypeId, while a diamond import of the SAME module yields the
/// same TypeId, so byte-identical de-dup falls out. 2 distinct flat authors
/// that do NOT all collapse onto one shared TypeId are `.ambiguous` even when
/// none carries a concrete TypeId yet two same-name GENERIC TEMPLATES (whose
/// template name is registered in no `findByName` slot) are a genuine
/// collision the source cannot disambiguate, exactly like two registered
/// structs (issue 0105 case 4). A library template's
/// INTERNAL bare-TYPE refs (a 2-flat-hop type like `List(T).append`'s
/// `alloc: Allocator`) stay resolvable because instantiation is source-pinned
/// to the template's defining module (E4 #1), so the query originates THERE
/// where the type is a direct flat import not at the cross-module call site.
/// The walk lives in `lower.zig`, NOT `resolver.zig` the single-graph-walk
/// invariant (one `flat_import_graph` iterator in `resolver.zig`) is untouched.
const FlatTypeAuthorCount = union(enum) { none, one: TypeId, ambiguous, unregistered };
fn flatTypeAuthorCount(self: *Lowering, name: []const u8, from: []const u8) FlatTypeAuthorCount {
const graph = self.program_index.flat_import_graph orelse return .none;
const direct = graph.get(from) orelse return .none;
var found: ?TypeId = null;
var authors: usize = 0;
var tid_authors: usize = 0;
var it = direct.iterator();
while (it.next()) |kv| {
const dep = kv.key_ptr.*;
if (self.moduleTypeAuthor(dep, name) != null) {
authors += 1;
if (self.moduleTypeAuthorTid(dep, name)) |tid| {
tid_authors += 1;
if (found) |f| {
if (tid != f) return .ambiguous;
} else found = tid;
}
}
}
if (authors == 0) return .none;
// 2 distinct flat authors that do NOT all collapse onto a single shared
// TypeId are genuinely ambiguous: two same-name GENERIC TEMPLATES (neither
// carries a concrete TypeId, `tid_authors == 0`), a registered author
// colliding with a same-name forward / template author (`tid_authors <
// authors`), or caught above by the per-TypeId early return two
// distinct registered TypeIds. Only when EVERY author resolved to ONE
// shared TypeId (a diamond import, or two aliases onto the same target)
// does it collapse to `.one`.
if (authors >= 2 and !(tid_authors == authors and found != null)) return .ambiguous;
if (found) |t| return .{ .one = t };
return .unregistered;
}
/// TRUE iff `name` is authored as a TYPE a NAMED type OR a type ALIAS in
/// ANY module's raw facts. The leak detector: a name that is a type author
/// somewhere but not flat-visible from the querying module is reachable only
@@ -14284,38 +14178,69 @@ pub const Lowering = struct {
if (self.emitting_default_context) return .proceed;
if (self.program_index.module_decls == null or self.program_index.flat_import_graph == null) return .proceed;
const from = self.current_source_file orelse return .proceed;
// The querying source's OWN author wins outright (own-wins, 0754) even
// against a same-name flat import. Resolve to ITS per-source TypeId so a
// NON-leaf bare-type site (reflection / type-arg / array-literal /
// type-value / match arm) uses the own author, NOT whichever same-name
// flat author a global `findByName` would pick. Mirrors
// `selectNominalLeaf`'s own-author arm. A not-yet-registered own author (a
// forward / self reference resolved mid-registration, or a generic
// template the head path instantiates) carries no concrete TypeId yet
// `.proceed`, so the existing instantiation / stub path handles it.
if (self.moduleTypeAuthor(from, name)) |author| switch (author) {
.alias => |tid| return .{ .resolved = tid },
.named => |ref| {
if (self.namedRefTid(ref, name)) |tid| return .{ .resolved = tid };
var res_walk = self.resolver();
const author_set = res_walk.collectVisibleAuthors(name, from, .user_bare_flat);
defer if (author_set.flat.len > 0) self.alloc.free(author_set.flat);
// Own author wins outright (own-wins, 0754). Pending / unregistered .proceed.
if (author_set.own) |own| switch (own.raw) {
.const_decl => {
if (self.program_index.type_aliases_by_source.get(own.source)) |inner| {
if (inner.get(name)) |tid| return .{ .resolved = tid };
}
return .proceed;
},
else => if (isNamedTypeKind(own.raw)) {
if (self.namedRefTid(own.raw, name)) |tid| return .{ .resolved = tid };
return .proceed;
},
};
switch (self.flatTypeAuthorCount(name, from)) {
.none => {},
.one => |tid| return .{ .resolved = tid },
.ambiguous => {
// Flat type authors
var flat_type_count: usize = 0;
var found_tid: ?TypeId = null;
var flat_tid_count: usize = 0;
for (author_set.flat) |fa| {
const is_type = switch (fa.raw) {
.const_decl => blk: {
if (self.program_index.type_aliases_by_source.get(fa.source)) |inner|
break :blk inner.contains(name);
break :blk false;
},
else => isNamedTypeKind(fa.raw),
};
if (!is_type) continue;
flat_type_count += 1;
const fa_tid: ?TypeId = switch (fa.raw) {
.const_decl => blk: {
if (self.program_index.type_aliases_by_source.get(fa.source)) |inner|
break :blk inner.get(name);
break :blk null;
},
else => self.namedRefTid(fa.raw, name),
};
if (fa_tid) |t| {
flat_tid_count += 1;
if (found_tid) |f| { if (t != f) {
if (self.diagnostics) |d|
d.addFmt(.err, span, "type '{s}' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import", .{name});
return .ambiguous;
} } else found_tid = t;
}
}
if (flat_type_count > 0) {
// 2 authors but not all resolved to one TypeId ambiguous
if (flat_type_count >= 2 and !(flat_tid_count == flat_type_count and found_tid != null)) {
if (self.diagnostics) |d|
d.addFmt(.err, span, "type '{s}' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import", .{name});
return .ambiguous;
},
// A single flat author exists but its TypeId is not registered yet (a
// forward reference, a foreign / lazily-registered class, or a generic
// template) fall open so the caller instantiates / stubs it.
.unregistered => return .proceed,
}
if (found_tid) |t| return .{ .resolved = t };
return .proceed; // single author exists but TypeId not registered
}
// A block-local type / generic declared in THIS source is visible here.
if (self.localTypeInSource(from, name)) return .proceed;
// Not a cross-module type author at all nothing to gate.
if (!self.nameAuthoredAsTypeAnywhere(name)) return .proceed;
if (self.diagnostics) |d|
d.addFmt(.err, span, "type '{s}' is not visible; #import the module that declares it", .{name});
@@ -15402,27 +15327,26 @@ pub const Lowering = struct {
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 "";
// own-wins: the querying source's own generic-struct author shadows imports.
if (self.moduleTypeAuthor(from, name)) |author| {
const sd = switch (author) {
.named => |ref| structDeclOfRaw(ref) orelse return null,
.alias => return null,
};
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 };
}
// Otherwise: the single 1-hop flat-import generic-struct author.
var res = self.resolver();
const set = res.collectVisibleAuthors(name, from, .user_bare_flat);
defer if (set.flat.len > 0) self.alloc.free(set.flat);
if (set.own != null) return null; // own non-type shadow leave to existing paths
// 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 gate diagnoses ambiguity
if (picked != null) return null; // 2 visible authors
picked = sd;
picked_src = fa.source;
}