fix(lower): pin nested-leaf source to the SELECTED const's author — close F1 R1 [stdlib E2 attempt-5]
A same-name expression const read from another module folded its nested
leaves (`M` inside `K :: M + 1`) from the CALLER's source, not the source
that authored the selected const. A unique imported `K` became ambiguous
when the reading module also flat-imported a different same-name `M`.
`selectModuleConst` now returns the author SOURCE alongside the const info
(`SelectedConst`), and the fold/lower of a selected const's RHS pins
`current_source_file` to that author for the duration (`pinConstAuthorSource`)
— so `K :: M + 1` defined in `a.sx` always folds `M` against `a.sx`,
coherently whether `K` is read as a runtime value or used as an array
dimension. Each recursion level pins to its own selected author's source.
Single-author programs pin to the source they were already in → byte-
identical (499 prior examples unchanged). Genuine ambiguity at the read
site (0760) is still caught before any pin.
Regression: examples/0762-modules-same-name-const-leaf-author-pin
(`a.sx M::1; K::M+1`, `b.sx M::10`, main flat-imports both, reads K as
value AND `[K]u8` dimension → val=2 len=2). Fail-before on 8518b66
(`'M' is ambiguous` / "array dimension must be a compile-time integer
constant"), pass-after.
This commit is contained in:
@@ -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: `$<pack>[<lit>]` 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
|
||||
|
||||
Reference in New Issue
Block a user