revert(stdlib): narrow E2 to the 0105 type/alias close; defer value consts to E5 [stdlib E2 attempt-6]

Scope-narrowing revert of the value-const same-name sub-area (attempts 3-5),
per PO/Agra ruling. The 0105 type/alias close (per-source nominal struct
identity, source-keyed type aliases, F1 self/mutual refs, anon-struct
regression) is kept intact; cross-module same-name VALUE consts move to step E5.

- imports.zig: narrow `isPerSourceDecl` so a `const_decl` is retained
  per-source ONLY when its RHS introduces a TYPE (alias / inline type decl).
  VALUE consts (literal / value-expression RHS) and functions keep the pre-E2
  first-wins name-merge. Restores value-const reads to exactly the
  wt-stdlib-base (pre-E2) first-wins behavior.
- lower.zig / program_index.zig: restored to the pre-value-const state
  (66d10c0) — removes selectModuleConst / SourceConstCtx / pinConstAuthorSource
  / SelectedConst and the rewired comptimeIntNamed / float / runtime /
  global-init const reads; value-const reads return to the global path.
- examples: drop 0759-0762 (value-const own-wins / ambiguous / expr-chain-dim
  / leaf-author-pin) — they move to E5.

Kept green: 0752-0758 (same-name structs distinct + own-wins + ambiguous + self
/mutual ref), 0756 (alias per-source), 0170 (anon-struct field distinct).

Gate: zig build + zig build test (423/423, LSP sweep 513 no-crash) +
run_examples (496/0, prior markers byte-identical) + m3te ios-sim build exit 0.
This commit is contained in:
agra
2026-06-08 07:28:31 +03:00
parent 4666fb1941
commit f8efa25416
27 changed files with 87 additions and 402 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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