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