fix(lower): source-aware value-const resolution (own-wins / ambiguous) — close F2 [stdlib E2 attempt-3]
E2 retained per-source const declarations but left the const READ path on the
global last-wins `module_const_map`, so a module's OWN reference to a same-name
const bound the LAST global author (F2: a.sx `K::1`, b.sx `K::2`, main flat-imports
both → both read B's K). Complete the const analog of the type (`selectNominalLeaf`)
and callable (`selectPlainCallableAuthor`) source-aware models.
- `selectModuleConst`: own-wins; exactly one flat-visible author → it; ≥2 distinct
flat-visible → `.ambiguous` (loud diagnostic, consistent with 0755/0724); none
→ `.none`. Reads the SELECTED author's per-source value (`module_consts_by_source`)
and folds its RHS over the global leaf map, so a const-EXPRESSION chain
(`N :: M + 1`, M flat-imported) still resolves M.
- Rewire `comptimeIntNamed` / `lookupFloatName` / `nameIsFloatTyped`, the runtime
identifier path, and the global-init-from-const path through it; drop the now
subsumed `moduleConstBareInvisible` gate.
- program_index: `moduleConst{Int,Float,IsFloatTyped}With` fold a selected `ci`.
Examples: 0759 (own-wins value const, a=1 b=2) + 0760 (two-flat-visible →
ambiguous). Single-author byte-identical (run_examples 498/0, 496 prior unchanged;
zig build test 423/423; corpus sweep 515 no-crash; m3te ios-sim exit 0).
This commit is contained in:
16
examples/0759-modules-same-name-const-own.sx
Normal file
16
examples/0759-modules-same-name-const-own.sx
Normal file
@@ -0,0 +1,16 @@
|
||||
// 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
|
||||
}
|
||||
3
examples/0759-modules-same-name-const-own/a.sx
Normal file
3
examples/0759-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/0759-modules-same-name-const-own/b.sx
Normal file
5
examples/0759-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`. 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; }
|
||||
14
examples/0760-modules-same-name-const-ambiguous.sx
Normal file
14
examples/0760-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 "0760-modules-same-name-const-ambiguous/a.sx";
|
||||
#import "0760-modules-same-name-const-ambiguous/b.sx";
|
||||
|
||||
main :: () -> s32 {
|
||||
print("K={}\n", K);
|
||||
0
|
||||
}
|
||||
3
examples/0760-modules-same-name-const-ambiguous/a.sx
Normal file
3
examples/0760-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/0760-modules-same-name-const-ambiguous/b.sx
Normal file
2
examples/0760-modules-same-name-const-ambiguous/b.sx
Normal file
@@ -0,0 +1,2 @@
|
||||
// The second flat author of value const `K`.
|
||||
K :: 2;
|
||||
1
examples/expected/0759-modules-same-name-const-own.exit
Normal file
1
examples/expected/0759-modules-same-name-const-own.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
a=1 b=2
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
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);
|
||||
| ^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
129
src/ir/lower.zig
129
src/ir/lower.zig
@@ -1379,8 +1379,18 @@ 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| {
|
||||
// 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 ci = switch (self.selectModuleConst(id.name)) {
|
||||
.resolved => |c| c,
|
||||
.none => ci_global,
|
||||
.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;
|
||||
},
|
||||
};
|
||||
if (self.constExprValue(ci.value, var_ty)) |cv| break :blk cv;
|
||||
}
|
||||
if (self.diagnostics) |d|
|
||||
@@ -3895,13 +3905,29 @@ 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);
|
||||
// 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 => |ci| break :blk self.emitModuleConst(ci),
|
||||
.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),
|
||||
}
|
||||
}
|
||||
// Check if it's a function name — produce function pointer reference
|
||||
// Resolve mangled name for block-local functions
|
||||
@@ -13385,8 +13411,10 @@ 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 switch (self.selectModuleConst(name)) {
|
||||
.resolved => |ci| program_index_mod.moduleConstFloatWith(&self.program_index.module_const_map, &self.module.types, name, ci),
|
||||
.ambiguous, .none => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// True iff `name` is a FLOAT-valued module const (`F : f64 : 2.5`,
|
||||
@@ -13396,8 +13424,10 @@ 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 switch (self.selectModuleConst(name)) {
|
||||
.resolved => |ci| program_index_mod.moduleConstIsFloatTypedWith(&self.program_index.module_const_map, &self.module.types, name, ci),
|
||||
.ambiguous, .none => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// Resolve a name to a compile-time integer across the three const tables.
|
||||
@@ -13409,43 +13439,66 @@ 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);
|
||||
// E2/F2: select the SOURCE-AWARE module-const author (own-wins; ≥2
|
||||
// flat-visible → ambiguous → no value here, the loud diagnostic is the
|
||||
// reference site's job). The selected `ci`'s RHS is folded over the global
|
||||
// leaf map, so a const-EXPRESSION chain (`N :: M + 1`, M flat-imported)
|
||||
// still resolves `M` exactly as before. Single-author selects the same
|
||||
// `ci` the global map holds, so this is byte-identical to the legacy read.
|
||||
return switch (self.selectModuleConst(name)) {
|
||||
.resolved => |ci| program_index_mod.moduleConstIntWith(&self.program_index.module_const_map, &self.module.types, name, ci),
|
||||
.ambiguous, .none => 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;
|
||||
/// 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: program_index_mod.ModuleConstInfo,
|
||||
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 = ci };
|
||||
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 = ci };
|
||||
var the_one: ?program_index_mod.ModuleConstInfo = 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 = ci;
|
||||
}
|
||||
if (the_one) |ci| return .{ .resolved = ci };
|
||||
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) ?program_index_mod.ModuleConstInfo {
|
||||
const inner = self.program_index.module_consts_by_source.get(source) orelse return null;
|
||||
return inner.get(name);
|
||||
}
|
||||
|
||||
/// Resolve a type node, checking type_bindings first for generic type params.
|
||||
|
||||
@@ -134,6 +134,11 @@ 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 });
|
||||
@@ -160,11 +165,27 @@ 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 });
|
||||
}
|
||||
|
||||
/// `moduleConstInt` for a const whose AUTHOR was already selected source-aware
|
||||
/// (E2): fold the provided `ci`'s RHS instead of `consts.get(name)`, so a
|
||||
/// same-name shadow resolves to ITS OWN value while a const-EXPRESSION leaf still
|
||||
/// resolves through the global `consts` map (the chain reader). Single-author →
|
||||
/// byte-identical to `moduleConstInt` (the selected `ci` IS the global one).
|
||||
pub fn moduleConstIntWith(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8, ci: ModuleConstInfo) ?i64 {
|
||||
return foldConstInt(consts, table, name, ci, null);
|
||||
}
|
||||
|
||||
/// A name bound to a module-global integer constant → its value, else null.
|
||||
/// SINGLE source for both array-dimension resolvers — the stateful
|
||||
/// body-lowering path (`Lowering.comptimeIntNamed`) and the stateless
|
||||
@@ -195,6 +216,11 @@ 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 });
|
||||
@@ -204,6 +230,12 @@ pub fn moduleConstFloat(consts: *const std.StringHashMap(ModuleConstInfo), table
|
||||
return moduleConstFloatFramed(consts, table, name, null);
|
||||
}
|
||||
|
||||
/// `moduleConstFloat` for a source-aware-selected `ci` (E2) — the float analog
|
||||
/// of `moduleConstIntWith`.
|
||||
pub fn moduleConstFloatWith(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8, ci: ModuleConstInfo) ?f64 {
|
||||
return foldConstFloat(consts, table, name, ci, null);
|
||||
}
|
||||
|
||||
/// True iff `name` is a FLOAT-valued module const — judged by VALUE, so it covers
|
||||
/// a typed float const (`K : f64 : 4.0`), an untyped float-EXPRESSION const
|
||||
/// (`ME :: 4.0 + 1.0`, whose placeholder type is `s64`), and a non-integral float
|
||||
@@ -214,6 +246,11 @@ pub fn moduleConstIsFloatTyped(consts: *const std.StringHashMap(ModuleConstInfo)
|
||||
return moduleConstFloatValuedFramed(consts, table, name, null);
|
||||
}
|
||||
|
||||
/// `moduleConstIsFloatTyped` for a source-aware-selected `ci` (E2).
|
||||
pub fn moduleConstIsFloatTypedWith(consts: *const std.StringHashMap(ModuleConstInfo), table: *const types.TypeTable, name: []const u8, ci: ModuleConstInfo) bool {
|
||||
return foldConstFloatValued(consts, table, name, ci, null);
|
||||
}
|
||||
|
||||
/// True iff `node` is a FLOAT-valued compile-time expression — a float literal,
|
||||
/// a float-typed const leaf (`F : f64 : 2.5`, `K : f64 : 4.0`), a builtin float
|
||||
/// numeric-limit (`f64.max`), or arithmetic over any of those. THE predicate the
|
||||
|
||||
Reference in New Issue
Block a user