diff --git a/src/sema.zig b/src/sema.zig index 1f8deb1..3ec86c3 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -162,7 +162,7 @@ pub const Analyzer = struct { var param_types = std.ArrayList(Type).empty; var has_variadic = false; for (fd.params) |param| { - const pt = Type.fromTypeExpr(param.type_expr) orelse Type.unresolved; + const pt = self.fieldType(param.type_expr); if (param.is_variadic) { has_variadic = true; // Variadic param becomes a slice type @@ -200,7 +200,7 @@ pub const Analyzer = struct { const lam = cd.value.data.lambda; var param_types = std.ArrayList(Type).empty; for (lam.params) |param| { - const pt = Type.fromTypeExpr(param.type_expr) orelse Type.unresolved; + const pt = self.fieldType(param.type_expr); try param_types.append(self.allocator, pt); } const ret = if (lam.return_type) |rt| @@ -924,7 +924,7 @@ pub const Analyzer = struct { { var param_types = std.ArrayList(Type).empty; for (fd.params) |param| { - const pt = Type.fromTypeExpr(param.type_expr) orelse Type.unresolved; + const pt = self.fieldType(param.type_expr); try param_types.append(self.allocator, pt); } try self.fn_signatures.put(fd.name, .{ @@ -992,6 +992,31 @@ pub const Analyzer = struct { for (call.args) |arg| { try self.analyzeNode(arg); } + // Mirror lower.zig: passing a `*T` where a `T` value is expected + // (a `for xs: (*m)` capture, a `*T` parameter, any pointer local). + // Restricted to direct (identifier) calls so args align 1:1 with + // the declared params — UFCS/method calls drop the receiver. + if (call.callee.data == .identifier) { + if (self.resolveCalleeName(call)) |callee_name| { + if (self.fn_signatures.get(callee_name)) |sig| { + const n = @min(call.args.len, sig.param_types.len); + var i: usize = 0; + while (i < n) : (i += 1) { + const pt = sig.param_types[i]; + if (pt.isPointer()) continue; + const pt_name = pt.toName() orelse continue; + const at = self.inferExprType(call.args[i]); + if (!at.isPointer()) continue; + if (!std.mem.eql(u8, at.pointer_type.pointee_name, pt_name)) continue; + const msg = if (call.args[i].data == .identifier) + std.fmt.allocPrint(self.allocator, "argument '{s}' has type '*{s}', but '{s}' is expected here; dereference it with `{s}.*`", .{ call.args[i].data.identifier.name, pt_name, pt_name, call.args[i].data.identifier.name }) catch continue + else + std.fmt.allocPrint(self.allocator, "argument has type '*{s}', but '{s}' is expected here; dereference it with `.*`", .{ pt_name, pt_name }) catch continue; + try self.diagnostics.append(self.allocator, .{ .level = .err, .span = call.args[i].span, .message = msg }); + } + } + } + } }, .ffi_intrinsic_call => |fic| { try self.analyzeNode(fic.return_type); @@ -1299,7 +1324,7 @@ pub const Analyzer = struct { fn inferFnReturnType(self: *Analyzer, params: []const ast.Param, body: *const Node) ?Type { self.pushScope() catch return null; for (params) |param| { - const pt = Type.fromTypeExpr(param.type_expr) orelse Type.unresolved; + const pt = self.fieldType(param.type_expr); self.addSymbol(param.name, .param, pt, param.name_span) catch {}; } // Arrow fn_decl wraps body in block{[expr]} — unwrap to inner expression @@ -2121,6 +2146,31 @@ fn offsetIn(source: []const u8, span: ast.Span, needle: []const u8) bool { return span.start >= at and span.start < at + needle.len; } +test "sema: passing *T where a T value is expected is diagnosed" { + const parser_mod = @import("parser.zig"); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const source = + "Move :: struct { flag: s64; }" ++ + "take :: (m: Move) -> s64 { m.flag; }" ++ + "take_ptr :: (m: *Move) -> s64 { m.flag; }" ++ + "bad :: (p: *Move) -> s64 { take(p); }" ++ // *Move into a Move param → flagged + "good :: (p: *Move) -> s64 { take_ptr(p); }"; // *Move into a *Move param → fine + var parser = parser_mod.Parser.init(alloc, source); + const root = try parser.parse(); + var an = Analyzer.init(alloc); + an.source = source; + const res = try an.analyze(root); + + var mismatch_count: usize = 0; + for (res.diagnostics) |d| { + if (std.mem.indexOf(u8, d.message, "expected here") != null) mismatch_count += 1; + } + try std.testing.expectEqual(@as(usize, 1), mismatch_count); +} + test "sema: member references record fields, methods, and enum variants" { const parser_mod = @import("parser.zig"); var arena = std.heap.ArenaAllocator.init(std.testing.allocator);