fix(0123): wrong arg counts to fixed-arity fns error at the call site
checkCallArity compares the supplied count against the declared params (min = params without trailing defaults, max = params.len, unbounded past a variadic) at the five plain dispatch sites in lowerCall — bare selected-author + lazy, namespace alias-gate + qualified, struct method, ufcs. Pack / comptime / generic / #compiler / #builtin callees keep their own dispatch. The method/ufcs sites also gain the appendDefaultArgs fill the generic-instance leg already had, so trailing defaults work on dot-calls instead of emitting under-arity calls. lowerStmt's local fn_decl arm now registers a pointer into the AST node in fn_ast_map, not a stack temporary that aliased every later local fn.
This commit is contained in:
@@ -1800,6 +1800,7 @@ pub const Lowering = struct {
|
||||
pub const reflectionTypeArgGuard = lower_call.reflectionTypeArgGuard;
|
||||
pub const reflectionErrorSentinel = lower_call.reflectionErrorSentinel;
|
||||
pub const appendDefaultArgs = lower_call.appendDefaultArgs;
|
||||
pub const checkCallArity = lower_call.checkCallArity;
|
||||
pub const expandCallDefaults = lower_call.expandCallDefaults;
|
||||
pub const userParamTypes = lower_call.userParamTypes;
|
||||
pub const resolveCallParamTypes = lower_call.resolveCallParamTypes;
|
||||
|
||||
@@ -461,6 +461,15 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
d.addFmt(.err, c.callee.span, "'{s}' is ambiguous; declared by multiple imported modules — qualify the call", .{func_name});
|
||||
return Ref.none;
|
||||
}
|
||||
// `args.items` is the post-expansion count: trailing defaults
|
||||
// were filled by `expandCallDefaults`, comptime-pack spreads
|
||||
// expanded element-wise above.
|
||||
{
|
||||
const arity_fd: ?*const ast.FnDecl = if (sel_author) |sf| sf.decl else self.program_index.fn_ast_map.get(func_name);
|
||||
if (arity_fd) |fd| {
|
||||
if (self.checkCallArity(fd, id.name, args.items.len, false, c.callee.span)) return Ref.none;
|
||||
}
|
||||
}
|
||||
if (sel_author) |sf| {
|
||||
const fid = self.selectedFuncId(sf, func_name);
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
@@ -722,6 +731,7 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
}
|
||||
if (hasComptimeParams(fd)) return self.lowerComptimeCall(fd, c);
|
||||
if (fd.type_params.len > 0) return self.lowerGenericCall(fd, fa.field, c, args.items);
|
||||
if (self.checkCallArity(fd, fa.field, args.items.len, false, c.callee.span)) return Ref.none;
|
||||
var sf = SelectedFunc{ .decl = fd, .source = target.target_module_path };
|
||||
const fid = self.selectedFuncId(&sf, fa.field);
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
@@ -763,6 +773,7 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
const ret_ty = func.ret;
|
||||
const params = func.params;
|
||||
if (self.program_index.fn_ast_map.get(effective_name)) |fd| {
|
||||
if (self.checkCallArity(fd, effective_name, args.items.len, false, c.callee.span)) return Ref.none;
|
||||
self.packVariadicCallArgs(fd, c, &args);
|
||||
}
|
||||
const final_args = self.prependCtxIfNeeded(func, args.items);
|
||||
@@ -973,11 +984,12 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
}
|
||||
|
||||
// Try non-generic qualified method
|
||||
if (self.program_index.fn_ast_map.get(qualified)) |fd| {
|
||||
const plain_method_fd = self.program_index.fn_ast_map.get(qualified);
|
||||
if (plain_method_fd) |fd| {
|
||||
if (self.checkCallArity(fd, qualified, method_args.items.len, true, c.callee.span)) return Ref.none;
|
||||
if (!self.lowered_functions.contains(qualified)) {
|
||||
self.lazyLowerFunction(qualified);
|
||||
}
|
||||
_ = fd;
|
||||
}
|
||||
if (self.resolveFuncByName(qualified)) |fid| {
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
@@ -985,6 +997,7 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
const params = func.params;
|
||||
const has_ctx = func.has_implicit_ctx;
|
||||
self.fixupMethodReceiver(&method_args, func, effective_obj_node, obj_ty);
|
||||
if (plain_method_fd) |fd| self.appendDefaultArgs(fd, &method_args);
|
||||
// Note: coerceCallArgs can trigger protocol thunk creation
|
||||
// (module.addFunction), invalidating func pointer.
|
||||
// Use pre-extracted params/ret_ty (+ has_ctx) instead of
|
||||
@@ -1073,6 +1086,10 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
return self.emitError(eff_field, c.callee.span);
|
||||
}
|
||||
}
|
||||
const ufcs_arity_fd: ?*const ast.FnDecl = if (sel_author) |sf| sf.decl else ufcs_fd;
|
||||
if (ufcs_arity_fd) |fd| {
|
||||
if (self.checkCallArity(fd, fa.field, method_args.items.len, true, c.callee.span)) return Ref.none;
|
||||
}
|
||||
const ufcs_fid: ?FuncId = blk_uf: {
|
||||
if (sel_author) |sf| {
|
||||
break :blk_uf self.selectedFuncId(sf, eff_field);
|
||||
@@ -1092,6 +1109,7 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
// free function's first param is `*T` and the receiver is a
|
||||
// value `T`, pass its address instead of a by-value copy
|
||||
self.fixupMethodReceiver(&method_args, func, effective_obj_node, obj_ty);
|
||||
if (ufcs_arity_fd) |fd| self.appendDefaultArgs(fd, &method_args);
|
||||
const final_args = self.prependCtxIfNeeded(func, method_args.items);
|
||||
self.coerceCallArgs(final_args, params);
|
||||
return self.builder.call(fid, final_args, ret_ty);
|
||||
@@ -1969,6 +1987,50 @@ pub fn appendDefaultArgs(self: *Lowering, fd: *const ast.FnDecl, args: *std.Arra
|
||||
}
|
||||
}
|
||||
|
||||
/// Reject a direct call whose argument count cannot bind to the callee's
|
||||
/// declared parameter list. `supplied` counts the args as they bind to
|
||||
/// params — receiver included for dot-dispatch, defaults not yet
|
||||
/// appended. Returns true when a diagnostic was emitted (the call must
|
||||
/// not lower). Pack / comptime / generic / `#compiler` / `#builtin`
|
||||
/// callees bind args through their own dispatch and are exempt.
|
||||
pub fn checkCallArity(self: *Lowering, fd: *const ast.FnDecl, callee_name: []const u8, supplied: usize, has_receiver: bool, span: ast.Span) bool {
|
||||
|
||||
if (fd.type_params.len > 0 or hasComptimeParams(fd) or isPackFn(fd)) return false;
|
||||
switch (fd.body.data) {
|
||||
.compiler_expr, .builtin_expr => return false,
|
||||
else => {},
|
||||
}
|
||||
var min: usize = 0;
|
||||
var max: ?usize = fd.params.len;
|
||||
for (fd.params, 0..) |p, i| {
|
||||
if (p.is_variadic) {
|
||||
max = null;
|
||||
break;
|
||||
}
|
||||
if (p.default_expr == null) min = i + 1;
|
||||
}
|
||||
if (supplied >= min and (max == null or supplied <= max.?)) return false;
|
||||
if (self.diagnostics) |d| {
|
||||
// Dot-dispatch report counts the user-visible args: the receiver
|
||||
// slot is implicit at the call site, so it is elided from both
|
||||
// the expected and the supplied counts.
|
||||
const recv: usize = @intFromBool(has_receiver);
|
||||
const got = supplied -| recv;
|
||||
const lo = min -| recv;
|
||||
const got_verb: []const u8 = if (got == 1) "was" else "were";
|
||||
if (max == null) {
|
||||
const s: []const u8 = if (lo == 1) "" else "s";
|
||||
d.addFmt(.err, span, "'{s}' expects at least {d} argument{s}, but {d} {s} given", .{ callee_name, lo, s, got, got_verb });
|
||||
} else if (max.? -| recv == lo) {
|
||||
const s: []const u8 = if (lo == 1) "" else "s";
|
||||
d.addFmt(.err, span, "'{s}' expects {d} argument{s}, but {d} {s} given", .{ callee_name, lo, s, got, got_verb });
|
||||
} else {
|
||||
d.addFmt(.err, span, "'{s}' expects between {d} and {d} arguments, but {d} {s} given", .{ callee_name, lo, max.? -| recv, got, got_verb });
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// When a bare-identifier call omits trailing positional args and the
|
||||
/// callee's signature provides defaults for them, return a fresh Call
|
||||
/// node with the defaults filled in. Returns null when no expansion is
|
||||
|
||||
@@ -179,7 +179,10 @@ pub fn lowerStmt(self: *Lowering, node: *const Node) void {
|
||||
switch (node.data) {
|
||||
.var_decl => |vd| self.lowerVarDecl(&vd),
|
||||
.const_decl => |cd| self.lowerConstDecl(&cd),
|
||||
.fn_decl => |fd| self.lowerLocalFnDecl(&fd),
|
||||
// Pointer capture, not by-value: `lowerLocalFnDecl` registers the
|
||||
// decl pointer in `fn_ast_map`, so it must point into the AST node,
|
||||
// not at a stack temporary that the next statement reuses.
|
||||
.fn_decl => |*fd| self.lowerLocalFnDecl(fd),
|
||||
.return_stmt => |rs| self.lowerReturn(&rs),
|
||||
.raise_stmt => |rs| self.lowerRaise(&rs, node.span),
|
||||
.assignment => |asgn| self.lowerAssignment(&asgn),
|
||||
|
||||
Reference in New Issue
Block a user