fix(stdlib/E5): source-aware same-name VALUE consts (own-wins / ambiguous / cross-module expr-chains)

Re-land the value-const analog of the E1-E4 type work, reconciled onto the
current source-keyed resolver and hardened. A same-name VALUE const declared in
multiple flat-imported modules is now resolved per declaring source, not the
global last-wins `module_const_map`.

- imports.zig: `isPerSourceDecl` retains every non-function `const_decl`
  per-source (value consts + type aliases), so each same-name author reaches
  registration as a distinct author of its own module. Functions and var_decls
  keep first-wins.
- lower.zig:
  * `selectModuleConst` over `module_consts_by_source` — own-wins; exactly one
    flat-visible resolves; >=2 flat-visible bare -> loud ambiguous (consistent
    with the 0755 type / 0724 fn / 0782 generic ambiguities). Rewires every
    consumer: `comptimeIntNamed`, the runtime-id read, the global-init read,
    and the float-name path (`lookupFloatName` / `nameIsFloatTyped`).
  * `SourceConstCtx` + `foldSourceConstInt`/`Float` + `sourceConstIsFloatTyped`
    fold a selected const's RHS with nested same-name leaves re-selected in
    their own author source, so VALUE and array-DIMENSION results are coherent.
  * `pinConstAuthorSource` pins each fold level to the SELECTED const's author
    (F1), including multi-level cross-module chains.
  * cycle guard keyed on (name, author-source), not name alone (F3), so
    same-name nested consts across modules do not trip a false cycle.
  * `emitModuleConst` takes the author source and pins while folding/lowering.
  Registration-time struct/inline-type field dimensions route through the now
  source-aware stateful reader; the type-alias dimension path resolves each
  alias against its own author's consts.
- program_index.zig: expose `isFloatConstType` / `isCountableConstType` for the
  source-aware folds.

examples: 0786 own-wins, 0787 ambiguous (exit 1), 0788 expr-chain value+dim
coherent, 0789 leaf-author-pin, 0790 cross-module cycle-guard (F3), 0791
multi-level cross-module chain, 0792 struct-field registration-time dim.
Single-author corpus byte-identical (524 prior markers green); 531 total.
This commit is contained in:
agra
2026-06-08 21:29:31 +03:00
parent 37c3b1e1f4
commit 5df4ac61a7
25 changed files with 450 additions and 86 deletions

View File

@@ -0,0 +1,14 @@
// issue 0105 / F2 — same-name VALUE const, own-wins. Two flat-imported modules
// each declare a top-level `K` with a different value and a function that reads
// `K` bare. Each function's OWN reference must bind ITS OWN module's `K`
// (own-wins), exactly as same-name structs (0754) and functions (0722) do —
// NOT the global last-wins author: `a_k` returns 1 and `b_k` returns 2,
// resolved by the source-aware const author selector (`selectModuleConst`).
#import "modules/std.sx";
#import "0786-modules-same-name-const-own/a.sx";
#import "0786-modules-same-name-const-own/b.sx";
main :: () -> s32 {
print("a={} b={}\n", a_k(), b_k());
0
}

View File

@@ -0,0 +1,3 @@
// Module A authors its OWN value const `K` (1) and reads it bare.
K :: 1;
a_k :: () -> s64 { return K; }

View File

@@ -0,0 +1,5 @@
// Module B authors a DIFFERENT same-name value const `K` (2) — a shadow of A's
// `K`. Each `K` is selected per declaring source, so B's `b_k` reads B's value
// while A's `a_k` reads A's, never the global last-wins const.
K :: 2;
b_k :: () -> s64 { return K; }

View File

@@ -0,0 +1,14 @@
// issue 0105 / F2 — same-name VALUE const, two-flat-visible → AMBIGUOUS. `main`
// flat-imports two modules that each author a same-name `K` and authors none
// itself. A bare `K` reference can't be disambiguated, so the compiler emits a
// LOUD diagnostic (consistent with the type ambiguity at 0755 and the function
// ambiguity at 0724) and poisons the result — never a silent first-/last-wins
// pick.
#import "modules/std.sx";
#import "0787-modules-same-name-const-ambiguous/a.sx";
#import "0787-modules-same-name-const-ambiguous/b.sx";
main :: () -> s32 {
print("K={}\n", K);
0
}

View File

@@ -0,0 +1,3 @@
// One of two flat authors of value const `K`. A consumer that flat-imports BOTH
// and reads `K` bare cannot pick between them.
K :: 1;

View File

@@ -0,0 +1,2 @@
// The second flat author of value const `K`.
K :: 2;

View File

@@ -0,0 +1,18 @@
// issue 0105 / F2 / R1 — 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`. Both observables agree per module:
// a_len=2 a_val=2, b_len=11 b_val=11.
#import "modules/std.sx";
#import "0788-modules-same-name-const-expr-chain-dim/a.sx";
#import "0788-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,10 @@
// Module B authors a DIFFERENT same-name chain: `M :: 10`, `K :: M + 1` (= 11).
// A shadow of A's `M`/`K`. Each `K`'s RHS leaf resolves to its own source's `M`,
// so the dimension fold gives B's length 11 — never A's via the global map.
M :: 10;
K :: M + 1;
b_val :: () -> s64 { return K; }
b_len :: () -> s64 {
arr : [K]u8 = ---;
return arr.len;
}

View File

@@ -0,0 +1,22 @@
// 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`) → val=2 len=2. Without the author pin
// the nested leaf would re-select `M` from `main`'s view (two `M`s → ambiguous /
// non-const dimension).
#import "modules/std.sx";
#import "0789-modules-same-name-const-leaf-author-pin/a.sx";
#import "0789-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
}

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
// issue 0105 / F3 — the const cycle guard must key on (name, AUTHOR-source), not
// name alone. `a.sx` declares `M :: 1` and `K :: M + 1` (= 2). `b.sx` flat-imports
// `a.sx` and declares a DIFFERENT same-name `M :: K + 1` (= 3) — so the SAME name
// `M` appears at two levels of one fold chain, in two different modules
// (b's `M` → a's `K` → a's `M`). A name-only cycle guard sees `M` twice and trips
// a FALSE cycle, folding b's `M` to null → the array dimension `[M]u8` becomes a
// non-const error. Keyed on (name, source) the two `M`s are distinct, so the
// chain folds: `m=3 len=3`, coherent for the value read and the dimension.
#import "modules/std.sx";
#import "0790-modules-same-name-const-cross-cycle-guard/b.sx";
main :: () -> s32 {
arr : [M]u8 = ---;
print("m={} len={}\n", M, arr.len);
0
}

View File

@@ -0,0 +1,4 @@
// Module A authors `M :: 1` and `K :: M + 1` (= 2). A's `K` folds its leaf `M`
// against A's own `M`.
M :: 1;
K :: M + 1;

View File

@@ -0,0 +1,6 @@
// Module B flat-imports A and authors a DIFFERENT same-name `M :: K + 1` (= 3),
// reading A's `K`. Folding B's `M` walks B's `M` → A's `K` → A's `M` — the name
// `M` recurs across two modules. The cycle guard must NOT confuse B's `M` with
// A's `M`: keyed on (name, source) the chain folds to 3.
#import "a.sx";
M :: K + 1;

View File

@@ -0,0 +1,19 @@
// issue 0105 / F1 / R1 — a MULTI-LEVEL cross-module const chain pins EACH fold
// level to its own author. `a.sx` declares `M :: 1`, `K :: M + 1` (= 2). `b.sx`
// declares a full same-name shadow `M :: 10`, `K :: M + 1` (= 11). `c.sx`
// flat-imports `a.sx` only and declares `BIG :: K + 100` — its `K` is A's, so
// `BIG` = 102. `main` flat-imports `b.sx` and `c.sx`.
//
// Reading `BIG` walks BIG (c) → K (a) → M (a): each level resolves in its OWN
// author's context, so `BIG` folds A's chain (= 102) even though `main` itself
// sees only B's `K` (= 11) bare. If any level leaked to the reader's view, `BIG`
// would fold B's `K` (→ 111). `#import` is non-transitive, so `K` bare in `main`
// is B's (A is reachable only through C) → bk=11. Output: big=102 bk=11.
#import "modules/std.sx";
#import "0791-modules-same-name-const-multi-level-cross-module/b.sx";
#import "0791-modules-same-name-const-multi-level-cross-module/c.sx";
main :: () -> s32 {
print("big={} bk={}\n", BIG, K);
0
}

View File

@@ -0,0 +1,4 @@
// Module A authors the base chain `M :: 1`, `K :: M + 1` (= 2). Reached only via
// C's flat import — never bare-visible in `main`.
M :: 1;
K :: M + 1;

View File

@@ -0,0 +1,4 @@
// Module B authors a full same-name shadow `M :: 10`, `K :: M + 1` (= 11). `main`
// flat-imports B, so bare `K` in `main` is B's (= 11).
M :: 10;
K :: M + 1;

View File

@@ -0,0 +1,5 @@
// Module C flat-imports A only and authors `BIG :: K + 100`. Its bare `K` is A's
// (= 2), so `BIG` folds to 102. The leaf must pin to A across the import into
// `main`, which itself sees a different same-name `K` (B's = 11).
#import "a.sx";
BIG :: K + 100;

View File

@@ -0,0 +1,15 @@
// issue 0105 / F2 (#6 registration-time reader) — a same-name VALUE const used as
// a STRUCT FIELD array dimension, baked into the layout at REGISTRATION time, is
// source-aware. Two flat-imported modules each declare a same-name `K` and a
// same-name struct `Box { arr: [K]u8 }`. `size_of(Box)` in each module must use
// ITS OWN `K` for the field dimension (own-wins), so the layouts differ:
// a_sz=2, b_sz=7. The registration-time field-type resolution routes through the
// stateful source-aware const reader, not the global last-wins map.
#import "modules/std.sx";
#import "0792-modules-same-name-const-struct-field-dim/a.sx";
#import "0792-modules-same-name-const-struct-field-dim/b.sx";
main :: () -> s32 {
print("a_sz={} b_sz={}\n", a_sz(), b_sz());
0
}

View File

@@ -0,0 +1,5 @@
// Module A authors `K :: 2` and a struct `Box` whose array field is dimensioned
// by A's `K`. `size_of(Box)` folds the field dimension against A's `K` (= 2).
K :: 2;
Box :: struct { arr: [K]u8; }
a_sz :: () -> s64 { return size_of(Box); }

View File

@@ -0,0 +1,6 @@
// Module B authors a DIFFERENT same-name `K :: 7` and same-name struct `Box`.
// `size_of(Box)` folds the field dimension against B's `K` (= 7), so the layout
// differs from A's — never collapsed to a global last-wins `K`.
K :: 7;
Box :: struct { arr: [K]u8; }
b_sz :: () -> s64 { return size_of(Box); }