Skip to content

Commit

Permalink
Improve macro call arguments mismatch errors
Browse files Browse the repository at this point in the history
  • Loading branch information
GuillaumeGomez committed Jan 28, 2025
1 parent e81ca42 commit 5a7213e
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 28 deletions.
83 changes: 62 additions & 21 deletions rinja_derive/src/generator/node.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::borrow::Cow;
use std::collections::HashSet;
use std::collections::hash_map::{Entry, HashMap};
use std::fmt::Write;
use std::mem;
Expand Down Expand Up @@ -1421,33 +1422,73 @@ fn macro_call_ensure_arg_count(
def: &Macro<'_>,
ctx: &Context<'_>,
) -> Result<(), CompileError> {
if call.args.len() == def.args.len() {
// exactly enough arguments were provided
return Ok(());
if call.args.len() > def.args.len() {
return Err(ctx.generate_error(
format_args!(
"macro `{}` expected {} argument{}, found {}",
def.name,
def.args.len(),
if def.args.len() > 1 { "s" } else { "" },
call.args.len(),
),
call.span(),
));
}

let nb_default_args = def
.args
.iter()
.rev()
.take_while(|(_, default_value)| default_value.is_some())
.count();
if call.args.len() < def.args.len() && call.args.len() >= def.args.len() - nb_default_args {
// all missing arguments have a default value, and there weren't too many args
return Ok(());
// First we list of arguments position then we remove them one by one.
let mut args = (0..def.args.len()).collect::<HashSet<_>>();
for (pos, arg) in call.args.iter().enumerate() {
let pos = match **arg {
Expr::NamedArgument(name, ..) => {
def.args.iter().position(|(arg_name, _)| *arg_name == name)
}
_ => Some(pos),
};
if let Some(pos) = pos {
if !args.remove(&pos) {
// This argument was already passed, so error.
return Err(ctx.generate_error(
format_args!(
"argument `{}` is passed more than once when calling macro `{}`",
def.args[pos].0, def.name,
),
call.span(),
));
}
}
}

// either too many or not enough arguments were provided
let (expected_args, extra) = match nb_default_args {
0 => (def.args.len(), ""),
_ => (nb_default_args, "at least "),
// Now we need to filter out arguments with default value.
let args = args
.into_iter()
.filter(|pos| def.args[*pos].1.is_none())
.collect::<Vec<_>>();
let (extra, error) = match args.len() {
0 => return Ok(()),
1 => ("", format!("`{}`", def.args[0].0)),
2 => ("s", format!("`{}` and `{}`", def.args[0].0, def.args[1].0)),
_ => {
let mut error_s =
args.iter()
.take(args.len() - 2)
.fold(String::new(), |mut acc, arg| {
if !acc.is_empty() {
acc.push_str(", ");
}
acc.push_str(&format!("`{}`", def.args[*arg].0));
acc
});
error_s.push_str(&format!(
" and `{}`",
def.args[*args.last().expect("no last args")].0
));
("s", error_s)
}
};

Err(ctx.generate_error(
format_args!(
"macro {:?} expected {extra}{expected_args} argument{}, found {}",
def.name,
if expected_args != 1 { "s" } else { "" },
call.args.len(),
"missing argument{extra} when calling macro `{}`: {error}",
def.name
),
call.span(),
))
Expand Down
22 changes: 22 additions & 0 deletions testing/tests/ui/macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,27 @@ struct NoClosingParen3;
#[template(source = "{% macro thrice(a, b, c = %}{% endmacro %}", ext = "html")]
struct NoClosingParen4;

#[derive(rinja::Template)]
#[template(
source = r#"
{% macro example(name, value, current, label="", id="") %}
{% endmacro %}
{% call example(name="name", value="") %}
"#,
ext = "txt"
)]
struct WrongNumberOfParams;

#[derive(rinja::Template)]
#[template(
source = r#"
{% macro example(name, value, arg=12) %}
{% endmacro %}
{% call example(0, name="name", value="") %}
"#,
ext = "txt"
)]
struct DuplicatedArg;

fn main() {
}
32 changes: 29 additions & 3 deletions testing/tests/ui/macro.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: macro "thrice" expected 1 argument, found 2
error: macro `thrice` expected 1 argument, found 2
--> InvalidNumberOfArgs.html:5:2
"- call thrice(2, 3) -%}"
--> tests/ui/macro.rs:4:21
Expand All @@ -11,7 +11,7 @@ error: macro "thrice" expected 1 argument, found 2
8 | | {%- call thrice(2, 3) -%}", ext = "html")]
| |__________________________^

error: macro "thrice" expected 2 arguments, found 0
error: missing arguments when calling macro `thrice`: `param` and `param2`
--> InvalidNumberOfArgs2.html:5:2
"- call thrice() -%}"
--> tests/ui/macro.rs:12:21
Expand All @@ -24,7 +24,7 @@ error: macro "thrice" expected 2 arguments, found 0
16 | | {%- call thrice() -%}", ext = "html")]
| |______________________^

error: macro "thrice" expected 0 arguments, found 2
error: macro `thrice` expected 0 argument, found 2
--> InvalidNumberOfArgs3.html:4:2
"- call thrice(1, 2) -%}"
--> tests/ui/macro.rs:20:21
Expand Down Expand Up @@ -67,3 +67,29 @@ error: expected `)` to close macro argument list
|
39 | #[template(source = "{% macro thrice(a, b, c = %}{% endmacro %}", ext = "html")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: missing argument when calling macro `example`: `name`
--> WrongNumberOfParams.txt:4:10
" call example(name=\"name\", value=\"\") %}\n "
--> tests/ui/macro.rs:44:14
|
44 | source = r#"
| ______________^
45 | | {% macro example(name, value, current, label="", id="") %}
46 | | {% endmacro %}
47 | | {% call example(name="name", value="") %}
48 | | "#,
| |______^

error: argument `name` is passed more than once when calling macro `example`
--> DuplicatedArg.txt:4:10
" call example(0, name=\"name\", value=\"\") %}\n "
--> tests/ui/macro.rs:55:14
|
55 | source = r#"
| ______________^
56 | | {% macro example(name, value, arg=12) %}
57 | | {% endmacro %}
58 | | {% call example(0, name="name", value="") %}
59 | | "#,
| |______^
4 changes: 2 additions & 2 deletions testing/tests/ui/macro_default_value.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: macro "thrice" expected at least 1 argument, found 0
error: missing argument when calling macro `thrice`: `param1`
--> InvalidDefault1.html:4:2
"- call thrice() -%}"
--> tests/ui/macro_default_value.rs:4:21
Expand All @@ -10,7 +10,7 @@ error: macro "thrice" expected at least 1 argument, found 0
7 | | {%- call thrice() -%}", ext = "html")]
| |______________________^

error: macro "thrice" expected at least 1 argument, found 3
error: macro `thrice` expected 2 arguments, found 3
--> InvalidDefault2.html:4:2
"- call thrice(1, 2, 3) -%}"
--> tests/ui/macro_default_value.rs:11:21
Expand Down
4 changes: 2 additions & 2 deletions testing/tests/ui/macro_named_argument.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: no argument named `param3` in macro "thrice"
error: missing argument when calling macro `thrice`: `param1`
--> InvalidNamedArg.html:5:2
"- call thrice(param1=2, param3=3) -%}"
--> tests/ui/macro_named_argument.rs:4:21
Expand Down Expand Up @@ -49,7 +49,7 @@ error: named arguments must always be passed last
33 | | {%- call thrice(param1=2, 3) -%}", ext = "html")]
| |_________________________________^

error: `param1` is passed more than once
error: argument `param1` is passed more than once when calling macro `thrice`
--> InvalidNamedArg5.html:4:2
"- call thrice(3, param1=2) -%}"
--> tests/ui/macro_named_argument.rs:38:21
Expand Down

0 comments on commit 5a7213e

Please sign in to comment.