diff --git a/examples/0759-modules-same-name-const-own.sx b/examples/0759-modules-same-name-const-own.sx deleted file mode 100644 index 2d349d9..0000000 --- a/examples/0759-modules-same-name-const-own.sx +++ /dev/null @@ -1,16 +0,0 @@ -// 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. Pre-fix both `a_k` and `b_k` returned B's -// `K` (the const READ path read the global last-wins `module_const_map`); now -// `a_k` returns 1 and `b_k` returns 2, resolved by the source-aware const -// author selector (`selectModuleConst`). -#import "modules/std.sx"; -#import "0759-modules-same-name-const-own/a.sx"; -#import "0759-modules-same-name-const-own/b.sx"; - -main :: () -> s32 { - print("a={} b={}\n", a_k(), b_k()); - 0 -} diff --git a/examples/0759-modules-same-name-const-own/a.sx b/examples/0759-modules-same-name-const-own/a.sx deleted file mode 100644 index 66c7c26..0000000 --- a/examples/0759-modules-same-name-const-own/a.sx +++ /dev/null @@ -1,3 +0,0 @@ -// Module A authors its OWN value const `K` (1) and reads it bare. -K :: 1; -a_k :: () -> s64 { return K; } diff --git a/examples/0759-modules-same-name-const-own/b.sx b/examples/0759-modules-same-name-const-own/b.sx deleted file mode 100644 index ef12778..0000000 --- a/examples/0759-modules-same-name-const-own/b.sx +++ /dev/null @@ -1,5 +0,0 @@ -// Module B authors a DIFFERENT same-name value const `K` (2) — a shadow of A's -// `K`. Pre-fix the two collapsed last-wins in the global const map, so A's `a_k` -// read B's value; now each `K` is selected per declaring source. -K :: 2; -b_k :: () -> s64 { return K; } diff --git a/examples/0760-modules-same-name-const-ambiguous.sx b/examples/0760-modules-same-name-const-ambiguous.sx deleted file mode 100644 index e364abd..0000000 --- a/examples/0760-modules-same-name-const-ambiguous.sx +++ /dev/null @@ -1,14 +0,0 @@ -// 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 "0760-modules-same-name-const-ambiguous/a.sx"; -#import "0760-modules-same-name-const-ambiguous/b.sx"; - -main :: () -> s32 { - print("K={}\n", K); - 0 -} diff --git a/examples/0760-modules-same-name-const-ambiguous/a.sx b/examples/0760-modules-same-name-const-ambiguous/a.sx deleted file mode 100644 index cf57dc8..0000000 --- a/examples/0760-modules-same-name-const-ambiguous/a.sx +++ /dev/null @@ -1,3 +0,0 @@ -// 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; diff --git a/examples/0760-modules-same-name-const-ambiguous/b.sx b/examples/0760-modules-same-name-const-ambiguous/b.sx deleted file mode 100644 index 386c1a7..0000000 --- a/examples/0760-modules-same-name-const-ambiguous/b.sx +++ /dev/null @@ -1,2 +0,0 @@ -// The second flat author of value const `K`. -K :: 2; diff --git a/examples/0761-modules-same-name-const-expr-chain-dim.sx b/examples/0761-modules-same-name-const-expr-chain-dim.sx deleted file mode 100644 index d635b38..0000000 --- a/examples/0761-modules-same-name-const-expr-chain-dim.sx +++ /dev/null @@ -1,19 +0,0 @@ -// 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 -} diff --git a/examples/0761-modules-same-name-const-expr-chain-dim/a.sx b/examples/0761-modules-same-name-const-expr-chain-dim/a.sx deleted file mode 100644 index a806828..0000000 --- a/examples/0761-modules-same-name-const-expr-chain-dim/a.sx +++ /dev/null @@ -1,9 +0,0 @@ -// 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; -} diff --git a/examples/0761-modules-same-name-const-expr-chain-dim/b.sx b/examples/0761-modules-same-name-const-expr-chain-dim/b.sx deleted file mode 100644 index db1c9a1..0000000 --- a/examples/0761-modules-same-name-const-expr-chain-dim/b.sx +++ /dev/null @@ -1,11 +0,0 @@ -// 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; -} diff --git a/examples/0762-modules-same-name-const-leaf-author-pin.sx b/examples/0762-modules-same-name-const-leaf-author-pin.sx deleted file mode 100644 index 65f8257..0000000 --- a/examples/0762-modules-same-name-const-leaf-author-pin.sx +++ /dev/null @@ -1,25 +0,0 @@ -// 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`). -// -// Pre-fix (8518b66) the nested leaf re-selected `M` from the CALLER's source: -// `main` flat-imports two `M`s → `'M' is ambiguous` (value read) / "array -// dimension must be a compile-time integer constant" (dimension). Now the fold -// pins each level to its selected author's source → val=2 len=2. -#import "modules/std.sx"; -#import "0762-modules-same-name-const-leaf-author-pin/a.sx"; -#import "0762-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 -} diff --git a/examples/0762-modules-same-name-const-leaf-author-pin/a.sx b/examples/0762-modules-same-name-const-leaf-author-pin/a.sx deleted file mode 100644 index dd7c9dd..0000000 --- a/examples/0762-modules-same-name-const-leaf-author-pin/a.sx +++ /dev/null @@ -1,5 +0,0 @@ -// 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; diff --git a/examples/0762-modules-same-name-const-leaf-author-pin/b.sx b/examples/0762-modules-same-name-const-leaf-author-pin/b.sx deleted file mode 100644 index b2fe411..0000000 --- a/examples/0762-modules-same-name-const-leaf-author-pin/b.sx +++ /dev/null @@ -1,5 +0,0 @@ -// 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; diff --git a/examples/expected/0759-modules-same-name-const-own.exit b/examples/expected/0759-modules-same-name-const-own.exit deleted file mode 100644 index 573541a..0000000 --- a/examples/expected/0759-modules-same-name-const-own.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/examples/expected/0759-modules-same-name-const-own.stderr b/examples/expected/0759-modules-same-name-const-own.stderr deleted file mode 100644 index 8b13789..0000000 --- a/examples/expected/0759-modules-same-name-const-own.stderr +++ /dev/null @@ -1 +0,0 @@ - diff --git a/examples/expected/0759-modules-same-name-const-own.stdout b/examples/expected/0759-modules-same-name-const-own.stdout deleted file mode 100644 index b0ff4e7..0000000 --- a/examples/expected/0759-modules-same-name-const-own.stdout +++ /dev/null @@ -1 +0,0 @@ -a=1 b=2 diff --git a/examples/expected/0760-modules-same-name-const-ambiguous.exit b/examples/expected/0760-modules-same-name-const-ambiguous.exit deleted file mode 100644 index d00491f..0000000 --- a/examples/expected/0760-modules-same-name-const-ambiguous.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/examples/expected/0760-modules-same-name-const-ambiguous.stderr b/examples/expected/0760-modules-same-name-const-ambiguous.stderr deleted file mode 100644 index 03ed12e..0000000 --- a/examples/expected/0760-modules-same-name-const-ambiguous.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: 'K' is ambiguous: it is declared in multiple flat-imported modules; qualify the reference or remove the duplicate import - --> examples/0760-modules-same-name-const-ambiguous.sx:12:21 - | -12 | print("K={}\n", K); - | ^ diff --git a/examples/expected/0760-modules-same-name-const-ambiguous.stdout b/examples/expected/0760-modules-same-name-const-ambiguous.stdout deleted file mode 100644 index 8b13789..0000000 --- a/examples/expected/0760-modules-same-name-const-ambiguous.stdout +++ /dev/null @@ -1 +0,0 @@ - diff --git a/examples/expected/0761-modules-same-name-const-expr-chain-dim.exit b/examples/expected/0761-modules-same-name-const-expr-chain-dim.exit deleted file mode 100644 index 573541a..0000000 --- a/examples/expected/0761-modules-same-name-const-expr-chain-dim.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/examples/expected/0761-modules-same-name-const-expr-chain-dim.stderr b/examples/expected/0761-modules-same-name-const-expr-chain-dim.stderr deleted file mode 100644 index 8b13789..0000000 --- a/examples/expected/0761-modules-same-name-const-expr-chain-dim.stderr +++ /dev/null @@ -1 +0,0 @@ - diff --git a/examples/expected/0761-modules-same-name-const-expr-chain-dim.stdout b/examples/expected/0761-modules-same-name-const-expr-chain-dim.stdout deleted file mode 100644 index 6f78793..0000000 --- a/examples/expected/0761-modules-same-name-const-expr-chain-dim.stdout +++ /dev/null @@ -1 +0,0 @@ -a_len=2 a_val=2 b_len=11 b_val=11 diff --git a/examples/expected/0762-modules-same-name-const-leaf-author-pin.exit b/examples/expected/0762-modules-same-name-const-leaf-author-pin.exit deleted file mode 100644 index 573541a..0000000 --- a/examples/expected/0762-modules-same-name-const-leaf-author-pin.exit +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/examples/expected/0762-modules-same-name-const-leaf-author-pin.stderr b/examples/expected/0762-modules-same-name-const-leaf-author-pin.stderr deleted file mode 100644 index 8b13789..0000000 --- a/examples/expected/0762-modules-same-name-const-leaf-author-pin.stderr +++ /dev/null @@ -1 +0,0 @@ - diff --git a/examples/expected/0762-modules-same-name-const-leaf-author-pin.stdout b/examples/expected/0762-modules-same-name-const-leaf-author-pin.stdout deleted file mode 100644 index d5717e6..0000000 --- a/examples/expected/0762-modules-same-name-const-leaf-author-pin.stdout +++ /dev/null @@ -1 +0,0 @@ -val=2 len=2 diff --git a/src/imports.zig b/src/imports.zig index 47747d6..9717093 100644 --- a/src/imports.zig +++ b/src/imports.zig @@ -314,9 +314,9 @@ 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 (type / alias / - // const), which must reach registration as a distinct author of - // its own module (issues 0104/0105). + // (first-wins) UNLESS this is a per-source decl (a named type or + // a type-alias const), which must reach registration as a + // distinct author of its own module (issues 0104/0105). append_to_global = isPerSourceDecl(decl); } else { try seen_list.put(name, {}); @@ -352,12 +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 (type / alias / const), each of which must - // reach registration as a distinct same-name author of its own - // module (issues 0104/0105). 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. + // 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. if (!isPerSourceDecl(decl)) continue; } else { try seen_list.put(name, {}); @@ -370,18 +372,43 @@ 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 non-function `const_decl`s (type - /// aliases + value consts, source-keyed via the alias/const caches) are - /// per-source — that is what closes issues 0104/0105. 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`. + /// 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`. 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, - .const_decl => |cd| cd.value.data != .fn_decl, + // 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, + }, else => false, }; } diff --git a/src/ir/lower.zig b/src/ir/lower.zig index b5d5e23..9a82fc5 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -54,51 +54,6 @@ 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 { @@ -1424,21 +1379,9 @@ 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). 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 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; + // 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; } 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 }); @@ -3952,29 +3895,13 @@ 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_global| { + if (self.program_index.module_const_map.get(id.name)) |ci| { 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); } - // E2/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}); - // One diagnostic only (mirrors the type ambiguity at - // `resolveNominalLeaf`): a bare placeholder, no second - // "unresolved" cascade from `emitError`. - break :blk self.emitPlaceholder(id.name); - }, - .none => break :blk self.emitModuleConst(ci_global, null), - } + break :blk self.emitModuleConst(ci); } // Check if it's a function name — produce function pointer reference // Resolve mangled name for block-local functions @@ -13458,26 +13385,8 @@ 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 => |sel| { - if (!program_index_mod.isCountableConstType(&self.module.types, sel.info.ty)) return null; - var f = ConstFoldFrame{ .name = name, .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, - }; + if (self.moduleConstBareInvisible(name)) return null; + return program_index_mod.moduleConstFloat(&self.program_index.module_const_map, &self.module.types, name); } /// True iff `name` is a FLOAT-valued module const (`F : f64 : 2.5`, @@ -13487,30 +13396,11 @@ 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 => |sel| { - if (program_index_mod.isFloatConstType(sel.info.ty)) return true; - var f = ConstFoldFrame{ .name = name, .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, - }; + if (self.moduleConstBareInvisible(name)) return false; + return program_index_mod.moduleConstIsFloatTyped(&self.program_index.module_const_map, &self.module.types, name); } /// 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, @@ -13519,118 +13409,43 @@ pub const Lowering = struct { if (self.comptime_value_bindings) |cvb| { if (cvb.get(name)) |v| return v; } - return self.foldSourceConstInt(name, null); + // 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); } - /// 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 => |sel| { - if (!program_index_mod.isCountableConstType(&self.module.types, sel.info.ty)) return null; - var f = ConstFoldFrame{ .name = name, .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, - }; - } - - /// 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 - /// 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. - const ConstAuthor = union(enum) { - 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 = .{ .info = ci, .source = null } }; - return .none; - }; + /// 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; 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 = .{ .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; + 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; } - /// `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) ?program_index_mod.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 }; + /// 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); } /// Resolve a type node, checking type_bindings first for generic type params. @@ -16167,14 +15982,7 @@ pub const Lowering = struct { }; } - 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(); + fn emitModuleConst(self: *Lowering, ci: ModuleConstInfo) Ref { // 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 diff --git a/src/ir/program_index.zig b/src/ir/program_index.zig index c0c847c..5b14896 100644 --- a/src/ir/program_index.zig +++ b/src/ir/program_index.zig @@ -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`. -pub fn isFloatConstType(ty: TypeId) bool { +fn isFloatConstType(ty: TypeId) bool { return ty == .f32 or ty == .f64; } @@ -134,11 +134,6 @@ pub fn isFloatConstType(ty: TypeId) bool { fn moduleConstFloatValuedFramed(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8, parent: ?*const ModuleConstFrame) bool { if (moduleConstFrameContains(parent, name)) return false; const ci = consts.get(name) orelse return false; - return foldConstFloatValued(consts, table, name, ci, parent); -} - -/// Get-less core of `moduleConstFloatValuedFramed`. -fn foldConstFloatValued(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8, ci: ModuleConstInfo, parent: ?*const ModuleConstFrame) bool { if (isFloatConstType(ci.ty)) return true; var frame = ModuleConstFrame{ .name = name, .parent = parent }; return isFloatValuedExpr(ci.value, ModuleConstCtx{ .consts = consts, .table = table, .frame = &frame }); @@ -152,7 +147,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). -pub fn isCountableConstType(table: *const types.TypeTable, ty: TypeId) bool { +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)) { @@ -165,13 +160,6 @@ pub fn isCountableConstType(table: *const types.TypeTable, ty: TypeId) bool { fn moduleConstIntFramed(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8, parent: ?*const ModuleConstFrame) ?i64 { if (moduleConstFrameContains(parent, name)) return null; const ci = consts.get(name) orelse return null; - return foldConstInt(consts, table, name, ci, parent); -} - -/// Fold a SELECTED `ModuleConstInfo`'s RHS to an integer count, the get-less core -/// of `moduleConstIntFramed`. `name` keys the cycle frame; `consts` is the LEAF -/// map a const-EXPRESSION RHS (`N :: M + 1`) resolves `M` through. -fn foldConstInt(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8, ci: ModuleConstInfo, parent: ?*const ModuleConstFrame) ?i64 { if (!isCountableConstType(table, ci.ty)) return null; var frame = ModuleConstFrame{ .name = name, .parent = parent }; return evalConstIntExpr(ci.value, ModuleConstCtx{ .consts = consts, .table = table, .frame = &frame }); @@ -207,11 +195,6 @@ pub fn moduleConstInt(consts: *const std.StringHashMap(ModuleConstInfo), table: fn moduleConstFloatFramed(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8, parent: ?*const ModuleConstFrame) ?f64 { if (moduleConstFrameContains(parent, name)) return null; const ci = consts.get(name) orelse return null; - return foldConstFloat(consts, table, name, ci, parent); -} - -/// Float counterpart of `foldConstInt` — the get-less core of `moduleConstFloatFramed`. -fn foldConstFloat(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8, ci: ModuleConstInfo, parent: ?*const ModuleConstFrame) ?f64 { if (!isCountableConstType(table, ci.ty)) return null; var frame = ModuleConstFrame{ .name = name, .parent = parent }; return evalConstFloatExpr(ci.value, ModuleConstCtx{ .consts = consts, .table = table, .frame = &frame });