diff --git a/src/sema.zig b/src/sema.zig index d419b60..580825d 100644 --- a/src/sema.zig +++ b/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");