lsp/sema: resolve method-return types, slice .ptr/.len, tagged enums
ev := events.ptr[i] (events := g_plat.poll_events()) was <unresolved> through three gaps: 1. Return types went through Type.fromTypeExpr, which only handles a bare type_expr — so any []T / *T / List(T) return became void. An impl method 'poll_events -> []Event' registered as void and, merged after the protocol's correct signature, clobbered it. resolveReturnType now uses fieldType. 2. Struct/protocol methods were never put in fn_signatures, so recv.method() and Type.static() return types never resolved. registerMethodSig now adds them by bare name (first-wins), which is what resolveCalleeName already assumed. 3. .ptr/.len field access was string-only (and string.ptr wrongly returned string_type); now handles slices/arrays and returns the proper many-pointer element. 4. Tagged enums (payload variants) were only a symbol, never in a lookup registry; now also recorded in enum_types so the name resolves as a type. Net: events -> []Event, events.ptr -> [*]Event, ev -> Event. Regression test added; confirmed end-to-end via the LSP inlay hint.
This commit is contained in:
77
src/sema.zig
77
src/sema.zig
@@ -130,7 +130,7 @@ pub const Analyzer = struct {
|
||||
fn registerTopLevelDeclPrefixed(self: *Analyzer, node: *Node, ns_prefix: ?[]const u8) !void {
|
||||
switch (node.data) {
|
||||
.fn_decl => |fd| {
|
||||
const ret_ty = resolveReturnType(fd) orelse
|
||||
const ret_ty = self.resolveReturnType(fd) orelse
|
||||
if (fd.is_arrow) self.inferFnReturnType(fd.params, fd.body) else null;
|
||||
try self.addSymbol(fd.name, .function, ret_ty, node.span);
|
||||
// Populate fn_signatures registry
|
||||
@@ -198,8 +198,10 @@ pub const Analyzer = struct {
|
||||
},
|
||||
.enum_decl => |ed| {
|
||||
if (ed.variant_types.len > 0) {
|
||||
// Tagged enum with payloads
|
||||
// Tagged enum with payloads. Also recorded in `enum_types` so
|
||||
// the name resolves as a type (e.g. a `[*]Event` element).
|
||||
try self.addSymbol(ed.name, .enum_type, .{ .union_type = ed.name }, node.span);
|
||||
try self.enum_types.put(ed.name, ed.variant_names);
|
||||
} else {
|
||||
// Payload-less enum
|
||||
try self.addSymbol(ed.name, .enum_type, .{ .enum_type = ed.name }, node.span);
|
||||
@@ -254,6 +256,15 @@ pub const Analyzer = struct {
|
||||
.type_params = tp_slice,
|
||||
});
|
||||
}
|
||||
for (sd.methods) |mnode| {
|
||||
if (mnode.data == .fn_decl)
|
||||
try self.registerMethodSig(mnode.data.fn_decl.name, ns_prefix, mnode.data.fn_decl.return_type);
|
||||
}
|
||||
},
|
||||
.protocol_decl => |pd| {
|
||||
for (pd.methods) |m| {
|
||||
try self.registerMethodSig(m.name, ns_prefix, m.return_type);
|
||||
}
|
||||
},
|
||||
.union_decl => |ud| {
|
||||
try self.addSymbol(ud.name, .enum_type, .{ .union_type = ud.name }, node.span);
|
||||
@@ -275,6 +286,19 @@ pub const Analyzer = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a method's return type in `fn_signatures` under its bare name
|
||||
/// (first-wins) so `recv.method()` call inference can resolve it. Params
|
||||
/// are omitted — only the return type is consulted for type inference.
|
||||
fn registerMethodSig(self: *Analyzer, name: []const u8, ns_prefix: ?[]const u8, ret_node: ?*Node) !void {
|
||||
const key = if (ns_prefix) |pfx|
|
||||
try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ pfx, name })
|
||||
else
|
||||
name;
|
||||
if (self.fn_signatures.contains(key)) return;
|
||||
const ret = if (ret_node) |rt| self.fieldType(rt) else Type.void_type;
|
||||
try self.fn_signatures.put(key, .{ .param_types = &.{}, .return_type = ret });
|
||||
}
|
||||
|
||||
/// Resolve a type annotation node to a Type.
|
||||
/// Handles primitives, type_expr, array_type_expr, parameterized_type_expr,
|
||||
/// type aliases, enum types, and struct types.
|
||||
@@ -536,9 +560,14 @@ pub const Analyzer = struct {
|
||||
},
|
||||
.field_access => |fa| {
|
||||
const obj_ty = self.inferExprType(fa.object);
|
||||
if (obj_ty == .string_type) {
|
||||
if (std.mem.eql(u8, fa.field, "len")) return Type.s(64);
|
||||
if (std.mem.eql(u8, fa.field, "ptr")) return .string_type;
|
||||
// `.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);
|
||||
}
|
||||
if (std.mem.eql(u8, fa.field, "ptr")) {
|
||||
if (obj_ty == .string_type) return .{ .many_pointer_type = .{ .element_name = "u8" } };
|
||||
if (obj_ty.isSlice()) return .{ .many_pointer_type = .{ .element_name = obj_ty.slice_type.element_name } };
|
||||
if (obj_ty.isArray()) return .{ .many_pointer_type = .{ .element_name = obj_ty.array_type.element_name } };
|
||||
}
|
||||
if (obj_ty.isStruct()) {
|
||||
if (self.struct_types.get(obj_ty.struct_type)) |info| {
|
||||
@@ -797,7 +826,7 @@ pub const Analyzer = struct {
|
||||
fn analyzeNode(self: *Analyzer, node: *Node) !void {
|
||||
switch (node.data) {
|
||||
.fn_decl => |fd| {
|
||||
const local_ret_ty = resolveReturnType(fd) orelse
|
||||
const local_ret_ty = self.resolveReturnType(fd) orelse
|
||||
if (fd.is_arrow) self.inferFnReturnType(fd.params, fd.body) else null;
|
||||
try self.addSymbol(fd.name, .function, local_ret_ty, node.span);
|
||||
// Register fn_signatures for local functions (for return type hints + hover)
|
||||
@@ -1142,9 +1171,12 @@ pub const Analyzer = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn resolveReturnType(fd: ast.FnDecl) ?Type {
|
||||
fn resolveReturnType(self: *Analyzer, fd: ast.FnDecl) ?Type {
|
||||
if (fd.return_type) |rt| {
|
||||
return Type.fromTypeExpr(rt);
|
||||
// `fieldType`, not `Type.fromTypeExpr` — the latter only handles a
|
||||
// bare `type_expr`, so a `[]T` / `*T` / `[*]T` return silently
|
||||
// became void and clobbered correct signatures on merge.
|
||||
return self.fieldType(rt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1863,6 +1895,35 @@ test "sema: generic index resolves with realistic List/Move (methods, cross-refs
|
||||
try std.testing.expectEqualStrings("Square", f_ty.?.struct_type);
|
||||
}
|
||||
|
||||
test "sema: method-return slice + .ptr index + tagged-enum element" {
|
||||
const parser_mod = @import("parser.zig");
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const source =
|
||||
"Event :: enum { none; click: s64; }" ++
|
||||
"Plat :: protocol { poll :: () -> []Event; }" ++
|
||||
"go :: (p: *Plat) { evs := p.poll(); e := evs.ptr[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 evs_ty: ?Type = null;
|
||||
var e_ty: ?Type = null;
|
||||
for (res.symbols) |sym| {
|
||||
if (std.mem.eql(u8, sym.name, "evs")) evs_ty = sym.ty;
|
||||
if (std.mem.eql(u8, sym.name, "e")) e_ty = sym.ty;
|
||||
}
|
||||
// `p.poll()` resolves to its slice return type, not void.
|
||||
try std.testing.expect(evs_ty != null and evs_ty.? == .slice_type);
|
||||
try std.testing.expectEqualStrings("Event", evs_ty.?.slice_type.element_name);
|
||||
// `evs.ptr[0]` resolves to the (tagged-enum) element type.
|
||||
try std.testing.expect(e_ty != null);
|
||||
try std.testing.expectEqualStrings("Event", e_ty.?.toName().?);
|
||||
}
|
||||
|
||||
test "sema: variable shadowing in same scope is allowed" {
|
||||
const parser_mod = @import("parser.zig");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user