fix(0115): source-aware global selection — own-wins for module globals
The globals registry (global_names) was last-wins across modules with no per-importer gate: any module's bare K could read/write/type against an unrelated module's same-named global (hash.sx's K table hijacked every user K once std's namespace tail pulled hash into the program), and an own const of an unsupported shape borrowed another module's const and panicked at the unresolved-type tripwire. - var_decl joins RawDeclRef: module globals are selectable raw authors. - selectGlobalAuthor (the globals analogue of F2's selectModuleConst): own author wins, one flat-visible author resolves, >=2 distinct flat authors diagnose loudly, authored-but-not-visible diagnoses, and a compiler-synthesized global (no raw author) emits untracked. A var_decl author whose per-source registration was deduped at flat-merge (two modules declaring the same extern symbol) serves the symbol's registration. - All bare-identifier global sites route through it: value read, addr-of, assignment (store + compound), lvalue address, fn-ptr call, call param typing, and expression type inference. - selectModuleConst gains .own_opaque: an own const author with no materialized per-source value (e.g. an array '::' const) blocks borrowing another module's same-named const — the read diagnoses cleanly instead of panicking. - The fn-as-VALUE arm admits raw-facts-only authors: an own fn whose name a flat-merge collision dropped from the global decl list (first-wins) now resolves via author selection for func_ref/closure/Any shapes too. Regressions: examples 0835 (own const vs flat array global), 0836 (main const vs namespaced array global, incl. inference), 0837 (own array const never borrows cross-module — clean unresolved).
This commit is contained in:
@@ -8,6 +8,7 @@ const unescape = @import("../../unescape.zig");
|
||||
const parser_mod = @import("../../parser.zig");
|
||||
const interp_mod = @import("../interp.zig");
|
||||
const program_index_mod = @import("../program_index.zig");
|
||||
const resolver_mod = @import("../resolver.zig");
|
||||
const ModuleConstInfo = program_index_mod.ModuleConstInfo;
|
||||
|
||||
const TypeId = types.TypeId;
|
||||
@@ -788,7 +789,7 @@ pub fn foldSourceConstInt(self: *Lowering, name: []const u8, frame: ?*const Cons
|
||||
defer restore.unpin();
|
||||
return program_index_mod.evalConstIntExpr(sel.info.value, SourceConstCtx{ .lowering = self, .frame = &f });
|
||||
},
|
||||
.ambiguous, .none => null,
|
||||
.own_opaque, .ambiguous, .none => null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -803,7 +804,7 @@ pub fn foldSourceConstFloat(self: *Lowering, name: []const u8, frame: ?*const Co
|
||||
defer restore.unpin();
|
||||
return program_index_mod.evalConstFloatExpr(sel.info.value, SourceConstCtx{ .lowering = self, .frame = &f });
|
||||
},
|
||||
.ambiguous, .none => null,
|
||||
.own_opaque, .ambiguous, .none => null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -819,7 +820,7 @@ pub fn sourceConstIsFloatTyped(self: *Lowering, name: []const u8, frame: ?*const
|
||||
defer restore.unpin();
|
||||
return program_index_mod.isFloatValuedExpr(sel.info.value, SourceConstCtx{ .lowering = self, .frame = &f });
|
||||
},
|
||||
.ambiguous, .none => false,
|
||||
.own_opaque, .ambiguous, .none => false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -836,6 +837,11 @@ pub const SelectedConst = struct {
|
||||
|
||||
const ConstAuthor = union(enum) {
|
||||
resolved: SelectedConst,
|
||||
/// The reader's OWN module authors `name` as a const, but no per-source
|
||||
/// value registered — an unsupported const shape (e.g. an array-literal
|
||||
/// const). The own author owns the name: the read must NOT borrow another
|
||||
/// module's same-named const, so callers treat it as unresolvable.
|
||||
own_opaque,
|
||||
ambiguous,
|
||||
none,
|
||||
};
|
||||
@@ -864,7 +870,14 @@ pub fn selectModuleConst(self: *Lowering, name: []const u8) ConstAuthor {
|
||||
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) |o| if (self.sourceModuleConst(o.source, name)) |ci| return .{ .resolved = .{ .info = ci, .source = o.source } };
|
||||
if (set.own) |o| {
|
||||
if (self.sourceModuleConst(o.source, name)) |ci| return .{ .resolved = .{ .info = ci, .source = o.source } };
|
||||
// The reader's own module authors `name` as a const that never
|
||||
// materialized a per-source value (unsupported shape). Owning the
|
||||
// name blocks borrowing a flat import's / the global registration's
|
||||
// same-named const (issue 0115).
|
||||
if (o.raw == .const_decl) return .own_opaque;
|
||||
}
|
||||
var the_one: ?SelectedConst = null;
|
||||
var count: usize = 0;
|
||||
for (set.flat) |fa| {
|
||||
@@ -884,6 +897,89 @@ pub fn sourceModuleConst(self: *Lowering, source: []const u8, name: []const u8)
|
||||
return inner.get(name);
|
||||
}
|
||||
|
||||
pub const GlobalAuthor = union(enum) {
|
||||
/// The visible author's OWN global (per-source partition) — emit it.
|
||||
resolved: program_index_mod.GlobalInfo,
|
||||
/// The visible author declares `name` but NOT as a global (a const, fn,
|
||||
/// type, ...) — skip the global arm and let the later arms decide.
|
||||
not_a_global,
|
||||
/// ≥2 distinct flat-visible authors and none is the reader's own.
|
||||
ambiguous,
|
||||
/// `name` is authored somewhere, but no author is visible from the
|
||||
/// reading module (namespaced-only / beyond one flat hop).
|
||||
not_visible,
|
||||
/// No raw author anywhere — a compiler-synthesized global (FFI metadata,
|
||||
/// trace machinery, ...). Emit the global registration directly.
|
||||
untracked,
|
||||
};
|
||||
|
||||
/// The source-aware GLOBAL author of `name` from the querying module — the
|
||||
/// global-variable analogue of `selectModuleConst`. The global registry
|
||||
/// (`global_names`) is last-wins across modules; this selects the AUTHOR
|
||||
/// first (own wins, then the single direct flat author) and reads the
|
||||
/// global from the SELECTED author's per-source partition, so a module
|
||||
/// whose own `K` is a const (or whose flat import authors `K`) never has a
|
||||
/// bare `K` hijacked by an unrelated module's same-named global.
|
||||
pub fn selectGlobalAuthor(self: *Lowering, name: []const u8) GlobalAuthor {
|
||||
const from = self.current_source_file orelse self.main_file orelse return .untracked;
|
||||
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) |o| return globalAuthorAt(self, o, name);
|
||||
if (set.flat.len >= 2) return .ambiguous;
|
||||
if (set.flat.len == 1) return globalAuthorAt(self, set.flat[0], name);
|
||||
return if (anyRawAuthor(self, name)) .not_visible else .untracked;
|
||||
}
|
||||
|
||||
fn globalAuthorAt(self: *Lowering, author: resolver_mod.RawAuthor, name: []const u8) GlobalAuthor {
|
||||
if (self.program_index.globals_by_source.get(author.source)) |inner| {
|
||||
if (inner.get(name)) |g| return .{ .resolved = g };
|
||||
}
|
||||
// A var_decl author with no per-source registration: the decl was deduped
|
||||
// at flat-merge (two modules declaring the same extern symbol), so the
|
||||
// global registered under the surviving author's source. The author IS a
|
||||
// global — serve the symbol's registration.
|
||||
if (author.raw == .var_decl) {
|
||||
if (self.program_index.global_names.get(name)) |g| return .{ .resolved = g };
|
||||
}
|
||||
return .not_a_global;
|
||||
}
|
||||
|
||||
/// The source-aware global for a bare reference to `name`, or null when the
|
||||
/// reference does not (visibly) resolve to a global — the ambiguous /
|
||||
/// not-visible outcomes diagnose here and return null so the caller's
|
||||
/// fall-through path runs. Sites needing custom placeholder emission use
|
||||
/// `selectGlobalAuthor` directly.
|
||||
pub fn resolveGlobalRef(self: *Lowering, name: []const u8, span: ?ast.Span) ?program_index_mod.GlobalInfo {
|
||||
const gi = self.program_index.global_names.get(name) orelse return null;
|
||||
switch (self.selectGlobalAuthor(name)) {
|
||||
.resolved => |g| return g,
|
||||
.not_a_global => return null,
|
||||
.ambiguous => {
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, span, "'{s}' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import", .{name});
|
||||
return null;
|
||||
},
|
||||
.not_visible => {
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, span, "'{s}' is not visible; #import the module that declares it", .{name});
|
||||
return null;
|
||||
},
|
||||
.untracked => return gi,
|
||||
}
|
||||
}
|
||||
|
||||
/// True when ANY module's raw-decl index carries `name` — distinguishes a
|
||||
/// user-authored-but-not-visible name from a compiler-synthesized one.
|
||||
fn anyRawAuthor(self: *Lowering, name: []const u8) bool {
|
||||
const decls = self.program_index.module_decls orelse return false;
|
||||
var it = decls.valueIterator();
|
||||
while (it.next()) |mod| {
|
||||
if (mod.names.contains(name)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Saved `current_source_file` for a const-author pin; `unpin()` restores it.
|
||||
const ConstSourcePin = struct {
|
||||
lowering: *Lowering,
|
||||
|
||||
Reference in New Issue
Block a user