From 39d51fc26d236a2138bafda1e53a0aec21972d7e Mon Sep 17 00:00:00 2001 From: agra Date: Sun, 31 May 2026 13:56:45 +0300 Subject: [PATCH] lower: diagnose passing a by-ref loop capture where a value is expected `for xs: (*m)` binds `m` to a `*T`. Passing it directly to a parameter that wants `T` produced invalid IR that only LLVM's verifier caught, with the opaque 'Call parameter type does not match function signature'. Detect it at the call site and emit a clear error with a fix-it suggesting `m.*`. Add example 215 + expected output as a regression test. --- .../215-ref-capture-value-arg-diagnostic.sx | 18 ++++++++++++++++++ src/ir/lower.zig | 17 +++++++++++++++++ .../215-ref-capture-value-arg-diagnostic.exit | 1 + .../215-ref-capture-value-arg-diagnostic.txt | 10 ++++++++++ 4 files changed, 46 insertions(+) create mode 100644 examples/215-ref-capture-value-arg-diagnostic.sx create mode 100644 tests/expected/215-ref-capture-value-arg-diagnostic.exit create mode 100644 tests/expected/215-ref-capture-value-arg-diagnostic.txt diff --git a/examples/215-ref-capture-value-arg-diagnostic.sx b/examples/215-ref-capture-value-arg-diagnostic.sx new file mode 100644 index 0000000..3ffd40f --- /dev/null +++ b/examples/215-ref-capture-value-arg-diagnostic.sx @@ -0,0 +1,18 @@ +// 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 to the +// LLVM verifier ("Call parameter type does not match function signature"). +// The compiler now reports it at the call site with a fix-it: write `m.*`. + +#import "modules/std.sx"; + +Move :: struct { flag: s64; } + +take :: (m: Move) -> s64 { return m.flag; } + +main :: () -> s32 { + moves : [2]Move = .[ Move.{ flag = 1 }, Move.{ flag = 2 } ]; + for moves: (*m) { + take(m); + } + return 0; +} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 7f89f09..99e54a4 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -6569,6 +6569,23 @@ 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 nm = arg.data.identifier.name; + const tn = self.formatTypeName(pointee); + 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 }); + d.addHelpFmt(pid, arg.span, fix, "dereference it to pass the value: `{s}`", .{fix}); + } + } + } + } const val = self.lowerExpr(arg); self.target_type = saved_target; args.append(self.alloc, val) catch unreachable; diff --git a/tests/expected/215-ref-capture-value-arg-diagnostic.exit b/tests/expected/215-ref-capture-value-arg-diagnostic.exit new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/tests/expected/215-ref-capture-value-arg-diagnostic.exit @@ -0,0 +1 @@ +1 diff --git a/tests/expected/215-ref-capture-value-arg-diagnostic.txt b/tests/expected/215-ref-capture-value-arg-diagnostic.txt new file mode 100644 index 0000000..6aa3ad8 --- /dev/null +++ b/tests/expected/215-ref-capture-value-arg-diagnostic.txt @@ -0,0 +1,10 @@ +error: by-reference loop capture 'm' has type '*Move', but 'Move' is expected here + --> /Users/agra/projects/sx/examples/215-ref-capture-value-arg-diagnostic.sx:15:14 + | +15 | take(m); + | ^ + +help: dereference it to pass the value: `m.*` + | +15 | m.* + | ^