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.* + | ^