diff --git a/examples/0762-modules-same-name-const-leaf-author-pin.sx b/examples/0762-modules-same-name-const-leaf-author-pin.sx new file mode 100644 index 0000000..65f8257 --- /dev/null +++ b/examples/0762-modules-same-name-const-leaf-author-pin.sx @@ -0,0 +1,25 @@ +// issue 0105 / F1 — a UNIQUE expression const's nested leaf folds against the +// const's AUTHOR source, not the reading module's. `a.sx` declares `M :: 1` and +// `K :: M + 1` (= 2); `b.sx` declares a DIFFERENT same-name `M :: 10` (no `K`). +// `main` flat-imports both, so the reader sees two `M`s — but it reads only `K`, +// which is unique to `a.sx`. Folding `K`'s RHS must pin `M` to A's source (→ 1), +// giving `K = 2`, coherently whether `K` is read as a runtime VALUE (`print K`) +// or used as an array DIMENSION (`[K]u8`). +// +// Pre-fix (8518b66) the nested leaf re-selected `M` from the CALLER's source: +// `main` flat-imports two `M`s → `'M' is ambiguous` (value read) / "array +// dimension must be a compile-time integer constant" (dimension). Now the fold +// pins each level to its selected author's source → val=2 len=2. +#import "modules/std.sx"; +#import "0762-modules-same-name-const-leaf-author-pin/a.sx"; +#import "0762-modules-same-name-const-leaf-author-pin/b.sx"; + +read_dim :: () -> s64 { + arr : [K]u8 = ---; + return arr.len; +} + +main :: () -> s32 { + print("val={} len={}\n", K, read_dim()); + 0 +} diff --git a/examples/0762-modules-same-name-const-leaf-author-pin/a.sx b/examples/0762-modules-same-name-const-leaf-author-pin/a.sx new file mode 100644 index 0000000..dd7c9dd --- /dev/null +++ b/examples/0762-modules-same-name-const-leaf-author-pin/a.sx @@ -0,0 +1,5 @@ +// Module A authors `M :: 1` and the EXPRESSION const `K :: M + 1` (= 2). `K` is +// unique across the program; only A defines it. Its RHS leaf `M` must always +// fold against A's `M` (= 1), no matter which module reads `K`. +M :: 1; +K :: M + 1; diff --git a/examples/0762-modules-same-name-const-leaf-author-pin/b.sx b/examples/0762-modules-same-name-const-leaf-author-pin/b.sx new file mode 100644 index 0000000..b2fe411 --- /dev/null +++ b/examples/0762-modules-same-name-const-leaf-author-pin/b.sx @@ -0,0 +1,5 @@ +// Module B authors only a DIFFERENT same-name `M :: 10` — a shadow of A's `M`, +// with NO `K`. When `main` flat-imports both A and B, the reading module sees +// two `M`s; folding A's `K :: M + 1` must NOT use this `M` (which would make `M` +// ambiguous from the reader's view) — it must pin to A's `M`. +M :: 10; diff --git a/examples/expected/0762-modules-same-name-const-leaf-author-pin.exit b/examples/expected/0762-modules-same-name-const-leaf-author-pin.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/examples/expected/0762-modules-same-name-const-leaf-author-pin.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/0762-modules-same-name-const-leaf-author-pin.stderr b/examples/expected/0762-modules-same-name-const-leaf-author-pin.stderr new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/0762-modules-same-name-const-leaf-author-pin.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/0762-modules-same-name-const-leaf-author-pin.stdout b/examples/expected/0762-modules-same-name-const-leaf-author-pin.stdout new file mode 100644 index 0000000..d5717e6 --- /dev/null +++ b/examples/expected/0762-modules-same-name-const-leaf-author-pin.stdout @@ -0,0 +1 @@ +val=2 len=2 diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 58ea432..b5d5e23 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -1427,16 +1427,18 @@ pub const Lowering = struct { // same pass-2 before this). E2/F2: copy the SOURCE-AWARE author's // value (own-wins), and reject a ≥2-flat ambiguity loudly. if (self.program_index.module_const_map.get(id.name)) |ci_global| { - const ci = switch (self.selectModuleConst(id.name)) { - .resolved => |c| c, - .none => ci_global, + const sel: SelectedConst = switch (self.selectModuleConst(id.name)) { + .resolved => |s| s, + .none => .{ .info = ci_global, .source = null }, .ambiguous => { 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}); break :blk null; }, }; - if (self.constExprValue(ci.value, var_ty)) |cv| break :blk cv; + const author_pin = self.pinConstAuthorSource(sel.source); + defer author_pin.unpin(); + if (self.constExprValue(sel.info.value, var_ty)) |cv| break :blk cv; } 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 }); @@ -3962,7 +3964,7 @@ pub const Lowering = struct { // silent pick. `.none` after a visible name is the registration- // only author (no per-source partition) — emit its global value. switch (self.selectModuleConst(id.name)) { - .resolved => |ci| break :blk self.emitModuleConst(ci), + .resolved => |sel| break :blk self.emitModuleConst(sel.info, sel.source), .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}); @@ -3971,7 +3973,7 @@ pub const Lowering = struct { // "unresolved" cascade from `emitError`. break :blk self.emitPlaceholder(id.name); }, - .none => break :blk self.emitModuleConst(ci_global), + .none => break :blk self.emitModuleConst(ci_global, null), } } // Check if it's a function name — produce function pointer reference @@ -13467,10 +13469,12 @@ pub const Lowering = struct { fn foldSourceConstFloat(self: *Lowering, name: []const u8, frame: ?*const ConstFoldFrame) ?f64 { if (constFoldFrameContains(frame, name)) return null; return switch (self.selectModuleConst(name)) { - .resolved => |ci| { - if (!program_index_mod.isCountableConstType(&self.module.types, ci.ty)) return null; + .resolved => |sel| { + if (!program_index_mod.isCountableConstType(&self.module.types, sel.info.ty)) return null; var f = ConstFoldFrame{ .name = name, .parent = frame }; - return program_index_mod.evalConstFloatExpr(ci.value, SourceConstCtx{ .lowering = self, .frame = &f }); + const restore = self.pinConstAuthorSource(sel.source); + defer restore.unpin(); + return program_index_mod.evalConstFloatExpr(sel.info.value, SourceConstCtx{ .lowering = self, .frame = &f }); }, .ambiguous, .none => null, }; @@ -13492,10 +13496,12 @@ pub const Lowering = struct { fn sourceConstIsFloatTyped(self: *Lowering, name: []const u8, frame: ?*const ConstFoldFrame) bool { if (constFoldFrameContains(frame, name)) return false; return switch (self.selectModuleConst(name)) { - .resolved => |ci| { - if (program_index_mod.isFloatConstType(ci.ty)) return true; + .resolved => |sel| { + if (program_index_mod.isFloatConstType(sel.info.ty)) return true; var f = ConstFoldFrame{ .name = name, .parent = frame }; - return program_index_mod.isFloatValuedExpr(ci.value, SourceConstCtx{ .lowering = self, .frame = &f }); + const restore = self.pinConstAuthorSource(sel.source); + defer restore.unpin(); + return program_index_mod.isFloatValuedExpr(sel.info.value, SourceConstCtx{ .lowering = self, .frame = &f }); }, .ambiguous, .none => false, }; @@ -13530,15 +13536,28 @@ pub const Lowering = struct { fn foldSourceConstInt(self: *Lowering, name: []const u8, frame: ?*const ConstFoldFrame) ?i64 { if (constFoldFrameContains(frame, name)) return null; return switch (self.selectModuleConst(name)) { - .resolved => |ci| { - if (!program_index_mod.isCountableConstType(&self.module.types, ci.ty)) return null; + .resolved => |sel| { + if (!program_index_mod.isCountableConstType(&self.module.types, sel.info.ty)) return null; var f = ConstFoldFrame{ .name = name, .parent = frame }; - return program_index_mod.evalConstIntExpr(ci.value, SourceConstCtx{ .lowering = self, .frame = &f }); + const restore = self.pinConstAuthorSource(sel.source); + defer restore.unpin(); + return program_index_mod.evalConstIntExpr(sel.info.value, SourceConstCtx{ .lowering = self, .frame = &f }); }, .ambiguous, .none => null, }; } + /// A selected module const plus the SOURCE that authored it. `source` pins the + /// context in which the const's RHS leaves must be folded (F1): a same-name + /// `K :: M + 1` selected from author `a.sx` folds its nested `M` against `a.sx`, + /// not against whichever module read `K`. `source` is null only on the + /// fully-unwired fallback (no source partition at all), where the RHS resolves + /// through the global registration context unchanged. + const SelectedConst = struct { + info: program_index_mod.ModuleConstInfo, + source: ?[]const u8, + }; + /// The source-aware module-const author of `name` from the querying module /// (E2/F2) — the value-const analogue of `selectNominalLeaf` (types) and /// `selectPlainCallableAuthor` (functions). Selects over the ONE graph-walk @@ -13556,29 +13575,29 @@ pub const Lowering = struct { /// at all) falls open to the global registration, byte-identical to the legacy /// reader for the registration / comptime-host path. const ConstAuthor = union(enum) { - resolved: program_index_mod.ModuleConstInfo, + resolved: SelectedConst, ambiguous, none, }; fn selectModuleConst(self: *Lowering, name: []const u8) ConstAuthor { const from = self.current_source_file orelse self.main_file orelse { - if (self.program_index.module_const_map.get(name)) |ci| return .{ .resolved = ci }; + if (self.program_index.module_const_map.get(name)) |ci| return .{ .resolved = .{ .info = ci, .source = null } }; return .none; }; 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 = ci }; - var the_one: ?program_index_mod.ModuleConstInfo = null; + if (set.own) |o| if (self.sourceModuleConst(o.source, name)) |ci| return .{ .resolved = .{ .info = ci, .source = o.source } }; + var the_one: ?SelectedConst = null; var count: usize = 0; for (set.flat) |fa| { const ci = self.sourceModuleConst(fa.source, name) orelse continue; count += 1; if (count >= 2) return .ambiguous; - the_one = ci; + the_one = .{ .info = ci, .source = fa.source }; } - if (the_one) |ci| return .{ .resolved = ci }; + if (the_one) |sc| return .{ .resolved = sc }; return .none; } @@ -13589,6 +13608,31 @@ pub const Lowering = struct { return inner.get(name); } + /// Saved `current_source_file` for a const-author pin; `unpin()` restores it. + const ConstSourcePin = struct { + lowering: *Lowering, + saved: ?[]const u8, + active: bool, + fn unpin(self: ConstSourcePin) void { + if (self.active) self.lowering.setCurrentSourceFile(self.saved); + } + }; + + /// Pin `current_source_file` to a SELECTED const's AUTHOR source while its RHS + /// is folded / lowered, so nested same-name leaves resolve in the author's + /// visibility context (F1): `K :: M + 1` selected from `a.sx` always folds `M` + /// against `a.sx`, regardless of which module read `K`. A null author (the + /// fully-unwired fallback) leaves the context untouched. Single-author programs + /// pin to the source they were already in → byte-identical. + fn pinConstAuthorSource(self: *Lowering, source: ?[]const u8) ConstSourcePin { + if (source) |s| { + const saved = self.current_source_file; + self.setCurrentSourceFile(s); + return .{ .lowering = self, .saved = saved, .active = true }; + } + return .{ .lowering = self, .saved = self.current_source_file, .active = false }; + } + /// Resolve a type node, checking type_bindings first for generic type params. pub fn resolveTypeWithBindings(self: *Lowering, node: *const Node) TypeId { // Pack-index in a type position: `$[]` resolves to the @@ -16123,7 +16167,14 @@ pub const Lowering = struct { }; } - fn emitModuleConst(self: *Lowering, ci: ModuleConstInfo) Ref { + fn emitModuleConst(self: *Lowering, ci: ModuleConstInfo, author_source: ?[]const u8) Ref { + // F1: a const read from another module folds/lowers its RHS in the + // AUTHOR's visibility context, so a same-name leaf (`K :: M + 1` selected + // from `a.sx`) resolves `M` against `a.sx` — not against the reading + // module, which may flat-import a different same-name `M`. Single-author / + // own-read consts pin to the source they were already in → byte-identical. + const author_pin = self.pinConstAuthorSource(author_source); + defer author_pin.unpin(); // An integer-typed const whose initializer is a compile-time integer — // an int literal/expression, OR an INTEGRAL float that `typedConstInitFits` // accepted under the unified narrowing rule — materializes as its folded