c3c icon indicating copy to clipboard operation
c3c copied to clipboard

[Bug] Wrong error message with normal args behind varargs

Open BWindey opened this issue 4 weeks ago • 5 comments

When creating a macro or function (same behaviour) with varargs and then a normal argument, the compiler gives an error from where the function is called, instead of on the declaration of the function.

Example:

import std::io;

macro void print(int... nums, String test) { }

fn void main() {
	print(1, 2, 3, "numbers");
}

Error message:

 3: fn void print(int... nums, String test) { }
 4: 
 5: fn void main() {
 6:     print(1, 2, 3, "numbers");
                       ^^^^^^^^^
Error: You cannot cast 'String' to 'int'.

This seems not the expected behaviour.

It even gets stranger when the types do match:

import std::io;

fn void print(int... nums, int num) { }

fn void main() {
	print(1, 2, 3);
}

Error message:

 3: fn void print(int... nums, int num) { }
 4: 
 5: fn void main() {
 6:     print(1, 2, 3);
                     ^
Error: Expected 'num: ...' after this argument.

1: import std::io;
2: 
3: fn void print(int... nums, int num) { }
           ^^^^^
Note: The definition was here.

BWindey avatar Nov 26 '25 20:11 BWindey

This is actually correct!

lerno avatar Nov 26 '25 21:11 lerno

So you imply that a function with those arguments is correct? Why? And how would you call it?

BWindey avatar Nov 26 '25 23:11 BWindey

You always need to call those trailing arguments with a named parameter. This is because otherwise default parameters would not make sense:

fn void print(int... nums, int num = 1) { }

In the above, given print(1, 2, 3) how would you know what would be assigned to num? So the rule is that any parameters after a vaarg must be named.

We can consider a better example. Let's say we want this:

fn void do_something(int x, Context context = default_context) { ... }

As long as we have non-vaargs, this is straightforward: do_something(1) or do_something(1, the_context). However, if we want vaargs it becomes a problem:

fn void do_something2(int... y, Context context = default_context) { ... }

So in order to make all uses consistent, we require that named parameter. We then have:

do_something(1);
do_something2(1, 2, 3);
do_something(4, context: my_context);
do_something2(1, 2, 3, 4, 5, context: my_context);
// There is do_something(4, my_context); available
// but the programmer could avoid that for consistency

lerno avatar Nov 26 '25 23:11 lerno

Ok, that makes sense. I'm not convinced that the shown error message is the best one possible though. Can it hint at using named arguments?

BWindey avatar Nov 27 '25 07:11 BWindey

What heuristic should be used?

lerno avatar Nov 27 '25 11:11 lerno

What heuristic should be used?

Maybe if after varargs exist any args then add to error message: "Perhabs, patameter name after ... was not mentioned?" (or something like that).

EroMrinin134 avatar Dec 17 '25 15:12 EroMrinin134