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:
14
examples/0786-modules-same-name-const-own.sx
Normal file
14
examples/0786-modules-same-name-const-own.sx
Normal 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
|
||||
}
|
||||
3
examples/0786-modules-same-name-const-own/a.sx
Normal file
3
examples/0786-modules-same-name-const-own/a.sx
Normal 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; }
|
||||
5
examples/0786-modules-same-name-const-own/b.sx
Normal file
5
examples/0786-modules-same-name-const-own/b.sx
Normal 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; }
|
||||
14
examples/0787-modules-same-name-const-ambiguous.sx
Normal file
14
examples/0787-modules-same-name-const-ambiguous.sx
Normal 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
|
||||
}
|
||||
3
examples/0787-modules-same-name-const-ambiguous/a.sx
Normal file
3
examples/0787-modules-same-name-const-ambiguous/a.sx
Normal 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;
|
||||
2
examples/0787-modules-same-name-const-ambiguous/b.sx
Normal file
2
examples/0787-modules-same-name-const-ambiguous/b.sx
Normal file
@@ -0,0 +1,2 @@
|
||||
// The second flat author of value const `K`.
|
||||
K :: 2;
|
||||
18
examples/0788-modules-same-name-const-expr-chain-dim.sx
Normal file
18
examples/0788-modules-same-name-const-expr-chain-dim.sx
Normal 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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
10
examples/0788-modules-same-name-const-expr-chain-dim/b.sx
Normal file
10
examples/0788-modules-same-name-const-expr-chain-dim/b.sx
Normal 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;
|
||||
}
|
||||
22
examples/0789-modules-same-name-const-leaf-author-pin.sx
Normal file
22
examples/0789-modules-same-name-const-leaf-author-pin.sx
Normal 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
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
16
examples/0790-modules-same-name-const-cross-cycle-guard.sx
Normal file
16
examples/0790-modules-same-name-const-cross-cycle-guard.sx
Normal 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
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
15
examples/0792-modules-same-name-const-struct-field-dim.sx
Normal file
15
examples/0792-modules-same-name-const-struct-field-dim.sx
Normal 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
|
||||
}
|
||||
@@ -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); }
|
||||
@@ -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); }
|
||||
@@ -314,8 +314,8 @@ pub const ResolvedModule = struct {
|
||||
try self.scope.put(name, {});
|
||||
if (seen_list.contains(name)) {
|
||||
// A cross-module name collision: drop from the global list
|
||||
// (first-wins) UNLESS this is a per-source decl (a named type or
|
||||
// a type-alias const), which must reach registration as a
|
||||
// (first-wins) UNLESS this is a per-source decl (a type, alias,
|
||||
// or non-function const), which must reach registration as a
|
||||
// distinct author of its own module (issues 0104/0105).
|
||||
append_to_global = isPerSourceDecl(decl);
|
||||
} else {
|
||||
@@ -352,14 +352,14 @@ pub const ResolvedModule = struct {
|
||||
if (decl.data.declName()) |name| {
|
||||
if (seen_list.contains(name)) {
|
||||
// First-wins on a cross-module name collision — EXCEPT a
|
||||
// per-source decl (a named type or a type-alias const), each
|
||||
// of which must reach registration as a distinct same-name
|
||||
// author of its own module (issues 0104/0105). FUNCTIONS and
|
||||
// VALUE consts keep first-wins (issue 0102 — the shadowed
|
||||
// function stays reachable via its qualified name /
|
||||
// SelectedFunc; same-name value consts are deferred to step
|
||||
// E5). Node identity (above) still de-dups a diamond import of
|
||||
// the SAME decl.
|
||||
// per-source decl (a named type, or any non-function const:
|
||||
// type alias + value const), each of which must reach
|
||||
// registration as a distinct same-name author of its own
|
||||
// module (issues 0104/0105 types, step E5 value consts). Only
|
||||
// FUNCTIONS keep first-wins (issue 0102 — the shadowed author
|
||||
// stays reachable via its qualified name / SelectedFunc).
|
||||
// Node identity (above) still de-dups a diamond import of the
|
||||
// SAME decl.
|
||||
if (!isPerSourceDecl(decl)) continue;
|
||||
} else {
|
||||
try seen_list.put(name, {});
|
||||
@@ -372,43 +372,19 @@ pub const ResolvedModule = struct {
|
||||
|
||||
/// A decl that must register PER-SOURCE: each same-name author across modules
|
||||
/// registers against its OWN module rather than collapsing to a single
|
||||
/// first-wins winner. NAMED types and TYPE-introducing `const_decl`s (type
|
||||
/// aliases + inline type decls, source-keyed via the alias cache) are
|
||||
/// per-source — that is what closes issues 0104/0105 for types and aliases.
|
||||
/// Everything else keeps the first-wins name-merge:
|
||||
/// - FUNCTIONS (issue 0102 — the shadowed author stays reachable via its
|
||||
/// qualified name / SelectedFunc),
|
||||
/// - VALUE `const_decl`s (literal / value-expression RHS): a same-name
|
||||
/// value const keeps the pre-E2 first-wins read; cross-module same-name
|
||||
/// value-const support is a separate concern (step E5), NOT part of the
|
||||
/// 0105 type close,
|
||||
/// - and `var_decl`s, including a `#foreign` extern global declared in two
|
||||
/// files (e.g. `__stdinp : *void #foreign;`) that MUST resolve to the ONE
|
||||
/// libSystem symbol, not split into a duplicate `__stdinp.1`.
|
||||
/// first-wins winner. NAMED types and every non-function `const_decl` (type
|
||||
/// aliases + inline type decls + VALUE consts, source-keyed via the alias /
|
||||
/// const caches) are per-source — that is what closes issues 0104/0105 for
|
||||
/// types/aliases and supports same-name value consts (step E5). Everything
|
||||
/// else keeps the first-wins name-merge: FUNCTIONS (issue 0102 — the shadowed
|
||||
/// author stays reachable via its qualified name / SelectedFunc), and crucially
|
||||
/// `var_decl`s, including a `#foreign` extern global declared in two files
|
||||
/// (e.g. `__stdinp : *void #foreign;`) that MUST resolve to the ONE libSystem
|
||||
/// symbol, not split into a duplicate `__stdinp.1`.
|
||||
fn isPerSourceDecl(decl: *const Node) bool {
|
||||
return switch (decl.data) {
|
||||
.struct_decl, .enum_decl, .union_decl, .error_set_decl, .protocol_decl, .foreign_class_decl => true,
|
||||
// A `const_decl` is per-source ONLY when its RHS introduces a TYPE
|
||||
// (alias / inline type decl). A VALUE const — literal or value
|
||||
// expression — and a function const keep the first-wins merge.
|
||||
.const_decl => |cd| switch (cd.value.data) {
|
||||
.fn_decl,
|
||||
.int_literal,
|
||||
.float_literal,
|
||||
.bool_literal,
|
||||
.string_literal,
|
||||
.null_literal,
|
||||
.undef_literal,
|
||||
.enum_literal,
|
||||
.struct_literal,
|
||||
.array_literal,
|
||||
.tuple_literal,
|
||||
.binary_op,
|
||||
.unary_op,
|
||||
.chained_comparison,
|
||||
=> false,
|
||||
else => true,
|
||||
},
|
||||
.const_decl => |cd| cd.value.data != .fn_decl,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
274
src/ir/lower.zig
274
src/ir/lower.zig
@@ -54,6 +54,60 @@ 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`). The frame keys on the
|
||||
/// const's (name, author-source) pair, NOT name alone: same-name nested consts
|
||||
/// across modules (`a.M` ≠ `b.M`) must NOT trip a false cycle (F3). A pair
|
||||
/// 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,
|
||||
source: ?[]const u8,
|
||||
parent: ?*const ConstFoldFrame,
|
||||
};
|
||||
|
||||
fn constFoldFrameContains(frame: ?*const ConstFoldFrame, name: []const u8, source: ?[]const u8) bool {
|
||||
var cur = frame;
|
||||
while (cur) |c| : (cur = c.parent) {
|
||||
if (std.mem.eql(u8, c.name, name) and sourcesEql(c.source, source)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn sourcesEql(a: ?[]const u8, b: ?[]const u8) bool {
|
||||
if (a == null and b == null) return true;
|
||||
if (a == null or b == null) return false;
|
||||
return std.mem.eql(u8, a.?, b.?);
|
||||
}
|
||||
|
||||
/// Folding context for a SOURCE-AWARE module-const EXPRESSION RHS (E2/F2/R1).
|
||||
/// 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 {
|
||||
@@ -1387,9 +1441,22 @@ pub const Lowering = struct {
|
||||
// A global initialized from a module constant copies the
|
||||
// constant's recorded value (typed module consts land in
|
||||
// `module_const_map` via `registerTypedModuleConst`, run in the
|
||||
// same pass-2 before this).
|
||||
if (self.program_index.module_const_map.get(id.name)) |ci| {
|
||||
if (self.constExprValue(ci.value, var_ty)) |cv| break :blk cv;
|
||||
// same pass-2 before this). F1/F2: copy the SOURCE-AWARE author's
|
||||
// value (own-wins), folding its RHS in the author's context, and
|
||||
// reject a ≥2-flat ambiguity loudly.
|
||||
if (self.program_index.module_const_map.get(id.name)) |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;
|
||||
},
|
||||
};
|
||||
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 });
|
||||
@@ -4032,13 +4099,26 @@ pub const Lowering = struct {
|
||||
break :blk self.builder.emit(.{ .global_get = gi.id }, gi.ty);
|
||||
}
|
||||
// Check module-level value constants (e.g. AF_INET :s32: 2)
|
||||
if (self.program_index.module_const_map.get(id.name)) |ci| {
|
||||
if (self.program_index.module_const_map.get(id.name)) |ci_global| {
|
||||
if (!self.isNameVisible(id.name)) {
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, node.span, "'{s}' is not visible; #import the module that declares it", .{id.name});
|
||||
break :blk self.emitError(id.name, node.span);
|
||||
}
|
||||
break :blk self.emitModuleConst(ci);
|
||||
// F2: emit the SOURCE-AWARE author's value (own-wins), not the
|
||||
// global last-wins `ci_global`. ≥2 flat-visible same-name const
|
||||
// authors → a loud ambiguity (issue 0105 / 0760), never a 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 => |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});
|
||||
break :blk self.emitPlaceholder(id.name);
|
||||
},
|
||||
.none => break :blk self.emitModuleConst(ci_global, null),
|
||||
}
|
||||
}
|
||||
// Check if it's a function name — produce function pointer reference
|
||||
// Resolve mangled name for block-local functions
|
||||
@@ -13640,8 +13720,7 @@ 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 {
|
||||
if (self.moduleConstBareInvisible(name)) return null;
|
||||
return program_index_mod.moduleConstFloat(&self.program_index.module_const_map, &self.module.types, name);
|
||||
return self.foldSourceConstFloat(name, null);
|
||||
}
|
||||
|
||||
/// True iff `name` is a FLOAT-valued module const (`F : f64 : 2.5`,
|
||||
@@ -13651,11 +13730,13 @@ 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 {
|
||||
if (self.moduleConstBareInvisible(name)) return false;
|
||||
return program_index_mod.moduleConstIsFloatTyped(&self.program_index.module_const_map, &self.module.types, name);
|
||||
return self.sourceConstIsFloatTyped(name, null);
|
||||
}
|
||||
|
||||
/// 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,
|
||||
@@ -13664,43 +13745,149 @@ pub const Lowering = struct {
|
||||
if (self.comptime_value_bindings) |cvb| {
|
||||
if (cvb.get(name)) |v| return v;
|
||||
}
|
||||
// Folded req #1: gate the bare module const on source-aware visibility
|
||||
// before reading the global map (see `moduleConstBareInvisible`).
|
||||
if (self.moduleConstBareInvisible(name)) return null;
|
||||
// The module-const branch is shared verbatim with the stateless
|
||||
// registration-time resolver (`type_bridge`) so a `[N]T` dimension
|
||||
// resolves to the same length on both paths (issue 0083).
|
||||
return program_index_mod.moduleConstInt(&self.program_index.module_const_map, &self.module.types, name);
|
||||
return self.foldSourceConstInt(name, null);
|
||||
}
|
||||
|
||||
/// Folded req #1: TRUE iff `name` is a module const that is NOT reachable
|
||||
/// bare from the querying module — the source-aware gate every Lowering-side
|
||||
/// comptime `module_const_map` reader (`comptimeIntNamed` / `lookupFloatName`
|
||||
/// / `nameIsFloatTyped`) consults before the global first-match. A
|
||||
/// namespaced-only import's const must be qualified (`ns.X`); without this
|
||||
/// gate a bare reference leaks into a comptime-scalar / array-dim position
|
||||
/// through the global table (the int folder even falls back to the float
|
||||
/// reader, so all three must gate). The value itself is still folded over the
|
||||
/// global map, so a cross-module const CHAIN (`N :: M + 1`, M flat-imported)
|
||||
/// resolves exactly as before; the stateless `type_bridge` registration path
|
||||
/// keeps the global reader this step. A main-file body carries a null
|
||||
/// `current_source_file` (it IS the root), so the querying module is
|
||||
/// `main_file` there; a fully unwired index (no source at all) falls open.
|
||||
fn moduleConstBareInvisible(self: *Lowering, name: []const u8) bool {
|
||||
const from = self.current_source_file orelse self.main_file orelse return false;
|
||||
/// Source-aware INTEGER fold of a module const `name` (E2/F2/R1). 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` — 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` (keyed by name + author-source, F3) 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 {
|
||||
return switch (self.selectModuleConst(name)) {
|
||||
.resolved => |sel| {
|
||||
if (constFoldFrameContains(frame, name, sel.source)) return null;
|
||||
if (!program_index_mod.isCountableConstType(&self.module.types, sel.info.ty)) return null;
|
||||
var f = ConstFoldFrame{ .name = name, .source = sel.source, .parent = frame };
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/// Float counterpart of `foldSourceConstInt` (E2/F2/R1).
|
||||
fn foldSourceConstFloat(self: *Lowering, name: []const u8, frame: ?*const ConstFoldFrame) ?f64 {
|
||||
return switch (self.selectModuleConst(name)) {
|
||||
.resolved => |sel| {
|
||||
if (constFoldFrameContains(frame, name, sel.source)) return null;
|
||||
if (!program_index_mod.isCountableConstType(&self.module.types, sel.info.ty)) return null;
|
||||
var f = ConstFoldFrame{ .name = name, .source = sel.source, .parent = frame };
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/// Source-aware "is `name` a FLOAT-valued module const" (E2/F2/R1): judge the
|
||||
/// SELECTED author's value, with nested const leaves resolved source-aware.
|
||||
fn sourceConstIsFloatTyped(self: *Lowering, name: []const u8, frame: ?*const ConstFoldFrame) bool {
|
||||
return switch (self.selectModuleConst(name)) {
|
||||
.resolved => |sel| {
|
||||
if (constFoldFrameContains(frame, name, sel.source)) return false;
|
||||
if (program_index_mod.isFloatConstType(sel.info.ty)) return true;
|
||||
var f = ConstFoldFrame{ .name = name, .source = sel.source, .parent = frame };
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/// 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: ModuleConstInfo,
|
||||
source: ?[]const u8,
|
||||
};
|
||||
|
||||
const ConstAuthor = union(enum) {
|
||||
resolved: SelectedConst,
|
||||
ambiguous,
|
||||
none,
|
||||
};
|
||||
|
||||
/// 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
|
||||
/// collector and reads the value from the SELECTED author's per-source cache
|
||||
/// (`module_consts_by_source`), never the global last-wins `module_const_map`:
|
||||
///
|
||||
/// - **own-wins**: the querying module's OWN const author is selected outright.
|
||||
/// - else the FLAT-import-reachable const authors: exactly one → it; ≥2 distinct
|
||||
/// → `.ambiguous` (issue 0105 / 0760 — never a silent first-/last-wins pick).
|
||||
/// - none visible → `.none` (a namespaced-only const must be qualified `ns.X`;
|
||||
/// a non-const name folds to `.none` too).
|
||||
///
|
||||
/// A main-file body carries a null `current_source_file` (it IS the root), so
|
||||
/// the querying module is `main_file` there; a fully unwired index (no source
|
||||
/// at all) falls open to the global registration, byte-identical to the legacy
|
||||
/// reader for the registration / comptime-host path.
|
||||
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 = .{ .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.sourceHasModuleConst(o.source, name)) return false;
|
||||
for (set.flat) |fa| if (self.sourceHasModuleConst(fa.source, name)) return false;
|
||||
return true;
|
||||
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 = .{ .info = ci, .source = fa.source };
|
||||
}
|
||||
if (the_one) |sc| return .{ .resolved = sc };
|
||||
return .none;
|
||||
}
|
||||
|
||||
/// True iff `source`'s per-source const cache declares `name` (E0's
|
||||
/// `module_consts_by_source` write side).
|
||||
fn sourceHasModuleConst(self: *Lowering, source: []const u8, name: []const u8) bool {
|
||||
const inner = self.program_index.module_consts_by_source.get(source) orelse return false;
|
||||
return inner.contains(name);
|
||||
/// `source`'s per-source const cache entry for `name` (E0's
|
||||
/// `module_consts_by_source` write side), or null.
|
||||
fn sourceModuleConst(self: *Lowering, source: []const u8, name: []const u8) ?ModuleConstInfo {
|
||||
const inner = self.program_index.module_consts_by_source.get(source) orelse return null;
|
||||
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.
|
||||
@@ -16821,7 +17008,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
|
||||
|
||||
@@ -134,7 +134,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;
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ fn moduleConstFloatValuedFramed(consts: *const std.StringHashMap(ModuleConstInfo
|
||||
/// 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)) {
|
||||
|
||||
Reference in New Issue
Block a user