From bb1ed7294b8c5e9e0f6dd86674af898b9744ed64 Mon Sep 17 00:00:00 2001 From: agra Date: Sat, 6 Jun 2026 13:22:11 +0300 Subject: [PATCH] fix(lower): key fn_decl_fids by stable AST pointer in scanDecls [0102b F1] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scanDecls declared a bare `.fn_decl` via `declareFunction(&fd, ...)`, where `fd` is the switch-capture COPY of `decl.data.fn_decl`. Its address is a per-iteration stack temporary, so the winner author's `fn_decl_fids` entry was keyed by an address no later decl-identity lookup can reproduce — `fn_ast_map` and `module_fns` carry the stable `&decl.data.fn_decl`, so a lookup by that pointer missed the winner's FuncId. fix-0102c routes calls through exactly these stable pointers, so the key has to match. Record the entry under `&decl.data.fn_decl` (the persistent AST node field) to match `fn_ast_map`/`module_fns`. The other declareFunction sites already pass stable pointers (const_decl field, module_fns entry, fn_ast_map entry, struct-method node field, heap-synthesized objc decl); `lowered_fids` keys by FuncId value, so neither has the temporary-address mistake. Strengthen the fix-0102b regression test: assert the identity map round-trips by the STABLE pointer for BOTH same-name authors — the winner's `fn_ast_map` pointer resolves to the first-wins FuncId, and the shadow's `module_fns` pointer resolves to a distinct FuncId. This assertion fails on the pre-fix code (winner keyed by `&fd` → null) and passes after. Call resolution unchanged (name path still default). Gate (this worktree): zig build, zig build test (400/400), bash tests/run_examples.sh (457 passed) all green. --- src/ir/lower.test.zig | 26 ++++++++++++++++++++++++++ src/ir/lower.zig | 10 ++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/ir/lower.test.zig b/src/ir/lower.test.zig index 7a97a16..eb682e9 100644 --- a/src/ir/lower.test.zig +++ b/src/ir/lower.test.zig @@ -1400,4 +1400,30 @@ test "lower: shadowed same-name author gets its own FuncId + real body (fix-0102 } try std.testing.expect(first != null and second != null); try std.testing.expect(first.? != second.?); + + // F1 (attempt-2): the identity map must be keyed by the STABLE AST field + // pointer for BOTH same-name authors — the exact pointers `fn_ast_map` and + // `module_fns` carry — not a per-iteration switch-capture temporary. If the + // winner were keyed by `&fd` (the scanDecls bug), this lookup by the stable + // `fn_ast_map` pointer would miss (null). fix-0102c routes calls through + // exactly these pointers, so the round-trip must hold here. + const winner_fd = lowering.program_index.fn_ast_map.get("greet").?; + const winner_fid = lowering.fn_decl_fids.get(winner_fd); + try std.testing.expect(winner_fid != null); + // Round-trips to the first-wins winner FuncId (resolveFuncByName's pick). + try std.testing.expectEqual(lowering.resolveFuncByName("greet").?, winner_fid.?); + + // The shadowed author's stable pointer lives in `module_fns`; find the one + // that is NOT the winner and confirm IT round-trips to a DISTINCT FuncId. + var shadow_fd: ?*const ast.FnDecl = null; + var mf_it = module_fns.iterator(); + while (mf_it.next()) |path_entry| { + if (path_entry.value_ptr.get("greet")) |fd| { + if (fd != winner_fd) shadow_fd = fd; + } + } + try std.testing.expect(shadow_fd != null); + const shadow_fid = lowering.fn_decl_fids.get(shadow_fd.?); + try std.testing.expect(shadow_fid != null); + try std.testing.expect(shadow_fid.? != winner_fid.?); } diff --git a/src/ir/lower.zig b/src/ir/lower.zig index f65ec13..5c7f48b 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -791,8 +791,14 @@ pub const Lowering = struct { self.program_index.fn_ast_map.put(fd.name, &decl.data.fn_decl) catch {}; self.program_index.import_flags.put(fd.name, is_imported) catch {}; } - // Declare extern stub for all functions (bodies lowered lazily) - self.declareFunction(&fd, fd.name); + // Declare extern stub for all functions (bodies lowered + // lazily). Key the identity map (`fn_decl_fids`, inside + // `declareFunction`) by the STABLE AST field pointer — the + // same `&decl.data.fn_decl` stored in `fn_ast_map` and + // `module_fns` — not the switch-capture copy `fd`, whose + // address is a per-iteration stack temporary that no later + // decl-identity lookup can reproduce. + self.declareFunction(&decl.data.fn_decl, fd.name); }, .const_decl => |cd| { if (cd.value.data == .fn_decl) {