fix(stdlib/E5): source-aware value-const TYPE inference (F4)

Value-const SELECTION was source-aware for emission/folding (F2/R1/F1), but
expression TYPE inference still read the global last-wins `module_const_map`,
so an inferred return type / coercion on a same-name const borrowed another
module's const TYPE (mixed-type same-name consts were never exercised by the
attempt-1 same-typed goldens).

- expr_typer.zig: the `.identifier` const path now selects via the source-aware
  `selectModuleConst` (own-wins / one-flat-visible) instead of the global
  `module_const_map`. The global map still gates "is this a const name?"; an
  unpartitioned registration-only author emits its global type, and an ambiguous
  bare reference yields `.unresolved` (the emission path diagnoses loudly).
- lower.zig: expose `selectModuleConst` so the type-inference path shares the one
  author selector emission/folding already use.

Audited every `module_const_map` read: emission (4102) and global-init copy
(1447) were already source-aware (attempt-1); the binds-a-value predicate (6400)
is a boolean, not a type read; the in-`selectModuleConst` read (13842) is the
unwired fallback. No sibling inference site leaks.

examples: 0793 mixed-type own-wins inference (A's `K:s32` yields `1`, not the
global `f64`'s `1.000000`); 0794 mixed-type bare → loud ambiguous (exit 1), the
inference change does not mask the ambiguity. Prior E5 surfaces (0786-0792), the
0105 set (0752-0758), E1-E4 type surfaces (0763-0785) and FFI byte-identical;
533 markers green.
This commit is contained in:
agra
2026-06-08 22:07:12 +03:00
parent 189774712f
commit 919c7bd855
14 changed files with 71 additions and 4 deletions

View File

@@ -277,9 +277,21 @@ pub const ExprTyper = struct {
if (self.l.program_index.global_names.get(id.name)) |gi| {
return gi.ty;
}
// Check module-level value constants (e.g., WIDTH :f32: 800)
if (self.l.program_index.module_const_map.get(id.name)) |ci| {
return ci.ty;
// Check module-level value constants (e.g., WIDTH :f32: 800).
// F4: a same-name VALUE const must infer the SOURCE-AWARE author's
// TYPE (own-wins / one-flat-visible), not the global last-wins
// `module_const_map` — otherwise a return-type / coercion inferred
// on one module's `K` borrows another module's `K` TYPE. The global
// map still gates "is this a const name at all?"; `.none` is the
// registration-only author with no per-source partition (emit its
// global type), and an ambiguous bare reference yields `.unresolved`
// (the emission path diagnoses the ambiguity loudly).
if (self.l.program_index.module_const_map.get(id.name)) |ci_global| {
return switch (self.l.selectModuleConst(id.name)) {
.resolved => |sel| sel.info.ty,
.none => ci_global.ty,
.ambiguous => .unresolved,
};
}
// A bare type name (alias like `Vec4`, struct name, or
// builtin primitive) referenced in expression position

View File

@@ -13837,7 +13837,7 @@ pub const Lowering = struct {
/// the querying module is `main_file` there; a fully unwired index (no source
/// at all) falls open to the global registration, byte-identical to the legacy
/// reader for the registration / comptime-host path.
fn selectModuleConst(self: *Lowering, name: []const u8) ConstAuthor {
pub 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;