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:
agra
2026-06-08 01:06:44 +03:00
parent 72f06a109b
commit 8518b66cec
8 changed files with 141 additions and 31 deletions

View 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
}

View File

@@ -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;
}

View 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;
}

View File

@@ -0,0 +1 @@
a_len=2 a_val=2 b_len=11 b_val=11