From ef0d9a947791bb65950bcbf2099dfd409ad471be Mon Sep 17 00:00:00 2001 From: agra Date: Sun, 31 May 2026 11:22:24 +0300 Subject: [PATCH] lsp/sema: resolve pointer-typed params and field access through pointers Two gaps made 'piece := board.squares[move.from.index]' (board: *Board) : analyzeParams typed params with fromTypeExpr (bare-name only), so *Board / []T / *List params became null; and field_access only handled a struct value, not a *Struct. Params now resolve via fieldType, and field_access auto-derefs a pointer object (p.field on *T resolves on T). Regression test added. --- src/sema.zig | 45 +++++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/sema.zig b/src/sema.zig index 580825d..a7184e7 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -559,7 +559,11 @@ pub const Analyzer = struct { return self.inferExprType(unop.operand); }, .field_access => |fa| { - const obj_ty = self.inferExprType(fa.object); + var obj_ty = self.inferExprType(fa.object); + // `p.field` where `p` is `*T` resolves on the pointee `T`. + if (obj_ty.isPointer()) { + obj_ty = self.resolveTypeNameStr(obj_ty.pointer_type.pointee_name); + } // `.len` / `.ptr` on the built-in containers (string, slice, array). if (std.mem.eql(u8, fa.field, "len")) { if (obj_ty == .string_type or obj_ty.isSlice() or obj_ty.isArray()) return Type.s(64); @@ -718,19 +722,9 @@ pub const Analyzer = struct { fn analyzeParams(self: *Analyzer, params: []const ast.Param) !void { for (params) |param| { self.resolveTypeRef(param.type_expr); - const param_type = Type.fromTypeExpr(param.type_expr) orelse blk: { - if (param.type_expr.data == .type_expr) { - const name = param.type_expr.data.type_expr.name; - const resolved = self.type_aliases.get(name) orelse name; - if (self.symbol_index.get(resolved)) |indices| { - for (indices.items) |idx| { - if (self.symbols.items[idx].ty) |ty| break :blk ty; - } - } - } - break :blk null; - }; - try self.addSymbol(param.name, .param, param_type, param.name_span); + // `fieldType` (not `fromTypeExpr`) so pointer/slice/array param types + // like `*Board` / `[]Event` resolve instead of becoming null. + try self.addSymbol(param.name, .param, self.fieldType(param.type_expr), param.name_span); } } @@ -1924,6 +1918,29 @@ test "sema: method-return slice + .ptr index + tagged-enum element" { try std.testing.expectEqualStrings("Event", e_ty.?.toName().?); } +test "sema: field access + index through a *Struct param" { + const parser_mod = @import("parser.zig"); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const source = + "Cell :: struct { v: s64; }" ++ + "Grid :: struct { cells: [4]Cell; }" ++ + "look :: (g: *Grid) { c := g.cells[0]; }"; + var parser = parser_mod.Parser.init(alloc, source); + const root = try parser.parse(); + var an = Analyzer.init(alloc); + const res = try an.analyze(root); + + var c_ty: ?Type = null; + for (res.symbols) |sym| { + if (std.mem.eql(u8, sym.name, "c")) c_ty = sym.ty; + } + try std.testing.expect(c_ty != null and c_ty.? == .struct_type); + try std.testing.expectEqualStrings("Cell", c_ty.?.struct_type); +} + test "sema: variable shadowing in same scope is allowed" { const parser_mod = @import("parser.zig");