From 14d1d9d3a82aab3693e9a3c1d54228fd8d3af091 Mon Sep 17 00:00:00 2001 From: agra Date: Sun, 31 May 2026 14:17:25 +0300 Subject: [PATCH] lower: generalise the *T-where-T-expected diagnostic to any pointer The check only caught `for xs: (*m)` loop captures; passing a `*T` parameter or any pointer local where `T` is expected still slipped through to the LLVM verifier. Key the diagnostic on the lowered argument's type instead of the capture, so a `*Move` parameter forwarded into a by-value parameter is reported the same way. Ref-capture wording is preserved. Add example 216 (pointer-parameter case) alongside 215 (loop capture). --- .../216-pointer-param-value-arg-diagnostic.sx | 18 +++++++++++ src/ir/lower.zig | 31 ++++++++++++------- ...16-pointer-param-value-arg-diagnostic.exit | 1 + ...216-pointer-param-value-arg-diagnostic.txt | 10 ++++++ 4 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 examples/216-pointer-param-value-arg-diagnostic.sx create mode 100644 tests/expected/216-pointer-param-value-arg-diagnostic.exit create mode 100644 tests/expected/216-pointer-param-value-arg-diagnostic.txt diff --git a/examples/216-pointer-param-value-arg-diagnostic.sx b/examples/216-pointer-param-value-arg-diagnostic.sx new file mode 100644 index 0000000..8acb681 --- /dev/null +++ b/examples/216-pointer-param-value-arg-diagnostic.sx @@ -0,0 +1,18 @@ +// Passing a `*T` where a `T` value is expected is caught at the call site — +// not only for `for xs: (*m)` loop captures (see 215) but for any pointer, +// here a `*Move` parameter forwarded into a by-value parameter. Without the +// check this slipped through to the LLVM verifier as "Call parameter type +// does not match function signature". + +#import "modules/std.sx"; + +Move :: struct { flag: s64; } + +take :: (m: Move) -> s64 { return m.flag; } + +forward :: (m: *Move) -> s64 { return take(m); } + +main :: () -> s32 { + mv : Move = .{ flag = 7 }; + return xx forward(@mv); +} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 99e54a4..24a74dc 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -6569,25 +6569,32 @@ pub const Lowering = struct { } } } - // A by-reference loop capture (`for xs: (*m)`) binds `m` to a `*T`. - // Passing it where a `T` value is expected used to slip through as a - // type-mismatched call that only LLVM's verifier rejected; report it - // here with a fix-it instead. - if (ai < param_types.len and arg.data == .identifier) { - if (self.refCapturePointee(arg)) |pointee| { - if (pointee == param_types[ai]) { - if (self.diagnostics) |d| { + const val = self.lowerExpr(arg); + self.target_type = saved_target; + // Passing a `*T` where a `T` value is expected — a by-reference loop + // capture (`for xs: (*m)`), a `*T` parameter, or any pointer local — + // otherwise slips through to LLVM as an opaque "call parameter type + // does not match function signature" verifier error. Flag it at the + // call site with a `.*` fix-it. + if (ai < param_types.len) { + const vt = self.builder.getRefType(val); + const vti = self.module.types.get(vt); + if (vti == .pointer and vti.pointer.pointee == param_types[ai]) { + if (self.diagnostics) |d| { + const tn = self.formatTypeName(param_types[ai]); + if (arg.data == .identifier) { const nm = arg.data.identifier.name; - const tn = self.formatTypeName(pointee); + const lead: []const u8 = if (self.refCapturePointee(arg) != null) "by-reference loop capture" else "argument"; const fix = std.fmt.allocPrint(self.alloc, "{s}.*", .{nm}) catch nm; - const pid = d.addFmtId(.err, arg.span, "by-reference loop capture '{s}' has type '*{s}', but '{s}' is expected here", .{ nm, tn, tn }); + const pid = d.addFmtId(.err, arg.span, "{s} '{s}' has type '*{s}', but '{s}' is expected here", .{ lead, nm, tn, tn }); d.addHelpFmt(pid, arg.span, fix, "dereference it to pass the value: `{s}`", .{fix}); + } else { + const pid = d.addFmtId(.err, arg.span, "this argument has type '*{s}', but '{s}' is expected here", .{ tn, tn }); + d.addHelpFmt(pid, arg.span, null, "dereference it with `.*` to pass the value", .{}); } } } } - const val = self.lowerExpr(arg); - self.target_type = saved_target; args.append(self.alloc, val) catch unreachable; } diff --git a/tests/expected/216-pointer-param-value-arg-diagnostic.exit b/tests/expected/216-pointer-param-value-arg-diagnostic.exit new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/tests/expected/216-pointer-param-value-arg-diagnostic.exit @@ -0,0 +1 @@ +1 diff --git a/tests/expected/216-pointer-param-value-arg-diagnostic.txt b/tests/expected/216-pointer-param-value-arg-diagnostic.txt new file mode 100644 index 0000000..bdd10df --- /dev/null +++ b/tests/expected/216-pointer-param-value-arg-diagnostic.txt @@ -0,0 +1,10 @@ +error: argument 'm' has type '*Move', but 'Move' is expected here + --> /Users/agra/projects/sx/examples/216-pointer-param-value-arg-diagnostic.sx:13:44 + | +13 | forward :: (m: *Move) -> s64 { return take(m); } + | ^ + +help: dereference it to pass the value: `m.*` + | +13 | m.* + | ^