From 919c7bd8556ee11aba08234e022fcdc0a277cd8e Mon Sep 17 00:00:00 2001 From: agra Date: Mon, 8 Jun 2026 22:07:12 +0300 Subject: [PATCH] fix(stdlib/E5): source-aware value-const TYPE inference (F4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../0793-modules-same-name-const-type-infer.sx | 16 ++++++++++++++++ .../a.sx | 5 +++++ .../b.sx | 5 +++++ ...4-modules-same-name-const-type-ambiguous.sx | 15 +++++++++++++++ .../a.sx | 2 ++ .../b.sx | 2 ++ ...793-modules-same-name-const-type-infer.exit | 1 + ...3-modules-same-name-const-type-infer.stderr | 1 + ...3-modules-same-name-const-type-infer.stdout | 1 + ...modules-same-name-const-type-ambiguous.exit | 1 + ...dules-same-name-const-type-ambiguous.stderr | 5 +++++ ...dules-same-name-const-type-ambiguous.stdout | 1 + src/ir/expr_typer.zig | 18 +++++++++++++++--- src/ir/lower.zig | 2 +- 14 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 examples/0793-modules-same-name-const-type-infer.sx create mode 100644 examples/0793-modules-same-name-const-type-infer/a.sx create mode 100644 examples/0793-modules-same-name-const-type-infer/b.sx create mode 100644 examples/0794-modules-same-name-const-type-ambiguous.sx create mode 100644 examples/0794-modules-same-name-const-type-ambiguous/a.sx create mode 100644 examples/0794-modules-same-name-const-type-ambiguous/b.sx create mode 100644 examples/expected/0793-modules-same-name-const-type-infer.exit create mode 100644 examples/expected/0793-modules-same-name-const-type-infer.stderr create mode 100644 examples/expected/0793-modules-same-name-const-type-infer.stdout create mode 100644 examples/expected/0794-modules-same-name-const-type-ambiguous.exit create mode 100644 examples/expected/0794-modules-same-name-const-type-ambiguous.stderr create mode 100644 examples/expected/0794-modules-same-name-const-type-ambiguous.stdout diff --git a/examples/0793-modules-same-name-const-type-infer.sx b/examples/0793-modules-same-name-const-type-infer.sx new file mode 100644 index 0000000..f540f4e --- /dev/null +++ b/examples/0793-modules-same-name-const-type-infer.sx @@ -0,0 +1,16 @@ +// issue 0105 / F4 — same-name VALUE const TYPE inference is source-aware. Two +// flat-imported modules each declare a top-level `K` with a DIFFERENT declared +// TYPE (A: `s32`, B: `f64`) and an inferred-return function that reads `K` bare. +// The inferred return type must come from each module's OWN `K` (own-wins), not +// the global last-wins const TYPE: `a_k` infers A's `s32` and yields the integer +// `1` (printed `1`, not `1.000000`), while `b_k` infers B's `f64` and yields +// `2.500000`. Selection routes through the source-aware `selectModuleConst`, the +// same author selector emission/folding use — type inference must agree. +#import "modules/std.sx"; +#import "0793-modules-same-name-const-type-infer/a.sx"; +#import "0793-modules-same-name-const-type-infer/b.sx"; + +main :: () -> s32 { + print("a={} b={}\n", a_k(), b_k()); + 0 +} diff --git a/examples/0793-modules-same-name-const-type-infer/a.sx b/examples/0793-modules-same-name-const-type-infer/a.sx new file mode 100644 index 0000000..3d02899 --- /dev/null +++ b/examples/0793-modules-same-name-const-type-infer/a.sx @@ -0,0 +1,5 @@ +// Module A authors its OWN `K` declared `s32`. Its inferred-return `a_k` reads +// `K` bare; the inferred return type must be A's `s32`, so the value prints as +// the integer `1`, never coerced to B's `f64`. +K : s32 : 1; +a_k :: () { return K; } diff --git a/examples/0793-modules-same-name-const-type-infer/b.sx b/examples/0793-modules-same-name-const-type-infer/b.sx new file mode 100644 index 0000000..91b7bea --- /dev/null +++ b/examples/0793-modules-same-name-const-type-infer/b.sx @@ -0,0 +1,5 @@ +// Module B authors a DIFFERENT same-name `K` declared `f64` (a shadow of A's +// `K`). Its inferred-return `b_k` reads `K` bare; the inferred return type must +// be B's `f64`, so the value prints as `2.500000`. +K : f64 : 2.5; +b_k :: () { return K; } diff --git a/examples/0794-modules-same-name-const-type-ambiguous.sx b/examples/0794-modules-same-name-const-type-ambiguous.sx new file mode 100644 index 0000000..e5634d8 --- /dev/null +++ b/examples/0794-modules-same-name-const-type-ambiguous.sx @@ -0,0 +1,15 @@ +// issue 0105 / F4 — same-name VALUE const with DIFFERENT declared TYPES across +// two flat-imported modules (A: `s32`, B: `f64`), referenced bare at a mixed-type +// site. A bare `K` here is genuinely ambiguous — there are ≥2 flat-visible same- +// name authors — so it must diagnose loudly (exit 1), exactly as the same-typed +// 0787 does. The type-inference change must NOT mask the ambiguity (inferring +// `.unresolved` for the ambiguous reference) — the emission path still fires the +// loud author-count diagnostic regardless of the consts' declared types. +#import "modules/std.sx"; +#import "0794-modules-same-name-const-type-ambiguous/a.sx"; +#import "0794-modules-same-name-const-type-ambiguous/b.sx"; + +main :: () -> s32 { + print("K={}\n", K); + 0 +} diff --git a/examples/0794-modules-same-name-const-type-ambiguous/a.sx b/examples/0794-modules-same-name-const-type-ambiguous/a.sx new file mode 100644 index 0000000..a4460aa --- /dev/null +++ b/examples/0794-modules-same-name-const-type-ambiguous/a.sx @@ -0,0 +1,2 @@ +// Module A authors `K` declared `s32`. +K : s32 : 1; diff --git a/examples/0794-modules-same-name-const-type-ambiguous/b.sx b/examples/0794-modules-same-name-const-type-ambiguous/b.sx new file mode 100644 index 0000000..b713b4f --- /dev/null +++ b/examples/0794-modules-same-name-const-type-ambiguous/b.sx @@ -0,0 +1,2 @@ +// Module B authors a DIFFERENT same-name `K` declared `f64`. +K : f64 : 2.5; diff --git a/examples/expected/0793-modules-same-name-const-type-infer.exit b/examples/expected/0793-modules-same-name-const-type-infer.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/examples/expected/0793-modules-same-name-const-type-infer.exit @@ -0,0 +1 @@ +0 diff --git a/examples/expected/0793-modules-same-name-const-type-infer.stderr b/examples/expected/0793-modules-same-name-const-type-infer.stderr new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/0793-modules-same-name-const-type-infer.stderr @@ -0,0 +1 @@ + diff --git a/examples/expected/0793-modules-same-name-const-type-infer.stdout b/examples/expected/0793-modules-same-name-const-type-infer.stdout new file mode 100644 index 0000000..ace952f --- /dev/null +++ b/examples/expected/0793-modules-same-name-const-type-infer.stdout @@ -0,0 +1 @@ +a=1 b=2.500000 diff --git a/examples/expected/0794-modules-same-name-const-type-ambiguous.exit b/examples/expected/0794-modules-same-name-const-type-ambiguous.exit new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/examples/expected/0794-modules-same-name-const-type-ambiguous.exit @@ -0,0 +1 @@ +1 diff --git a/examples/expected/0794-modules-same-name-const-type-ambiguous.stderr b/examples/expected/0794-modules-same-name-const-type-ambiguous.stderr new file mode 100644 index 0000000..671ec2f --- /dev/null +++ b/examples/expected/0794-modules-same-name-const-type-ambiguous.stderr @@ -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/0794-modules-same-name-const-type-ambiguous.sx:13:21 + | +13 | print("K={}\n", K); + | ^ diff --git a/examples/expected/0794-modules-same-name-const-type-ambiguous.stdout b/examples/expected/0794-modules-same-name-const-type-ambiguous.stdout new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/0794-modules-same-name-const-type-ambiguous.stdout @@ -0,0 +1 @@ + diff --git a/src/ir/expr_typer.zig b/src/ir/expr_typer.zig index ed3d2f5..e7b5b70 100644 --- a/src/ir/expr_typer.zig +++ b/src/ir/expr_typer.zig @@ -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 diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 506ac87..c6093f5 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -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;