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