lsp/sema: resolve pointer-typed params and field access through pointers

Two gaps made 'piece := board.squares[move.from.index]' (board: *Board) <unresolved>: 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.
This commit is contained in:
agra
2026-05-31 11:22:24 +03:00
parent 5c9d8c23ca
commit ef0d9a9477

View File

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