From 13bd3c85ea1992769eff880638be43c9369bf00d Mon Sep 17 00:00:00 2001 From: agra Date: Sun, 31 May 2026 09:40:05 +0300 Subject: [PATCH] lsp/sema: regression tests for generic indexing through import merge Covers List(Move).items[i] -> Move via the LSP's flat-import struct_types merge (pre-registered, not self-declared) and with realistic methods/cross-referencing fields. Confirmed end-to-end against the real binary: the inlay hint for 'm := legal.items[i]' now resolves to Move. --- src/sema.zig | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/sema.zig b/src/sema.zig index 2d8f742..d419b60 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -1786,6 +1786,83 @@ test "sema: index into generic List(T).items resolves the element struct" { try std.testing.expect(found_m); } +test "sema: generic index resolves with pre-registered (imported) struct types" { + const parser_mod = @import("parser.zig"); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + // An "imported" module defining List + Move. + const lib_src = "Move :: struct { score: s64; }" ++ + "List :: struct ($T: Type) { items: [*]T = null; len: s64; }"; + var lib_parser = parser_mod.Parser.init(alloc, lib_src); + const lib_root = try lib_parser.parse(); + var lib = Analyzer.init(alloc); + const lib_res = try lib.analyze(lib_root); + + // Main analyzer pre-loaded with the imported struct_types (bare names), + // mirroring DocumentStore.analyzeDocument's flat-import merge. + var main_an = Analyzer.init(alloc); + var it = lib_res.struct_types.iterator(); + while (it.next()) |e| try main_an.struct_types.put(e.key_ptr.*, e.value_ptr.*); + + const main_src = "main :: () { legal := List(Move).{}; m := legal.items[0]; }"; + var main_parser = parser_mod.Parser.init(alloc, main_src); + const main_root = try main_parser.parse(); + const main_res = try main_an.analyze(main_root); + + var found_m = false; + for (main_res.symbols) |sym| { + if (std.mem.eql(u8, sym.name, "m")) { + found_m = true; + const ty = sym.ty orelse return error.TestUnexpectedResult; + try std.testing.expect(ty == .struct_type); + try std.testing.expectEqualStrings("Move", ty.struct_type); + break; + } + } + try std.testing.expect(found_m); +} + +test "sema: generic index resolves with realistic List/Move (methods, cross-refs)" { + const parser_mod = @import("parser.zig"); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const lib_src = + "Square :: struct { index: s64; }" ++ + "MoveFlag :: enum { none; promote_rook; }" ++ + "Move :: struct { from: Square; to: Square; flag: MoveFlag; is_capture :: (self: Move) -> bool { true; } }" ++ + "List :: struct ($T: Type) { items: [*]T = null; len: s64 = 0; cap: s64 = 0; append :: (list: *List(T), item: T) {} }"; + var lib_parser = parser_mod.Parser.init(alloc, lib_src); + const lib_root = try lib_parser.parse(); + var lib = Analyzer.init(alloc); + const lib_res = try lib.analyze(lib_root); + + var main_an = Analyzer.init(alloc); + var sit = lib_res.struct_types.iterator(); + while (sit.next()) |e| try main_an.struct_types.put(e.key_ptr.*, e.value_ptr.*); + var eit = lib_res.enum_types.iterator(); + while (eit.next()) |e| try main_an.enum_types.put(e.key_ptr.*, e.value_ptr.*); + + const main_src = "main :: () { legal := List(Move).{}; m := legal.items[0]; f := m.from; }"; + var main_parser = parser_mod.Parser.init(alloc, main_src); + const main_root = try main_parser.parse(); + const main_res = try main_an.analyze(main_root); + + var m_ty: ?Type = null; + var f_ty: ?Type = null; + for (main_res.symbols) |sym| { + if (std.mem.eql(u8, sym.name, "m")) m_ty = sym.ty; + if (std.mem.eql(u8, sym.name, "f")) f_ty = sym.ty; + } + try std.testing.expect(m_ty != null and m_ty.? == .struct_type); + try std.testing.expectEqualStrings("Move", m_ty.?.struct_type); + try std.testing.expect(f_ty != null and f_ty.? == .struct_type); + try std.testing.expectEqualStrings("Square", f_ty.?.struct_type); +} + test "sema: variable shadowing in same scope is allowed" { const parser_mod = @import("parser.zig");