fix(lower): propagate source-aware const selection into expression-chain folds — close F2 R1 [stdlib E2 attempt-4]
attempt-3 made the value-const READ source-aware (own-wins / ambiguous) but
the dimension/count fold of a SELECTED const's RHS still recursed through the
global last-wins `module_const_map`, so a nested same-name leaf came from the
wrong module. Reviewer R1: a.sx `M::1; K::M+1`, b.sx `M::10; K::M+1`, with both
`[K]u8` (a_len) and `return K` (a_val) — pre-fix `a_len=11 a_val=2`, an
INCOHERENCE for the same const `K` (a_val read A's chain; a_len read B's `M`).
`comptimeIntNamed` delegated to `moduleConstIntWith(global_map, ...)`, whose
leaf ctx (`ModuleConstCtx`) resolved nested names through the global map. The
value path (`emitModuleConst` -> `foldCountI64(ci.value, self)`) folds through
`self`, so its leaves bounce back to the source-aware `comptimeIntNamed` — which
is why a_val was already correct.
- New `SourceConstCtx` (lower.zig): the leaf-resolution twin of `ModuleConstCtx`,
but every nested const leaf re-selects its OWN source author via
`selectModuleConst` (own-wins / ambiguous), never the global last-wins map.
`ConstFoldFrame` cycle-guards a const whose RHS references another const.
- `comptimeIntNamed` / `lookupFloatName` / `nameIsFloatTyped` now fold the
selected `ci`'s RHS through `SourceConstCtx` (via `foldSourceConstInt` /
`foldSourceConstFloat` / `sourceConstIsFloatTyped`). This makes the dimension
and value reads of a shadowed expression-chain const coherent.
- Drop the now-unused `moduleConst{Int,Float,IsFloatTyped}With` wrappers from
program_index.zig; expose `isCountableConstType` / `isFloatConstType`.
Single-author -> byte-identical (the selected `ci` IS the global one and every
nested leaf has one author). The stateless `type_bridge` registration-time const
reader still folds leaves through the global map, but realistic dim sites (struct
fields, array aliases — probed) resolve via the stateful path and stay coherent
under import-order swaps; no reachable wrong-dimension found (tracked follow-up,
byte-identical single-author).
Regression: examples/0761-modules-same-name-const-expr-chain-dim — a_len=2
a_val=2, b_len=11 b_val=11. Fail-before on 72f06a1 (`a_len=11`), pass-after.
Gate: zig build + zig build test (423/423, LSP sweep 515 clean) + run_examples
(499/0, 498 prior byte-identical + 0761) + m3te ios-sim build exit 0.
This commit is contained in:
19
examples/0761-modules-same-name-const-expr-chain-dim.sx
Normal file
19
examples/0761-modules-same-name-const-expr-chain-dim.sx
Normal file
@@ -0,0 +1,19 @@
|
||||
// issue 0105 / F2 — same-name const EXPRESSION CHAIN, coherent across a value
|
||||
// read AND an array dimension. Two flat-imported modules each declare a same-name
|
||||
// `M` and a same-name `K :: M + 1` that reads `M`. Each module uses ITS OWN `K`
|
||||
// both as a runtime value (`return K`) and as an array dimension (`[K]u8`).
|
||||
//
|
||||
// The fold of a SELECTED const's RHS must resolve nested same-name leaves (the
|
||||
// `M` inside `K :: M + 1`) in the SELECTED author's source context, not through
|
||||
// the global last-wins `module_const_map`. Pre-fix (72f06a1) the dimension fold
|
||||
// recursed through the global map, so `a_len` read B's `M` (= 11) while `a_val`
|
||||
// correctly read A's chain (= 2) — an INCOHERENCE for the same const `K`. Now
|
||||
// both observables agree per module: a_len=2 a_val=2, b_len=11 b_val=11.
|
||||
#import "modules/std.sx";
|
||||
#import "0761-modules-same-name-const-expr-chain-dim/a.sx";
|
||||
#import "0761-modules-same-name-const-expr-chain-dim/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("a_len={} a_val={} b_len={} b_val={}\n", a_len(), a_val(), b_len(), b_val());
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Module A authors its OWN chain: `M :: 1`, `K :: M + 1` (= 2). Both the value
|
||||
// read and the array dimension must resolve `K` through A's `M`.
|
||||
M :: 1;
|
||||
K :: M + 1;
|
||||
a_val :: () -> s64 { return K; }
|
||||
a_len :: () -> s64 {
|
||||
arr : [K]u8 = ---;
|
||||
return arr.len;
|
||||
}
|
||||
11
examples/0761-modules-same-name-const-expr-chain-dim/b.sx
Normal file
11
examples/0761-modules-same-name-const-expr-chain-dim/b.sx
Normal file
@@ -0,0 +1,11 @@
|
||||
// Module B authors a DIFFERENT same-name chain: `M :: 10`, `K :: M + 1` (= 11).
|
||||
// A shadow of A's `M`/`K`. Pre-fix the dimension fold collapsed to B's `M` for
|
||||
// BOTH modules via the global last-wins map; now each `K`'s RHS leaf resolves to
|
||||
// its own source's `M`.
|
||||
M :: 10;
|
||||
K :: M + 1;
|
||||
b_val :: () -> s64 { return K; }
|
||||
b_len :: () -> s64 {
|
||||
arr : [K]u8 = ---;
|
||||
return arr.len;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
a_len=2 a_val=2 b_len=11 b_val=11
|
||||
106
src/ir/lower.zig
106
src/ir/lower.zig
@@ -54,6 +54,51 @@ fn isExportedEntryName(name: []const u8) bool {
|
||||
std.mem.startsWith(u8, name, "Java_");
|
||||
}
|
||||
|
||||
/// One frame in the chain of module-const names currently being folded by the
|
||||
/// SOURCE-AWARE const evaluator (`Lowering.foldSourceConstInt` and its float
|
||||
/// twins). Stack-allocated per recursive frame, so cycle detection needs no
|
||||
/// allocation — the source-aware analogue of `program_index.ModuleConstFrame`,
|
||||
/// which guards the GLOBAL-map fold (`moduleConstInt`). A name already on the
|
||||
/// chain is a cyclic definition (`N :: N`; `N :: M + 1; M :: N`) with no
|
||||
/// compile-time value → folds to null.
|
||||
const ConstFoldFrame = struct {
|
||||
name: []const u8,
|
||||
parent: ?*const ConstFoldFrame,
|
||||
};
|
||||
|
||||
fn constFoldFrameContains(frame: ?*const ConstFoldFrame, name: []const u8) bool {
|
||||
var cur = frame;
|
||||
while (cur) |c| : (cur = c.parent) {
|
||||
if (std.mem.eql(u8, c.name, name)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Folding context for a SOURCE-AWARE module-const EXPRESSION RHS (E2/F2).
|
||||
/// The leaf-resolution twin of `program_index.ModuleConstCtx`, but every leaf
|
||||
/// name resolves through the querying source's OWN const author
|
||||
/// (`selectModuleConst`, own-wins / ambiguous) instead of the GLOBAL last-wins
|
||||
/// `module_const_map`. This is what makes a same-name shadow's RHS chain
|
||||
/// (`K :: M + 1`, with `M` a same-name shadow too) fold `M` to the SELECTED
|
||||
/// author's `M` — coherently for a const used as a value AND as an
|
||||
/// array dimension / count. `frame` is the cyclic-definition guard.
|
||||
const SourceConstCtx = struct {
|
||||
lowering: *Lowering,
|
||||
frame: ?*const ConstFoldFrame,
|
||||
pub fn lookupDimName(self: SourceConstCtx, name: []const u8) ?i64 {
|
||||
return self.lowering.foldSourceConstInt(name, self.frame);
|
||||
}
|
||||
pub fn lookupPackLen(self: SourceConstCtx, name: []const u8) ?i64 {
|
||||
return self.lowering.lookupPackLen(name);
|
||||
}
|
||||
pub fn lookupFloatName(self: SourceConstCtx, name: []const u8) ?f64 {
|
||||
return self.lowering.foldSourceConstFloat(name, self.frame);
|
||||
}
|
||||
pub fn nameIsFloatTyped(self: SourceConstCtx, name: []const u8) bool {
|
||||
return self.lowering.sourceConstIsFloatTyped(name, self.frame);
|
||||
}
|
||||
};
|
||||
|
||||
// ── Scope ───────────────────────────────────────────────────────────────
|
||||
|
||||
pub const Binding = struct {
|
||||
@@ -13411,8 +13456,22 @@ pub const Lowering = struct {
|
||||
/// `evalConstIntExpr` delegation inside `evalConstFloatExpr`; this surfaces the
|
||||
/// non-integral float const so the rule can reject it.
|
||||
pub fn lookupFloatName(self: *Lowering, name: []const u8) ?f64 {
|
||||
return self.foldSourceConstFloat(name, null);
|
||||
}
|
||||
|
||||
/// Source-aware FLOAT fold of a module const `name` (E2/F2): the SELECTED
|
||||
/// author's RHS is folded with nested leaves resolved SOURCE-AWARE (own-wins)
|
||||
/// rather than through the global last-wins map. `frame` cycle-guards a const
|
||||
/// whose value references another const. Single-author → byte-identical to
|
||||
/// the legacy global-map fold (the selected `ci` IS the global one).
|
||||
fn foldSourceConstFloat(self: *Lowering, name: []const u8, frame: ?*const ConstFoldFrame) ?f64 {
|
||||
if (constFoldFrameContains(frame, name)) return null;
|
||||
return switch (self.selectModuleConst(name)) {
|
||||
.resolved => |ci| program_index_mod.moduleConstFloatWith(&self.program_index.module_const_map, &self.module.types, name, ci),
|
||||
.resolved => |ci| {
|
||||
if (!program_index_mod.isCountableConstType(&self.module.types, ci.ty)) return null;
|
||||
var f = ConstFoldFrame{ .name = name, .parent = frame };
|
||||
return program_index_mod.evalConstFloatExpr(ci.value, SourceConstCtx{ .lowering = self, .frame = &f });
|
||||
},
|
||||
.ambiguous, .none => null,
|
||||
};
|
||||
}
|
||||
@@ -13424,13 +13483,28 @@ pub const Lowering = struct {
|
||||
/// value bindings are always integer-valued, so only the module-const table
|
||||
/// can name a float.
|
||||
pub fn nameIsFloatTyped(self: *Lowering, name: []const u8) bool {
|
||||
return self.sourceConstIsFloatTyped(name, null);
|
||||
}
|
||||
|
||||
/// Source-aware "is `name` a FLOAT-valued module const" (E2/F2): judge the
|
||||
/// SELECTED author's value, with nested const leaves resolved source-aware.
|
||||
/// `frame` cycle-guards a const whose value references another const.
|
||||
fn sourceConstIsFloatTyped(self: *Lowering, name: []const u8, frame: ?*const ConstFoldFrame) bool {
|
||||
if (constFoldFrameContains(frame, name)) return false;
|
||||
return switch (self.selectModuleConst(name)) {
|
||||
.resolved => |ci| program_index_mod.moduleConstIsFloatTypedWith(&self.program_index.module_const_map, &self.module.types, name, ci),
|
||||
.resolved => |ci| {
|
||||
if (program_index_mod.isFloatConstType(ci.ty)) return true;
|
||||
var f = ConstFoldFrame{ .name = name, .parent = frame };
|
||||
return program_index_mod.isFloatValuedExpr(ci.value, SourceConstCtx{ .lowering = self, .frame = &f });
|
||||
},
|
||||
.ambiguous, .none => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// Resolve a name to a compile-time integer across the three const tables.
|
||||
/// A comptime binding (generic value param / inline-for cursor) or a
|
||||
/// `#run`/`OS`/`ARCH` comptime constant wins first; otherwise the name is a
|
||||
/// SOURCE-AWARE module const, folded with nested leaves resolved own-wins.
|
||||
fn comptimeIntNamed(self: *Lowering, name: []const u8) ?i64 {
|
||||
if (self.comptime_constants.get(name)) |cv| switch (cv) {
|
||||
.int_val => |iv| return iv,
|
||||
@@ -13439,14 +13513,28 @@ pub const Lowering = struct {
|
||||
if (self.comptime_value_bindings) |cvb| {
|
||||
if (cvb.get(name)) |v| return v;
|
||||
}
|
||||
// E2/F2: select the SOURCE-AWARE module-const author (own-wins; ≥2
|
||||
// flat-visible → ambiguous → no value here, the loud diagnostic is the
|
||||
// reference site's job). The selected `ci`'s RHS is folded over the global
|
||||
// leaf map, so a const-EXPRESSION chain (`N :: M + 1`, M flat-imported)
|
||||
// still resolves `M` exactly as before. Single-author selects the same
|
||||
// `ci` the global map holds, so this is byte-identical to the legacy read.
|
||||
return self.foldSourceConstInt(name, null);
|
||||
}
|
||||
|
||||
/// Source-aware INTEGER fold of a module const `name` (E2/F2). The crux of
|
||||
/// F2's expression-chain fix: select the SOURCE-AWARE author (own-wins; ≥2
|
||||
/// flat-visible → ambiguous → null, the loud diagnostic is the reference
|
||||
/// site's job), then fold ITS RHS with nested const leaves resolved through
|
||||
/// `SourceConstCtx` — i.e. each leaf re-selects its OWN source author, NOT the
|
||||
/// global last-wins `module_const_map`. So a shadowed `K :: M + 1` folds `M`
|
||||
/// to the SELECTED author's `M`, coherently whether `K` is read as a value
|
||||
/// (`return K`) or used as an array dimension / count (`[K]u8`). `frame`
|
||||
/// cycle-guards a const whose value references another const. Single-author →
|
||||
/// byte-identical to the legacy fold (the selected `ci` IS the global one and
|
||||
/// every nested leaf has exactly one author).
|
||||
fn foldSourceConstInt(self: *Lowering, name: []const u8, frame: ?*const ConstFoldFrame) ?i64 {
|
||||
if (constFoldFrameContains(frame, name)) return null;
|
||||
return switch (self.selectModuleConst(name)) {
|
||||
.resolved => |ci| program_index_mod.moduleConstIntWith(&self.program_index.module_const_map, &self.module.types, name, ci),
|
||||
.resolved => |ci| {
|
||||
if (!program_index_mod.isCountableConstType(&self.module.types, ci.ty)) return null;
|
||||
var f = ConstFoldFrame{ .name = name, .parent = frame };
|
||||
return program_index_mod.evalConstIntExpr(ci.value, SourceConstCtx{ .lowering = self, .frame = &f });
|
||||
},
|
||||
.ambiguous, .none => null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ const ModuleConstCtx = struct {
|
||||
/// True iff `ty` is a float type — one half of the float-valued-const test the
|
||||
/// int folder's division arm relies on. Module consts only ever carry the builtin
|
||||
/// `f32` / `f64`.
|
||||
fn isFloatConstType(ty: TypeId) bool {
|
||||
pub fn isFloatConstType(ty: TypeId) bool {
|
||||
return ty == .f32 or ty == .f64;
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ fn foldConstFloatValued(consts: *const std.StringHashMap(ModuleConstInfo), table
|
||||
/// off an integer-looking initializer (issue 0088 — the second symptom, where
|
||||
/// `N : string : 4` folded `[N]s64` to 4 by reading the `int_literal` node and
|
||||
/// ignoring the `string` annotation).
|
||||
fn isCountableConstType(table: *const types.TypeTable, ty: TypeId) bool {
|
||||
pub fn isCountableConstType(table: *const types.TypeTable, ty: TypeId) bool {
|
||||
return switch (ty) {
|
||||
.s8, .s16, .s32, .s64, .u8, .u16, .u32, .u64, .usize, .isize, .f32, .f64 => true,
|
||||
else => if (ty.isBuiltin()) false else switch (table.get(ty)) {
|
||||
@@ -177,15 +177,6 @@ fn foldConstInt(consts: *const std.StringHashMap(ModuleConstInfo), table: *const
|
||||
return evalConstIntExpr(ci.value, ModuleConstCtx{ .consts = consts, .table = table, .frame = &frame });
|
||||
}
|
||||
|
||||
/// `moduleConstInt` for a const whose AUTHOR was already selected source-aware
|
||||
/// (E2): fold the provided `ci`'s RHS instead of `consts.get(name)`, so a
|
||||
/// same-name shadow resolves to ITS OWN value while a const-EXPRESSION leaf still
|
||||
/// resolves through the global `consts` map (the chain reader). Single-author →
|
||||
/// byte-identical to `moduleConstInt` (the selected `ci` IS the global one).
|
||||
pub fn moduleConstIntWith(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8, ci: ModuleConstInfo) ?i64 {
|
||||
return foldConstInt(consts, table, name, ci, null);
|
||||
}
|
||||
|
||||
/// A name bound to a module-global integer constant → its value, else null.
|
||||
/// SINGLE source for both array-dimension resolvers — the stateful
|
||||
/// body-lowering path (`Lowering.comptimeIntNamed`) and the stateless
|
||||
@@ -230,12 +221,6 @@ pub fn moduleConstFloat(consts: *const std.StringHashMap(ModuleConstInfo), table
|
||||
return moduleConstFloatFramed(consts, table, name, null);
|
||||
}
|
||||
|
||||
/// `moduleConstFloat` for a source-aware-selected `ci` (E2) — the float analog
|
||||
/// of `moduleConstIntWith`.
|
||||
pub fn moduleConstFloatWith(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8, ci: ModuleConstInfo) ?f64 {
|
||||
return foldConstFloat(consts, table, name, ci, null);
|
||||
}
|
||||
|
||||
/// True iff `name` is a FLOAT-valued module const — judged by VALUE, so it covers
|
||||
/// a typed float const (`K : f64 : 4.0`), an untyped float-EXPRESSION const
|
||||
/// (`ME :: 4.0 + 1.0`, whose placeholder type is `s64`), and a non-integral float
|
||||
@@ -246,11 +231,6 @@ pub fn moduleConstIsFloatTyped(consts: *const std.StringHashMap(ModuleConstInfo)
|
||||
return moduleConstFloatValuedFramed(consts, table, name, null);
|
||||
}
|
||||
|
||||
/// `moduleConstIsFloatTyped` for a source-aware-selected `ci` (E2).
|
||||
pub fn moduleConstIsFloatTypedWith(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8, ci: ModuleConstInfo) bool {
|
||||
return foldConstFloatValued(consts, table, name, ci, null);
|
||||
}
|
||||
|
||||
/// True iff `node` is a FLOAT-valued compile-time expression — a float literal,
|
||||
/// a float-typed const leaf (`F : f64 : 2.5`, `K : f64 : 4.0`), a builtin float
|
||||
/// numeric-limit (`f64.max`), or arithmetic over any of those. THE predicate the
|
||||
|
||||
Reference in New Issue
Block a user