refactor(R2): selectNominalLeaf uses single collectVisibleAuthors walk

Replaces the 3 separate author-collection calls inside selectNominalLeaf
(moduleTypeAuthor + ownConstDeclIsPendingAlias + flatTypeAuthorCount +
forwardAliasOrUndeclared) with a single collectVisibleAuthors call plus
inline type-specific resolution. The flat walk now handles:
- own named type: resolved or forward (slot not yet interned)
- own const_decl: resolved alias or pending (own wins over flat)
- flat named types: ambiguous / resolved / forward
- flat const_decl pending aliases: pending (for forward aliases in imports)

Deletes 3 now-unused helpers: forwardAliasOrUndeclared, constAuthor,
ownConstDeclIsPendingAlias. Net: -17 lines.

541/541 regression tests pass. Issue 0107 repro still outputs 300.
This commit is contained in:
agra
2026-06-09 22:31:12 +03:00
parent 010e644897
commit 3b4df4ab8d

View File

@@ -2015,115 +2015,112 @@ pub const Lowering = struct {
// undeclared).
if (self.program_index.module_decls == null or self.program_index.flat_import_graph == null) {
if (registered) |existing| return .{ .resolved = existing };
return self.forwardAliasOrUndeclared(name, from);
// Direct per-source lookup for resolved alias, then pending check.
if (self.program_index.type_aliases_by_source.get(from)) |inner| {
if (inner.get(name)) |tid| return .{ .resolved = tid };
}
if (self.program_index.module_decls) |decls| {
if (decls.get(from)) |m| if (m.names.get(name)) |ref| if (ref == .const_decl) return .pending;
}
return .undeclared;
}
// 1. A flat-import-visible TYPE author (named type OR alias) resolve to
// its PER-SOURCE declared TypeId (E2). The author KIND decides
// resolution: an ALIAS resolves to its `type_aliases_by_source` target;
// a NAMED type resolves to its per-decl `type_decl_tids` nominal
// identity so two same-name structs authored in different sources
// return their OWN distinct TypeIds instead of collapsing last-wins
// (issue 0105). A named author not registered yet (a forward / self
// reference like `next: *ArenaChunk` resolved mid-registration) yields
// `.undeclared` the legacy empty-struct stub, reconciled by
// `internNamedTypeDecl` adopting that stub when the type registers.
//
// The querying source's OWN author wins outright (own-wins, 0105 case
// 3); otherwise the single-hop direct flat-import set is searched, and
// 2 DISTINCT flat-visible authors `.ambiguous` (0105 case 4). Single-
// author keeps 1 author across that set, so this stays byte-identical
// to the legacy leaf.
if (self.moduleTypeAuthor(from, name)) |author| switch (author) {
.alias => |tid| return .{ .resolved = tid },
.named => |ref| {
if (self.namedRefTid(ref, name)) |tid| return .{ .resolved = tid };
// The author exists but its slot is not interned yet (self /
// forward / mutual reference resolved mid-registration) a
// forward stub the type adopts when it registers, NOT undeclared.
return .forward;
// Single graph-walk over flat imports: one `collectVisibleAuthors` call
// replaces `moduleTypeAuthor` + `ownConstDeclIsPendingAlias` +
// `flatTypeAuthorCount` + `forwardAliasOrUndeclared`.
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);
// 1a. Own type author wins outright (own-wins, issue 0105/0107).
if (author_set.own) |own| switch (own.raw) {
.const_decl => {
// Type alias: present in type_aliases_by_source resolved.
if (self.program_index.type_aliases_by_source.get(own.source)) |inner| {
if (inner.get(name)) |tid| return .{ .resolved = tid };
}
// Own const_decl not yet resolved: pending (own takes priority
// over any flat author prevents issue 0107 flat-preemption).
return .pending;
},
else => if (isNamedTypeKind(own.raw)) {
if (self.namedRefTid(own.raw, name)) |tid| return .{ .resolved = tid };
return .forward; // named type exists but slot not yet interned
},
// fn_decl / namespace_decl: not a type author, fall to flat walk
};
// Own-wins before flat: if this module has an own `const_decl` for
// `name` that is not yet in `type_aliases_by_source`, it is a forward
// type alias pending the fixpoint. Deferring here prevents a flat-import's
// same-name type alias from winning before the own declaration is processed
// (issue 0107: flat `B :: u8` would preempt the own `B :: u64`).
if (self.ownConstDeclIsPendingAlias(from, name)) return .pending;
switch (self.flatTypeAuthorCount(name, from)) {
.none => {},
.one => |tid| return .{ .resolved = tid },
.ambiguous => return .ambiguous,
// A flat author exists but is not registered as a findByName-able type
// yet (a forward reference, or a foreign / lazily-registered class)
// the legacy empty-struct stub, NOT a namespaced-only leak (arm 3).
.unregistered => return .forward,
// 1b. Flat type authors (named types and resolved aliases only; pending
// flat aliases handled below).
var found_tid: ?TypeId = null;
var flat_type_count: usize = 0;
var flat_has_unregistered = false;
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| {
if (found_tid) |f| { if (t != f) return .ambiguous; } else found_tid = t;
} else {
flat_has_unregistered = true;
}
}
if (flat_type_count > 0) {
if (found_tid) |t| return .{ .resolved = t };
return .forward; // flat author exists but TypeId not yet registered
}
// 1c. Pending flat aliases (const_decl in a flat-imported module but not
// yet resolved in type_aliases_by_source the forward-alias fixpoint
// will settle these).
for (author_set.flat) |fa| {
if (fa.raw == .const_decl) {
if (self.program_index.type_aliases_by_source.get(fa.source)) |inner| {
if (inner.get(name)) |tid| return .{ .resolved = tid };
}
return .pending;
}
}
// 2. A block-local type (declared inside a fn / init body) clobbers the
// global entry for its name, so `existing` IS that local type. A local is
// visible ONLY in its OWN source. Resolve it ungated when the query
// originates in the local's source (R2): a legitimately-scoped local must
// originates in the local's source: a legitimately-scoped local must
// not be rejected just because a namespaced-only import also authors a
// top-level type of the same name. When the same name is a block-local of a
// DIFFERENT source e.g. an imported template's field (resolved in the
// template's source context, E3 attempt-4) naming a type the CALLER
// declared block-local the local is NOT visible here: route to the
// undeclared path so the leak surfaces, never the `registered` catch-all
// (arm 4) which would resolve the globally-registered cross-source local.
// DIFFERENT source e.g. an imported template's field naming a type the
// CALLER declared block-local the local is NOT visible here.
if (self.localTypeInSource(from, name)) {
if (registered) |existing| return .{ .resolved = existing };
} else if (self.localTypeInAnySource(name)) {
return self.forwardAliasOrUndeclared(name, from);
return .undeclared; // local in another source; no pending alias possible here
}
// 3. Authored as a TYPE (named OR alias) in some module, but NOT flat-
// import-reachable from `from` and NOT shadowed by a local reachable
// only over a namespace edge leak. Return `.not_visible`;
// `resolveNominalLeaf` surfaces the diagnostic and the `.unresolved`
// sentinel (qualify it `ns.Type`, Phase F).
// import-reachable from `from` reachable only over a namespace edge.
if (self.nameAuthoredAsTypeAnywhere(name)) return .not_visible;
// 4. Not a cross-module type author. A registered generic type-param bound
// or fabricated empty-struct stub (findByName hit, no module_decls
// author) resolves ungated. Otherwise a forward identifier alias
// (visible const author, target not resolved yet `.pending`, back to
// the fixpoint) or `.undeclared`.
// or fabricated empty-struct stub resolves ungated.
if (registered) |existing| return .{ .resolved = existing };
return self.forwardAliasOrUndeclared(name, from);
}
/// The forward-alias / undeclared tail of `selectNominalLeaf`: a bare nominal
/// name that is neither a flat-visible type author, a local, nor a leak.
/// Selects the single-hop const author (E1: `collectVisibleAuthors` returns
/// 1) and, if its alias target is not yet in `type_aliases_by_source`,
/// returns `.pending` so the forward-alias fixpoint re-resolves it (source-
/// aware in E1.5). A resolved flat-visible alias is already returned by
/// `moduleTypeAuthor` (arm 1) above, so the `inner.get` here only catches a
/// const author reachable via `collectVisibleAuthors` whose target landed
/// between the two reads the fixpoint path is the common outcome.
fn forwardAliasOrUndeclared(self: *Lowering, name: []const u8, from: []const u8) TypeHeadResolution {
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 (constAuthor(set)) |author| {
if (self.program_index.type_aliases_by_source.get(author.source)) |inner| {
if (inner.get(name)) |alias_ty| return .{ .resolved = alias_ty };
}
return .pending;
}
return .undeclared;
}
/// The single `const_decl` (alias-or-value const) author of a collected
/// `AuthorSet`. E1 is single-author (`collectVisibleAuthors` returns 1), so
/// own-then-flat picks the one author. E2 adds shadow ambiguity.
fn constAuthor(set: resolver_mod.AuthorSet) ?resolver_mod.RawAuthor {
if (set.own) |o| if (o.raw == .const_decl) return o;
for (set.flat) |fa| if (fa.raw == .const_decl) return fa;
return null;
}
/// 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
@@ -2157,20 +2154,6 @@ pub const Lowering = struct {
/// `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).
/// True when `from` declares `name` as a `const_decl` that is NOT yet
/// registered in `type_aliases_by_source` an unprocessed forward type alias
/// whose own-module resolution should take precedence over flat imports.
fn ownConstDeclIsPendingAlias(self: *Lowering, from: []const u8, name: []const u8) bool {
const decls = self.program_index.module_decls orelse return false;
const m = decls.get(from) orelse return false;
const ref = m.names.get(name) orelse return false;
if (std.meta.activeTag(ref) != .const_decl) return false;
if (self.program_index.type_aliases_by_source.get(from)) |inner| {
if (inner.contains(name)) return false;
}
return true;
}
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 };