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:
15
examples/0835-modules-same-name-global-vs-const-own.sx
Normal file
15
examples/0835-modules-same-name-global-vs-const-own.sx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// A module's own scalar const `K` and another module's same-named ARRAY
|
||||||
|
// GLOBAL (`K : [4]s64 = .[...]`) coexist: each module's bare `K` binds its
|
||||||
|
// OWN author. The global registry is last-wins across modules, so without
|
||||||
|
// source-aware selection a.sx's `K` read the array global's address.
|
||||||
|
//
|
||||||
|
// Regression (issue 0115): a.sx printed the array's address; h.sx's reads
|
||||||
|
// stayed correct only by registration order.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
#import "0835-modules-same-name-global-vs-const-own/a.sx";
|
||||||
|
h :: #import "0835-modules-same-name-global-vs-const-own/h.sx";
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
print("a_k={} use_k={}\n", a_k(), h.use_k());
|
||||||
|
}
|
||||||
2
examples/0835-modules-same-name-global-vs-const-own/a.sx
Normal file
2
examples/0835-modules-same-name-global-vs-const-own/a.sx
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
K :: 1;
|
||||||
|
a_k :: () -> s64 { K }
|
||||||
2
examples/0835-modules-same-name-global-vs-const-own/h.sx
Normal file
2
examples/0835-modules-same-name-global-vs-const-own/h.sx
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
K : [4]s64 = .[11, 22, 33, 44];
|
||||||
|
use_k :: () -> s64 { K[2] }
|
||||||
15
examples/0836-modules-own-const-vs-ns-array-global.sx
Normal file
15
examples/0836-modules-own-const-vs-ns-array-global.sx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// The MAIN file's own typed scalar const `K : s64 : 4` vs a namespaced
|
||||||
|
// module's same-named array global: the main file's bare `K` is its own
|
||||||
|
// scalar — type inference must not borrow the array global's type either
|
||||||
|
// (the print pack used to format the whole 4-element array).
|
||||||
|
//
|
||||||
|
// Regression (issue 0115).
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
h :: #import "0836-modules-own-const-vs-ns-array-global/h.sx";
|
||||||
|
|
||||||
|
K : s64 : 4;
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
print("K={} use_k={}\n", K, h.use_k());
|
||||||
|
}
|
||||||
2
examples/0836-modules-own-const-vs-ns-array-global/h.sx
Normal file
2
examples/0836-modules-own-const-vs-ns-array-global/h.sx
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
K : [4]s64 = .[11, 22, 33, 44];
|
||||||
|
use_k :: () -> s64 { K[2] }
|
||||||
17
examples/0837-modules-array-const-no-cross-borrow.sx
Normal file
17
examples/0837-modules-array-const-no-cross-borrow.sx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// An ARRAY-typed `::` const (`K : [4]s64 : .[...]`) is not a supported
|
||||||
|
// module-const shape — its own module's read diagnoses cleanly as
|
||||||
|
// unresolved. The own author OWNS the name: the read must never borrow
|
||||||
|
// another module's same-named scalar const (which used to type `K[2]`
|
||||||
|
// against the scalar and panic at LLVM emission).
|
||||||
|
//
|
||||||
|
// Regression (issue 0115). When array `::` consts land, repoint this
|
||||||
|
// example at the new behavior.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
h :: #import "0837-modules-array-const-no-cross-borrow/h.sx";
|
||||||
|
|
||||||
|
K : s64 : 4;
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
print("{}\n", h.use_k());
|
||||||
|
}
|
||||||
2
examples/0837-modules-array-const-no-cross-borrow/h.sx
Normal file
2
examples/0837-modules-array-const-no-cross-borrow/h.sx
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
K : [4]s64 : .[11, 22, 33, 44];
|
||||||
|
use_k :: () -> s64 { K[2] }
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
a_k=1 use_k=33
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
K=4 use_k=33
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
error: unresolved 'K' (in examples/0837-modules-array-const-no-cross-borrow/h.sx fn use_k)
|
||||||
|
--> examples/0837-modules-array-const-no-cross-borrow/h.sx:2:22
|
||||||
|
|
|
||||||
|
2 | use_k :: () -> s64 { K[2] }
|
||||||
|
| ^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -1,5 +1,23 @@
|
|||||||
# 0115 — same-name consts of different shapes collide across modules (panic / silent clobber)
|
# 0115 — same-name consts of different shapes collide across modules (panic / silent clobber)
|
||||||
|
|
||||||
|
> **RESOLVED** (2026-06-11). Root cause: the globals registry
|
||||||
|
> (`global_names`) is last-wins across modules and every read/write/addr/
|
||||||
|
> call site consulted it with no source-awareness; `var_decl` was not even
|
||||||
|
> a selectable raw author. Fix: `var_decl` joins `RawDeclRef`;
|
||||||
|
> `selectGlobalAuthor` (the globals analogue of F2's `selectModuleConst`)
|
||||||
|
> selects the author own-wins / one-flat-visible / ambiguous-loudly and
|
||||||
|
> serves the AUTHOR's per-source global; all bare-identifier global sites
|
||||||
|
> (read, addr-of, assignment, fn-ptr call, type inference) route through
|
||||||
|
> it. `selectModuleConst` gained `.own_opaque` so an own const author with
|
||||||
|
> no materialized value (unsupported shape, e.g. an array `::` const)
|
||||||
|
> blocks borrowing another module's same-named const instead of panicking.
|
||||||
|
> The fn-as-VALUE arm admits raw-facts-only authors (an own fn dropped from
|
||||||
|
> the global decl list by a flat-merge collision — the 0601 `test` case).
|
||||||
|
> Regression tests: examples/0835, 0836, 0837. The co-blockers also
|
||||||
|
> landed: dead-global elimination at emit (unreferenced plain-data globals
|
||||||
|
> are not emitted) and 1055/1056 no longer pin global error ordinals —
|
||||||
|
> the full std namespace tail is enabled on top.
|
||||||
|
|
||||||
**Symptom.** When two modules in one program declare a same-named module
|
**Symptom.** When two modules in one program declare a same-named module
|
||||||
const with DIFFERENT shapes (scalar `K : s64 : 4` vs array
|
const with DIFFERENT shapes (scalar `K : s64 : 4` vs array
|
||||||
`K : [4]s64 : .[...]`), resolution conflates them instead of selecting
|
`K : [4]s64 : .[...]`), resolution conflates them instead of selecting
|
||||||
|
|||||||
@@ -457,6 +457,7 @@ pub const ModuleCache = std.StringHashMap(ResolvedModule);
|
|||||||
pub const RawDeclRef = union(enum) {
|
pub const RawDeclRef = union(enum) {
|
||||||
fn_decl: *const ast.FnDecl,
|
fn_decl: *const ast.FnDecl,
|
||||||
const_decl: *const ast.ConstDecl,
|
const_decl: *const ast.ConstDecl,
|
||||||
|
var_decl: *const ast.VarDecl,
|
||||||
struct_decl: *const ast.StructDecl,
|
struct_decl: *const ast.StructDecl,
|
||||||
enum_decl: *const ast.EnumDecl,
|
enum_decl: *const ast.EnumDecl,
|
||||||
union_decl: *const ast.UnionDecl,
|
union_decl: *const ast.UnionDecl,
|
||||||
@@ -499,13 +500,14 @@ pub const NamespaceTarget = struct {
|
|||||||
pub const NamespaceEdges = std.StringHashMap(std.StringHashMap(NamespaceTarget));
|
pub const NamespaceEdges = std.StringHashMap(std.StringHashMap(NamespaceTarget));
|
||||||
|
|
||||||
/// The `RawDeclRef` a top-level node carries, or null when the node is not a
|
/// The `RawDeclRef` a top-level node carries, or null when the node is not a
|
||||||
/// selectable named declaration (e.g. `impl_block`, `var_decl`, `ufcs_alias`,
|
/// selectable named declaration (e.g. `impl_block`, `ufcs_alias`, a flat
|
||||||
/// a flat `c_import_decl`). Public so the unified resolver's namespace collector
|
/// `c_import_decl`). Public so the unified resolver's namespace collector
|
||||||
/// can classify a `NamespaceTarget.own_decls` node without re-deriving the map.
|
/// can classify a `NamespaceTarget.own_decls` node without re-deriving the map.
|
||||||
pub fn rawDeclRefOf(decl: *const Node) ?RawDeclRef {
|
pub fn rawDeclRefOf(decl: *const Node) ?RawDeclRef {
|
||||||
return switch (decl.data) {
|
return switch (decl.data) {
|
||||||
.fn_decl => |*d| .{ .fn_decl = d },
|
.fn_decl => |*d| .{ .fn_decl = d },
|
||||||
.const_decl => |*d| .{ .const_decl = d },
|
.const_decl => |*d| .{ .const_decl = d },
|
||||||
|
.var_decl => |*d| .{ .var_decl = d },
|
||||||
.struct_decl => |*d| .{ .struct_decl = d },
|
.struct_decl => |*d| .{ .struct_decl = d },
|
||||||
.enum_decl => |*d| .{ .enum_decl = d },
|
.enum_decl => |*d| .{ .enum_decl = d },
|
||||||
.union_decl => |*d| .{ .union_decl = d },
|
.union_decl => |*d| .{ .union_decl = d },
|
||||||
@@ -584,6 +586,7 @@ pub fn buildImportFacts(
|
|||||||
pub const DeclKind = enum {
|
pub const DeclKind = enum {
|
||||||
function,
|
function,
|
||||||
constant,
|
constant,
|
||||||
|
global,
|
||||||
@"struct",
|
@"struct",
|
||||||
@"enum",
|
@"enum",
|
||||||
@"union",
|
@"union",
|
||||||
@@ -597,6 +600,7 @@ fn declKindOf(ref: RawDeclRef) DeclKind {
|
|||||||
return switch (ref) {
|
return switch (ref) {
|
||||||
.fn_decl => .function,
|
.fn_decl => .function,
|
||||||
.const_decl => .constant,
|
.const_decl => .constant,
|
||||||
|
.var_decl => .global,
|
||||||
.struct_decl => .@"struct",
|
.struct_decl => .@"struct",
|
||||||
.enum_decl => .@"enum",
|
.enum_decl => .@"enum",
|
||||||
.union_decl => .@"union",
|
.union_decl => .@"union",
|
||||||
|
|||||||
@@ -273,9 +273,17 @@ pub const ExprTyper = struct {
|
|||||||
if (self.l.implicit_ctx_enabled and std.mem.eql(u8, id.name, "context")) {
|
if (self.l.implicit_ctx_enabled and std.mem.eql(u8, id.name, "context")) {
|
||||||
if (self.l.module.types.findByName(self.l.module.types.internString("Context"))) |ty| return ty;
|
if (self.l.module.types.findByName(self.l.module.types.internString("Context"))) |ty| return ty;
|
||||||
}
|
}
|
||||||
// Check global variables (e.g., `context : Context`)
|
// Check global variables (e.g., `context : Context`) —
|
||||||
|
// source-aware (issue 0115): infer the AUTHOR's global type,
|
||||||
|
// never an unrelated module's same-named one. `.not_a_global`
|
||||||
|
// falls through to the const / fn arms below.
|
||||||
if (self.l.program_index.global_names.get(id.name)) |gi| {
|
if (self.l.program_index.global_names.get(id.name)) |gi| {
|
||||||
return gi.ty;
|
switch (self.l.selectGlobalAuthor(id.name)) {
|
||||||
|
.resolved => |g| return g.ty,
|
||||||
|
.untracked => return gi.ty,
|
||||||
|
.ambiguous, .not_visible => return .unresolved,
|
||||||
|
.not_a_global => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Check module-level value constants (e.g., WIDTH :f32: 800).
|
// Check module-level value constants (e.g., WIDTH :f32: 800).
|
||||||
// F4: a same-name VALUE const must infer the SOURCE-AWARE author's
|
// F4: a same-name VALUE const must infer the SOURCE-AWARE author's
|
||||||
@@ -290,7 +298,7 @@ pub const ExprTyper = struct {
|
|||||||
return switch (self.l.selectModuleConst(id.name)) {
|
return switch (self.l.selectModuleConst(id.name)) {
|
||||||
.resolved => |sel| sel.info.ty,
|
.resolved => |sel| sel.info.ty,
|
||||||
.none => ci_global.ty,
|
.none => ci_global.ty,
|
||||||
.ambiguous => .unresolved,
|
.own_opaque, .ambiguous => .unresolved,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// A bare type name (alias like `Vec4`, struct name, or
|
// A bare type name (alias like `Vec4`, struct name, or
|
||||||
|
|||||||
@@ -1508,6 +1508,9 @@ pub const Lowering = struct {
|
|||||||
pub const sourceConstIsFloatTyped = lower_comptime.sourceConstIsFloatTyped;
|
pub const sourceConstIsFloatTyped = lower_comptime.sourceConstIsFloatTyped;
|
||||||
pub const comptimeIntNamed = lower_comptime.comptimeIntNamed;
|
pub const comptimeIntNamed = lower_comptime.comptimeIntNamed;
|
||||||
pub const selectModuleConst = lower_comptime.selectModuleConst;
|
pub const selectModuleConst = lower_comptime.selectModuleConst;
|
||||||
|
pub const GlobalAuthor = lower_comptime.GlobalAuthor;
|
||||||
|
pub const selectGlobalAuthor = lower_comptime.selectGlobalAuthor;
|
||||||
|
pub const resolveGlobalRef = lower_comptime.resolveGlobalRef;
|
||||||
pub const sourceModuleConst = lower_comptime.sourceModuleConst;
|
pub const sourceModuleConst = lower_comptime.sourceModuleConst;
|
||||||
pub const pinConstAuthorSource = lower_comptime.pinConstAuthorSource;
|
pub const pinConstAuthorSource = lower_comptime.pinConstAuthorSource;
|
||||||
pub const foldComptimeFloatInit = lower_comptime.foldComptimeFloatInit;
|
pub const foldComptimeFloatInit = lower_comptime.foldComptimeFloatInit;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const unescape = @import("../../unescape.zig");
|
|||||||
const errors = @import("../../errors.zig");
|
const errors = @import("../../errors.zig");
|
||||||
const program_index_mod = @import("../program_index.zig");
|
const program_index_mod = @import("../program_index.zig");
|
||||||
const ProtocolMethodInfo = program_index_mod.ProtocolMethodInfo;
|
const ProtocolMethodInfo = program_index_mod.ProtocolMethodInfo;
|
||||||
|
const GlobalInfo = program_index_mod.GlobalInfo;
|
||||||
const CallResolver = @import("../calls.zig").CallResolver;
|
const CallResolver = @import("../calls.zig").CallResolver;
|
||||||
|
|
||||||
const TypeId = types.TypeId;
|
const TypeId = types.TypeId;
|
||||||
@@ -542,7 +543,7 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// May be a global variable holding a function pointer
|
// May be a global variable holding a function pointer
|
||||||
if (self.program_index.global_names.get(id.name)) |gi| {
|
if (self.resolveGlobalRef(id.name, c.callee.span)) |gi| {
|
||||||
if (!gi.ty.isBuiltin()) {
|
if (!gi.ty.isBuiltin()) {
|
||||||
const gti = self.module.types.get(gi.ty);
|
const gti = self.module.types.get(gi.ty);
|
||||||
if (gti == .function) {
|
if (gti == .function) {
|
||||||
@@ -2176,12 +2177,20 @@ pub fn resolveCallParamTypes(self: *Lowering, c: *const ast.Call, sel_author: ?*
|
|||||||
return types_list.items;
|
return types_list.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check global function pointer variables
|
// Check global function pointer variables (quiet author-aware lookup —
|
||||||
if (self.program_index.global_names.get(bare_name)) |gi| {
|
// param typing only; the call site diagnoses ambiguity / visibility)
|
||||||
if (!gi.ty.isBuiltin()) {
|
if (self.program_index.global_names.get(bare_name)) |gi_global| {
|
||||||
const ti = self.module.types.get(gi.ty);
|
const gi: ?GlobalInfo = switch (self.selectGlobalAuthor(bare_name)) {
|
||||||
if (ti == .function) {
|
.resolved => |g| g,
|
||||||
return ti.function.params;
|
.untracked => gi_global,
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
|
if (gi) |g| {
|
||||||
|
if (!g.ty.isBuiltin()) {
|
||||||
|
const ti = self.module.types.get(g.ty);
|
||||||
|
if (ti == .function) {
|
||||||
|
return ti.function.params;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const unescape = @import("../../unescape.zig");
|
|||||||
const parser_mod = @import("../../parser.zig");
|
const parser_mod = @import("../../parser.zig");
|
||||||
const interp_mod = @import("../interp.zig");
|
const interp_mod = @import("../interp.zig");
|
||||||
const program_index_mod = @import("../program_index.zig");
|
const program_index_mod = @import("../program_index.zig");
|
||||||
|
const resolver_mod = @import("../resolver.zig");
|
||||||
const ModuleConstInfo = program_index_mod.ModuleConstInfo;
|
const ModuleConstInfo = program_index_mod.ModuleConstInfo;
|
||||||
|
|
||||||
const TypeId = types.TypeId;
|
const TypeId = types.TypeId;
|
||||||
@@ -788,7 +789,7 @@ pub fn foldSourceConstInt(self: *Lowering, name: []const u8, frame: ?*const Cons
|
|||||||
defer restore.unpin();
|
defer restore.unpin();
|
||||||
return program_index_mod.evalConstIntExpr(sel.info.value, SourceConstCtx{ .lowering = self, .frame = &f });
|
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();
|
defer restore.unpin();
|
||||||
return program_index_mod.evalConstFloatExpr(sel.info.value, SourceConstCtx{ .lowering = self, .frame = &f });
|
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();
|
defer restore.unpin();
|
||||||
return program_index_mod.isFloatValuedExpr(sel.info.value, SourceConstCtx{ .lowering = self, .frame = &f });
|
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) {
|
const ConstAuthor = union(enum) {
|
||||||
resolved: SelectedConst,
|
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,
|
ambiguous,
|
||||||
none,
|
none,
|
||||||
};
|
};
|
||||||
@@ -864,7 +870,14 @@ pub fn selectModuleConst(self: *Lowering, name: []const u8) ConstAuthor {
|
|||||||
var res = self.resolver();
|
var res = self.resolver();
|
||||||
const set = res.collectVisibleAuthors(name, from, .user_bare_flat);
|
const set = res.collectVisibleAuthors(name, from, .user_bare_flat);
|
||||||
defer if (set.flat.len > 0) self.alloc.free(set.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 the_one: ?SelectedConst = null;
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
for (set.flat) |fa| {
|
for (set.flat) |fa| {
|
||||||
@@ -884,6 +897,89 @@ pub fn sourceModuleConst(self: *Lowering, source: []const u8, name: []const u8)
|
|||||||
return inner.get(name);
|
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.
|
/// Saved `current_source_file` for a const-author pin; `unpin()` restores it.
|
||||||
const ConstSourcePin = struct {
|
const ConstSourcePin = struct {
|
||||||
lowering: *Lowering,
|
lowering: *Lowering,
|
||||||
|
|||||||
@@ -1024,6 +1024,11 @@ pub fn globalInitValue(self: *Lowering, vd: *const ast.VarDecl, var_ty: TypeId)
|
|||||||
const sel: SelectedConst = switch (self.selectModuleConst(id.name)) {
|
const sel: SelectedConst = switch (self.selectModuleConst(id.name)) {
|
||||||
.resolved => |s| s,
|
.resolved => |s| s,
|
||||||
.none => .{ .info = ci_global, .source = null },
|
.none => .{ .info = ci_global, .source = null },
|
||||||
|
.own_opaque => {
|
||||||
|
if (self.diagnostics) |d|
|
||||||
|
d.addFmt(.err, v.span, "global '{s}' must be initialized by a compile-time constant; '{s}' is not a usable constant here", .{ vd.name, id.name });
|
||||||
|
break :blk null;
|
||||||
|
},
|
||||||
.ambiguous => {
|
.ambiguous => {
|
||||||
if (self.diagnostics) |d|
|
if (self.diagnostics) |d|
|
||||||
d.addFmt(.err, v.span, "'{s}' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import", .{id.name});
|
d.addFmt(.err, v.span, "'{s}' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import", .{id.name});
|
||||||
@@ -1570,7 +1575,7 @@ pub fn selectNominalLeaf(self: *Lowering, name: []const u8, from: []const u8, ra
|
|||||||
pub fn isNamedTypeKind(raw: resolver_mod.RawDeclRef) bool {
|
pub 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,
|
||||||
.fn_decl, .const_decl, .namespace_decl => false,
|
.fn_decl, .const_decl, .var_decl, .namespace_decl => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1598,7 +1603,7 @@ pub fn namedRefTid(self: *Lowering, ref: resolver_mod.RawDeclRef, name: []const
|
|||||||
.enum_decl => |d| (table.type_decl_tids.get(@ptrCast(d)) orelse table.findByName(table.internString(name))),
|
.enum_decl => |d| (table.type_decl_tids.get(@ptrCast(d)) orelse table.findByName(table.internString(name))),
|
||||||
.union_decl => |d| (table.type_decl_tids.get(@ptrCast(d)) orelse table.findByName(table.internString(name))),
|
.union_decl => |d| (table.type_decl_tids.get(@ptrCast(d)) orelse table.findByName(table.internString(name))),
|
||||||
.error_set_decl, .protocol_decl, .foreign_class_decl => table.findByName(table.internString(name)),
|
.error_set_decl, .protocol_decl, .foreign_class_decl => table.findByName(table.internString(name)),
|
||||||
.fn_decl, .const_decl, .namespace_decl => null,
|
.fn_decl, .const_decl, .var_decl, .namespace_decl => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1622,9 +1622,26 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
|||||||
};
|
};
|
||||||
break :blk self.builder.load(self.current_ctx_ref, ctx_ty);
|
break :blk self.builder.load(self.current_ctx_ref, ctx_ty);
|
||||||
}
|
}
|
||||||
// Check globals (#run constants)
|
// Check globals (#run constants) — source-aware (issue 0115):
|
||||||
|
// the global registry is last-wins across modules, so select the
|
||||||
|
// AUTHOR first and emit ITS global, never an unrelated module's
|
||||||
|
// same-named one.
|
||||||
if (self.program_index.global_names.get(id.name)) |gi| {
|
if (self.program_index.global_names.get(id.name)) |gi| {
|
||||||
break :blk self.builder.emit(.{ .global_get = gi.id }, gi.ty);
|
switch (self.selectGlobalAuthor(id.name)) {
|
||||||
|
.resolved => |g| break :blk self.builder.emit(.{ .global_get = g.id }, g.ty),
|
||||||
|
.not_a_global => {},
|
||||||
|
.ambiguous => {
|
||||||
|
if (self.diagnostics) |d|
|
||||||
|
d.addFmt(.err, node.span, "'{s}' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import", .{id.name});
|
||||||
|
break :blk self.emitPlaceholder(id.name);
|
||||||
|
},
|
||||||
|
.not_visible => {
|
||||||
|
if (self.diagnostics) |d|
|
||||||
|
d.addFmt(.err, node.span, "'{s}' is not visible; #import the module that declares it", .{id.name});
|
||||||
|
break :blk self.emitError(id.name, node.span);
|
||||||
|
},
|
||||||
|
.untracked => break :blk self.builder.emit(.{ .global_get = gi.id }, gi.ty),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Check module-level value constants (e.g. AF_INET :s32: 2)
|
// Check module-level value constants (e.g. AF_INET :s32: 2)
|
||||||
if (self.program_index.module_const_map.get(id.name)) |ci_global| {
|
if (self.program_index.module_const_map.get(id.name)) |ci_global| {
|
||||||
@@ -1640,6 +1657,10 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
|||||||
// author (no per-source partition) — emit its global value.
|
// author (no per-source partition) — emit its global value.
|
||||||
switch (self.selectModuleConst(id.name)) {
|
switch (self.selectModuleConst(id.name)) {
|
||||||
.resolved => |sel| break :blk self.emitModuleConst(sel.info, sel.source),
|
.resolved => |sel| break :blk self.emitModuleConst(sel.info, sel.source),
|
||||||
|
// Own const author with no materialized value (unsupported
|
||||||
|
// shape, e.g. an array const) — fall through; the tail of
|
||||||
|
// identifier lowering diagnoses it as unresolved.
|
||||||
|
.own_opaque => {},
|
||||||
.ambiguous => {
|
.ambiguous => {
|
||||||
if (self.diagnostics) |d|
|
if (self.diagnostics) |d|
|
||||||
d.addFmt(.err, node.span, "'{s}' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import", .{id.name});
|
d.addFmt(.err, node.span, "'{s}' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import", .{id.name});
|
||||||
@@ -1651,7 +1672,16 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
|||||||
// Check if it's a function name — produce function pointer reference
|
// Check if it's a function name — produce function pointer reference
|
||||||
// Resolve mangled name for block-local functions
|
// Resolve mangled name for block-local functions
|
||||||
const eff_fn_name = if (self.scope) |scope| scope.lookupFn(id.name) orelse id.name else id.name;
|
const eff_fn_name = if (self.scope) |scope| scope.lookupFn(id.name) orelse id.name else id.name;
|
||||||
if (self.program_index.fn_ast_map.contains(eff_fn_name)) {
|
// An own fn whose name a flat-merge collision dropped from the
|
||||||
|
// global decl list (first-wins) has no `fn_ast_map` entry but IS
|
||||||
|
// a raw-facts author — the author selection inside this arm
|
||||||
|
// serves it, so admit it through the gate.
|
||||||
|
const fn_author_only = !self.program_index.fn_ast_map.contains(eff_fn_name) and
|
||||||
|
std.mem.eql(u8, eff_fn_name, id.name) and
|
||||||
|
(if (self.scope) |scope| scope.lookup(id.name) == null else true) and
|
||||||
|
self.current_source_file != null and
|
||||||
|
self.selectPlainCallableAuthor(id.name, self.current_source_file.?) == .func;
|
||||||
|
if (self.program_index.fn_ast_map.contains(eff_fn_name) or fn_author_only) {
|
||||||
// Visibility check only for user-typed bare names (id.name
|
// Visibility check only for user-typed bare names (id.name
|
||||||
// == eff_fn_name) without a UFCS alias. Mangled local-
|
// == eff_fn_name) without a UFCS alias. Mangled local-
|
||||||
// scope names and UFCS rewrites are compiler indirections
|
// scope names and UFCS rewrites are compiler indirections
|
||||||
@@ -1666,11 +1696,18 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
|||||||
}
|
}
|
||||||
// Type-as-value: if target is Any (Type variable), produce a type name string
|
// Type-as-value: if target is Any (Type variable), produce a type name string
|
||||||
if (self.target_type == .any) {
|
if (self.target_type == .any) {
|
||||||
const fd = self.program_index.fn_ast_map.get(eff_fn_name).?;
|
const fd_any: ?*const ast.FnDecl = self.program_index.fn_ast_map.get(eff_fn_name) orelse fd_blk: {
|
||||||
const fn_type_str = self.formatFnTypeString(fd);
|
switch (self.selectPlainCallableAuthor(id.name, self.current_source_file.?)) {
|
||||||
const sid = self.module.types.internString(fn_type_str);
|
.func => |sf| break :fd_blk sf.decl,
|
||||||
const str = self.builder.constString(sid);
|
else => break :fd_blk null,
|
||||||
break :blk self.builder.boxAny(str, .string);
|
}
|
||||||
|
};
|
||||||
|
if (fd_any) |fd| {
|
||||||
|
const fn_type_str = self.formatFnTypeString(fd);
|
||||||
|
const sid = self.module.types.internString(fn_type_str);
|
||||||
|
const str = self.builder.constString(sid);
|
||||||
|
break :blk self.builder.boxAny(str, .string);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// taking a bare same-name fn as a VALUE
|
// taking a bare same-name fn as a VALUE
|
||||||
// (func_ref, fn-ptr / closure coercion) must capture the
|
// (func_ref, fn-ptr / closure coercion) must capture the
|
||||||
@@ -1835,7 +1872,7 @@ pub fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// address_of(global) → emit global_addr (pointer to global, not load)
|
// address_of(global) → emit global_addr (pointer to global, not load)
|
||||||
if (self.program_index.global_names.get(id_name)) |gi| {
|
if (self.resolveGlobalRef(id_name, node.span)) |gi| {
|
||||||
const ptr_ty = self.module.types.ptrTo(gi.ty);
|
const ptr_ty = self.module.types.ptrTo(gi.ty);
|
||||||
break :blk self.builder.emit(.{ .global_addr = gi.id }, ptr_ty);
|
break :blk self.builder.emit(.{ .global_addr = gi.id }, ptr_ty);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ pub fn rawNamedTypePtr(ref: resolver_mod.RawDeclRef) ?*const anyopaque {
|
|||||||
.error_set_decl => |d| @ptrCast(d),
|
.error_set_decl => |d| @ptrCast(d),
|
||||||
.protocol_decl => |d| @ptrCast(d),
|
.protocol_decl => |d| @ptrCast(d),
|
||||||
.foreign_class_decl => |d| @ptrCast(d),
|
.foreign_class_decl => |d| @ptrCast(d),
|
||||||
.fn_decl, .const_decl, .namespace_decl => null,
|
.fn_decl, .const_decl, .var_decl, .namespace_decl => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -552,8 +552,14 @@ pub fn lowerAssignment(self: *Lowering, asgn: *const ast.Assignment) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found_local) {
|
if (!found_local) {
|
||||||
|
// Quiet author-aware lookup (type inference only; the store
|
||||||
|
// site diagnoses ambiguity / visibility).
|
||||||
if (self.program_index.global_names.get(asgn.target.data.identifier.name)) |gi| {
|
if (self.program_index.global_names.get(asgn.target.data.identifier.name)) |gi| {
|
||||||
self.target_type = gi.ty;
|
switch (self.selectGlobalAuthor(asgn.target.data.identifier.name)) {
|
||||||
|
.resolved => |g| self.target_type = g.ty,
|
||||||
|
.untracked => self.target_type = gi.ty,
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (asgn.target.data == .index_expr) {
|
} else if (asgn.target.data == .index_expr) {
|
||||||
@@ -617,9 +623,11 @@ pub fn lowerAssignment(self: *Lowering, asgn: *const ast.Assignment) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fallback: global variable assignment
|
// Fallback: global variable assignment — source-aware (issue
|
||||||
|
// 0115): write the AUTHOR's global, never an unrelated module's
|
||||||
|
// same-named one.
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
if (self.program_index.global_names.get(id.name)) |gi| {
|
if (self.resolveGlobalRef(id.name, asgn.target.span)) |gi| {
|
||||||
if (asgn.op == .assign) {
|
if (asgn.op == .assign) {
|
||||||
const val_ty = self.builder.getRefType(val);
|
const val_ty = self.builder.getRefType(val);
|
||||||
const store_val = if (val_ty != gi.ty and val_ty != .void and gi.ty != .void)
|
const store_val = if (val_ty != gi.ty and val_ty != .void and gi.ty != .void)
|
||||||
@@ -890,7 +898,7 @@ pub fn lowerExprAsPtr(self: *Lowering, node: *const Node) Ref {
|
|||||||
}
|
}
|
||||||
return binding.ref;
|
return binding.ref;
|
||||||
}
|
}
|
||||||
} else if (self.program_index.global_names.get(id.name)) |gi| {
|
} else if (self.resolveGlobalRef(id.name, null)) |gi| {
|
||||||
// Module-global lvalue: address into the global's live storage
|
// Module-global lvalue: address into the global's live storage
|
||||||
// so a downstream GEP/store targets the global itself, not a
|
// so a downstream GEP/store targets the global itself, not a
|
||||||
// loaded copy. A pointer-typed global is loaded first to get
|
// loaded copy. A pointer-typed global is loaded first to get
|
||||||
|
|||||||
Reference in New Issue
Block a user