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 /
|
/// 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
|
/// 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
|
/// `namespace_decl` are NOT named types. A type ALIAS is a `const_decl`;
|
||||||
/// recognised as a type author NOT here but via `type_aliases_by_source`
|
/// it is recognised via `type_aliases_by_source` separately from named types.
|
||||||
/// (E0's source-keyed cache) in `moduleTypeAuthor`, so the two type-author
|
|
||||||
/// kinds — named type and alias — gate identically (R4).
|
|
||||||
fn isNamedTypeKind(raw: resolver_mod.RawDeclRef) bool {
|
fn isNamedTypeKind(raw: resolver_mod.RawDeclRef) bool {
|
||||||
return switch (raw) {
|
return switch (raw) {
|
||||||
.struct_decl, .enum_decl, .union_decl, .error_set_decl, .protocol_decl, .foreign_class_decl => true,
|
.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
|
/// 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
|
/// when its slot is not registered yet (a forward / self reference resolved
|
||||||
/// mid-registration → the caller yields the legacy empty-struct stub). A
|
/// 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
|
/// 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
|
/// 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
|
/// 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.emitting_default_context) return .proceed;
|
||||||
if (self.program_index.module_decls == null or self.program_index.flat_import_graph == null) 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;
|
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
|
var res_walk = self.resolver();
|
||||||
// NON-leaf bare-type site (reflection / type-arg / array-literal /
|
const author_set = res_walk.collectVisibleAuthors(name, from, .user_bare_flat);
|
||||||
// type-value / match arm) uses the own author, NOT whichever same-name
|
defer if (author_set.flat.len > 0) self.alloc.free(author_set.flat);
|
||||||
// flat author a global `findByName` would pick. Mirrors
|
|
||||||
// `selectNominalLeaf`'s own-author arm. A not-yet-registered own author (a
|
// Own author wins outright (own-wins, 0754). Pending / unregistered → .proceed.
|
||||||
// forward / self reference resolved mid-registration, or a generic
|
if (author_set.own) |own| switch (own.raw) {
|
||||||
// template the head path instantiates) carries no concrete TypeId yet →
|
.const_decl => {
|
||||||
// `.proceed`, so the existing instantiation / stub path handles it.
|
if (self.program_index.type_aliases_by_source.get(own.source)) |inner| {
|
||||||
if (self.moduleTypeAuthor(from, name)) |author| switch (author) {
|
if (inner.get(name)) |tid| return .{ .resolved = tid };
|
||||||
.alias => |tid| return .{ .resolved = tid },
|
}
|
||||||
.named => |ref| {
|
return .proceed;
|
||||||
if (self.namedRefTid(ref, name)) |tid| return .{ .resolved = tid };
|
},
|
||||||
|
else => if (isNamedTypeKind(own.raw)) {
|
||||||
|
if (self.namedRefTid(own.raw, name)) |tid| return .{ .resolved = tid };
|
||||||
return .proceed;
|
return .proceed;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
switch (self.flatTypeAuthorCount(name, from)) {
|
|
||||||
.none => {},
|
// Flat type authors
|
||||||
.one => |tid| return .{ .resolved = tid },
|
var flat_type_count: usize = 0;
|
||||||
.ambiguous => {
|
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|
|
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});
|
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;
|
return .ambiguous;
|
||||||
},
|
}
|
||||||
// A single flat author exists but its TypeId is not registered yet (a
|
if (found_tid) |t| return .{ .resolved = t };
|
||||||
// forward reference, a foreign / lazily-registered class, or a generic
|
return .proceed; // single author exists but TypeId not registered
|
||||||
// template) — fall open so the caller instantiates / stubs it.
|
|
||||||
.unregistered => return .proceed,
|
|
||||||
}
|
}
|
||||||
// A block-local type / generic declared in THIS source is visible here.
|
|
||||||
if (self.localTypeInSource(from, name)) return .proceed;
|
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.nameAuthoredAsTypeAnywhere(name)) return .proceed;
|
||||||
if (self.diagnostics) |d|
|
if (self.diagnostics) |d|
|
||||||
d.addFmt(.err, span, "type '{s}' is not visible; #import the module that declares it", .{name});
|
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 from = self.current_source_file orelse return null;
|
||||||
const canon = self.program_index.struct_template_map.get(name) orelse return null;
|
const canon = self.program_index.struct_template_map.get(name) orelse return null;
|
||||||
const canon_src = canon.source_file orelse "";
|
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| {
|
var res_walk = self.resolver();
|
||||||
const sd = switch (author) {
|
const set = res_walk.collectVisibleAuthors(name, from, .user_bare_flat);
|
||||||
.named => |ref| structDeclOfRaw(ref) orelse return null,
|
defer if (set.flat.len > 0) self.alloc.free(set.flat);
|
||||||
.alias => return null,
|
|
||||||
};
|
// 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 (sd.type_params.len == 0) return null;
|
||||||
if (std.mem.eql(u8, from, canon_src)) return null;
|
if (std.mem.eql(u8, from, canon_src)) return null;
|
||||||
return .{ .sd = sd, .source = from };
|
return .{ .sd = sd, .source = from };
|
||||||
}
|
}
|
||||||
// Otherwise: the single 1-hop flat-import generic-struct author.
|
|
||||||
var res = self.resolver();
|
// Single flat-import generic-struct author.
|
||||||
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
|
|
||||||
var picked: ?*const ast.StructDecl = null;
|
var picked: ?*const ast.StructDecl = null;
|
||||||
var picked_src: []const u8 = "";
|
var picked_src: []const u8 = "";
|
||||||
for (set.flat) |fa| {
|
for (set.flat) |fa| {
|
||||||
const sd = structDeclOfRaw(fa.raw) orelse continue;
|
const sd = structDeclOfRaw(fa.raw) orelse continue;
|
||||||
if (sd.type_params.len == 0) 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 = sd;
|
||||||
picked_src = fa.source;
|
picked_src = fa.source;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user