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:
212
src/ir/lower.zig
212
src/ir/lower.zig
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user