fix: protocol method calls arity-check (issue 0131)
emitProtocolDispatch now requires the user-arg count to equal the protocol method's parameter list — exact, since protocol signatures have no defaults, packs, or variadics — and emits the same "expects N arguments, but M were given" diagnostic plain calls get. Previously extra args were silently dropped (and missing args left the thunk reading garbage). The dispatch gains the call-site span for the diagnostic. examples/1634 pins the rejection; full sweep confirms no existing code relied on the leniency.
This commit is contained in:
12
examples/1634-protocol-call-arity.sx
Normal file
12
examples/1634-protocol-call-arity.sx
Normal file
@@ -0,0 +1,12 @@
|
||||
// issue 0131 regression: a protocol method call must arity-check like a
|
||||
// plain call. `Allocator.dealloc_bytes` declares (ptr); calling it with
|
||||
// an extra argument used to compile and silently drop the extra.
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> i32 {
|
||||
gpa := GPA.init();
|
||||
a : Allocator = xx gpa;
|
||||
p := a.alloc_bytes(64);
|
||||
a.dealloc_bytes(p, 12345);
|
||||
return 0;
|
||||
}
|
||||
1
examples/expected/1634-protocol-call-arity.exit
Normal file
1
examples/expected/1634-protocol-call-arity.exit
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
5
examples/expected/1634-protocol-call-arity.stderr
Normal file
5
examples/expected/1634-protocol-call-arity.stderr
Normal file
@@ -0,0 +1,5 @@
|
||||
error: 'dealloc_bytes' expects 1 argument, but 2 were given
|
||||
--> examples/1634-protocol-call-arity.sx:10:5
|
||||
|
|
||||
10 | a.dealloc_bytes(p, 12345);
|
||||
| ^^^^^^^^^^^^^^^
|
||||
0
examples/expected/1634-protocol-call-arity.stdout
Normal file
0
examples/expected/1634-protocol-call-arity.stdout
Normal file
@@ -861,7 +861,7 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
// protocol value as its first param).
|
||||
if (self.getProtocolInfo(obj_ty)) |proto_info| {
|
||||
if (protocolHasMethod(proto_info, fa.field)) {
|
||||
return self.emitProtocolDispatch(obj, proto_info, fa.field, args.items, obj_ty);
|
||||
return self.emitProtocolDispatch(obj, proto_info, fa.field, args.items, obj_ty, c.callee.span);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -878,7 +878,7 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
const pay_ty = opt_info.optional.child;
|
||||
if (self.getProtocolInfo(pay_ty)) |proto_info| {
|
||||
if (protocolHasMethod(proto_info, fa.field)) {
|
||||
return self.emitProtocolDispatch(obj, proto_info, fa.field, args.items, pay_ty);
|
||||
return self.emitProtocolDispatch(obj, proto_info, fa.field, args.items, pay_ty, c.callee.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,7 +498,7 @@ pub fn buildProtocolValue(self: *Lowering, concrete_ptr: Ref, proto_name: []cons
|
||||
|
||||
/// Emit protocol method dispatch for a protocol-typed receiver.
|
||||
/// Returns the call result ref.
|
||||
pub fn emitProtocolDispatch(self: *Lowering, receiver: Ref, proto_info: ProtocolDeclInfo, method_name: []const u8, args: []const Ref, proto_ty: TypeId) Ref {
|
||||
pub fn emitProtocolDispatch(self: *Lowering, receiver: Ref, proto_info: ProtocolDeclInfo, method_name: []const u8, args: []const Ref, proto_ty: TypeId, span: ast.Span) Ref {
|
||||
// Find method index
|
||||
var method_idx: ?usize = null;
|
||||
var method_info: ?ProtocolMethodInfo = null;
|
||||
@@ -512,6 +512,19 @@ pub fn emitProtocolDispatch(self: *Lowering, receiver: Ref, proto_info: Protocol
|
||||
const mi = method_info orelse return self.emitError(method_name, null);
|
||||
const midx = method_idx orelse 0;
|
||||
|
||||
// Arity is exact: a protocol signature has no defaults, packs, or
|
||||
// variadics, so the user-arg count must equal its parameter list
|
||||
// (issue 0131: extra args were silently dropped here; missing args
|
||||
// left the thunk reading garbage).
|
||||
if (args.len != mi.param_types.len) {
|
||||
if (self.diagnostics) |d| {
|
||||
const s: []const u8 = if (mi.param_types.len == 1) "" else "s";
|
||||
const got_verb: []const u8 = if (args.len == 1) "was" else "were";
|
||||
d.addFmt(.err, span, "'{s}' expects {d} argument{s}, but {d} {s} given", .{ method_name, mi.param_types.len, s, args.len, got_verb });
|
||||
}
|
||||
return Ref.none;
|
||||
}
|
||||
|
||||
// Extract ctx from protocol struct (field 0)
|
||||
const void_ptr = self.module.types.ptrTo(.void);
|
||||
const ctx = self.builder.structGet(receiver, 0, void_ptr);
|
||||
|
||||
Reference in New Issue
Block a user