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.
This commit is contained in:
agra
2026-05-31 09:40:05 +03:00
parent 7c7a5ad5c7
commit 13bd3c85ea

View File

@@ -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");