Skip to content

Commit 0454bfb

Browse files
Improve macro call arguments mismatch errors
1 parent e81ca42 commit 0454bfb

File tree

6 files changed

+151
-28
lines changed

6 files changed

+151
-28
lines changed

rinja_derive/src/generator/node.rs

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::borrow::Cow;
2+
use std::collections::HashSet;
23
use std::collections::hash_map::{Entry, HashMap};
34
use std::fmt::Write;
45
use std::mem;
@@ -1421,33 +1422,73 @@ fn macro_call_ensure_arg_count(
14211422
def: &Macro<'_>,
14221423
ctx: &Context<'_>,
14231424
) -> Result<(), CompileError> {
1424-
if call.args.len() == def.args.len() {
1425-
// exactly enough arguments were provided
1426-
return Ok(());
1425+
if call.args.len() > def.args.len() {
1426+
return Err(ctx.generate_error(
1427+
format_args!(
1428+
"macro `{}` expected {} argument{}, found {}",
1429+
def.name,
1430+
def.args.len(),
1431+
if def.args.len() > 1 { "s" } else { "" },
1432+
call.args.len(),
1433+
),
1434+
call.span(),
1435+
));
14271436
}
1428-
1429-
let nb_default_args = def
1430-
.args
1431-
.iter()
1432-
.rev()
1433-
.take_while(|(_, default_value)| default_value.is_some())
1434-
.count();
1435-
if call.args.len() < def.args.len() && call.args.len() >= def.args.len() - nb_default_args {
1436-
// all missing arguments have a default value, and there weren't too many args
1437-
return Ok(());
1437+
// First we list of arguments position then we remove them one by one.
1438+
let mut args = (0..def.args.len()).collect::<HashSet<_>>();
1439+
for (pos, arg) in call.args.iter().enumerate() {
1440+
let pos = match **arg {
1441+
Expr::NamedArgument(name, ..) => {
1442+
def.args.iter().position(|(arg_name, _)| *arg_name == name)
1443+
}
1444+
_ => Some(pos),
1445+
};
1446+
if let Some(pos) = pos {
1447+
if !args.remove(&pos) {
1448+
// This argument was already passed, so error.
1449+
return Err(ctx.generate_error(
1450+
format_args!(
1451+
"argument `{}` is passed more than once when calling macro `{}`",
1452+
def.args[pos].0, def.name,
1453+
),
1454+
call.span(),
1455+
));
1456+
}
1457+
}
14381458
}
14391459

1440-
// either too many or not enough arguments were provided
1441-
let (expected_args, extra) = match nb_default_args {
1442-
0 => (def.args.len(), ""),
1443-
_ => (nb_default_args, "at least "),
1460+
// Now we need to filter out arguments with default value.
1461+
let args = args
1462+
.into_iter()
1463+
.filter(|pos| def.args[*pos].1.is_none())
1464+
.collect::<Vec<_>>();
1465+
let (extra, error) = match args.len() {
1466+
0 => return Ok(()),
1467+
1 => ("", format!("`{}`", def.args[0].0)),
1468+
2 => ("s", format!("`{}` and `{}`", def.args[0].0, def.args[1].0)),
1469+
_ => {
1470+
let mut error_s =
1471+
args.iter()
1472+
.take(args.len() - 2)
1473+
.fold(String::new(), |mut acc, arg| {
1474+
if !acc.is_empty() {
1475+
acc.push_str(", ");
1476+
}
1477+
acc.push_str(&format!("`{}`", def.args[*arg].0));
1478+
acc
1479+
});
1480+
error_s.push_str(&format!(
1481+
" and `{}`",
1482+
def.args[*args.last().expect("no last args")].0
1483+
));
1484+
("s", error_s)
1485+
}
14441486
};
1487+
14451488
Err(ctx.generate_error(
14461489
format_args!(
1447-
"macro {:?} expected {extra}{expected_args} argument{}, found {}",
1448-
def.name,
1449-
if expected_args != 1 { "s" } else { "" },
1450-
call.args.len(),
1490+
"missing argument{extra} when calling macro `{}`: {error}",
1491+
def.name
14511492
),
14521493
call.span(),
14531494
))

testing/tests/ui/macro.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,27 @@ struct NoClosingParen3;
3939
#[template(source = "{% macro thrice(a, b, c = %}{% endmacro %}", ext = "html")]
4040
struct NoClosingParen4;
4141

42+
#[derive(rinja::Template)]
43+
#[template(
44+
source = r#"
45+
{% macro example(name, value, current, label="", id="") %}
46+
{% endmacro %}
47+
{% call example(name="name", value="") %}
48+
"#,
49+
ext = "txt"
50+
)]
51+
struct WrongNumberOfParams;
52+
53+
#[derive(rinja::Template)]
54+
#[template(
55+
source = r#"
56+
{% macro example(name, value, arg=12) %}
57+
{% endmacro %}
58+
{% call example(0, name="name", value="") %}
59+
"#,
60+
ext = "txt"
61+
)]
62+
struct DuplicatedArg;
63+
4264
fn main() {
4365
}

testing/tests/ui/macro.stderr

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: macro "thrice" expected 1 argument, found 2
1+
error: macro `thrice` expected 1 argument, found 2
22
--> InvalidNumberOfArgs.html:5:2
33
"- call thrice(2, 3) -%}"
44
--> tests/ui/macro.rs:4:21
@@ -11,7 +11,7 @@ error: macro "thrice" expected 1 argument, found 2
1111
8 | | {%- call thrice(2, 3) -%}", ext = "html")]
1212
| |__________________________^
1313

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

27-
error: macro "thrice" expected 0 arguments, found 2
27+
error: macro `thrice` expected 0 argument, found 2
2828
--> InvalidNumberOfArgs3.html:4:2
2929
"- call thrice(1, 2) -%}"
3030
--> tests/ui/macro.rs:20:21
@@ -67,3 +67,29 @@ error: expected `)` to close macro argument list
6767
|
6868
39 | #[template(source = "{% macro thrice(a, b, c = %}{% endmacro %}", ext = "html")]
6969
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
70+
71+
error: missing argument when calling macro `example`: `name`
72+
--> WrongNumberOfParams.txt:4:10
73+
" call example(name=\"name\", value=\"\") %}\n "
74+
--> tests/ui/macro.rs:44:14
75+
|
76+
44 | source = r#"
77+
| ______________^
78+
45 | | {% macro example(name, value, current, label="", id="") %}
79+
46 | | {% endmacro %}
80+
47 | | {% call example(name="name", value="") %}
81+
48 | | "#,
82+
| |______^
83+
84+
error: argument `name` is passed more than once when calling macro `example`
85+
--> DuplicatedArg.txt:4:10
86+
" call example(0, name=\"name\", value=\"\") %}\n "
87+
--> tests/ui/macro.rs:55:14
88+
|
89+
55 | source = r#"
90+
| ______________^
91+
56 | | {% macro example(name, value, arg=12) %}
92+
57 | | {% endmacro %}
93+
58 | | {% call example(0, name="name", value="") %}
94+
59 | | "#,
95+
| |______^

testing/tests/ui/macro_default_value.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: macro "thrice" expected at least 1 argument, found 0
1+
error: missing argument when calling macro `thrice`: `param1`
22
--> InvalidDefault1.html:4:2
33
"- call thrice() -%}"
44
--> tests/ui/macro_default_value.rs:4:21
@@ -10,7 +10,7 @@ error: macro "thrice" expected at least 1 argument, found 0
1010
7 | | {%- call thrice() -%}", ext = "html")]
1111
| |______________________^
1212

13-
error: macro "thrice" expected at least 1 argument, found 3
13+
error: macro `thrice` expected 2 arguments, found 3
1414
--> InvalidDefault2.html:4:2
1515
"- call thrice(1, 2, 3) -%}"
1616
--> tests/ui/macro_default_value.rs:11:21

testing/tests/ui/macro_named_argument.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,17 @@ struct InvalidNamedArg4;
4141
{%- call thrice(3, param1=2) -%}", ext = "html")]
4242
struct InvalidNamedArg5;
4343

44+
#[derive(Template)]
45+
#[template(source = "{%- macro thrice(param1, param2) -%}
46+
{%- endmacro -%}
47+
{%- call thrice() -%}", ext = "html")]
48+
struct MissingArgs;
49+
50+
#[derive(Template)]
51+
#[template(source = "{%- macro thrice(param1, param2=1) -%}
52+
{%- endmacro -%}
53+
{%- call thrice() -%}", ext = "html")]
54+
struct MissingArgs2;
55+
4456
fn main() {
4557
}

testing/tests/ui/macro_named_argument.stderr

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: no argument named `param3` in macro "thrice"
1+
error: missing argument when calling macro `thrice`: `param1`
22
--> InvalidNamedArg.html:5:2
33
"- call thrice(param1=2, param3=3) -%}"
44
--> tests/ui/macro_named_argument.rs:4:21
@@ -49,7 +49,7 @@ error: named arguments must always be passed last
4949
33 | | {%- call thrice(param1=2, 3) -%}", ext = "html")]
5050
| |_________________________________^
5151

52-
error: `param1` is passed more than once
52+
error: argument `param1` is passed more than once when calling macro `thrice`
5353
--> InvalidNamedArg5.html:4:2
5454
"- call thrice(3, param1=2) -%}"
5555
--> tests/ui/macro_named_argument.rs:38:21
@@ -60,3 +60,25 @@ error: `param1` is passed more than once
6060
40 | | {%- endmacro -%}
6161
41 | | {%- call thrice(3, param1=2) -%}", ext = "html")]
6262
| |_________________________________^
63+
64+
error: missing arguments when calling macro `thrice`: `param1` and `param2`
65+
--> MissingArgs.html:3:2
66+
"- call thrice() -%}"
67+
--> tests/ui/macro_named_argument.rs:45:21
68+
|
69+
45 | #[template(source = "{%- macro thrice(param1, param2) -%}
70+
| _____________________^
71+
46 | | {%- endmacro -%}
72+
47 | | {%- call thrice() -%}", ext = "html")]
73+
| |______________________^
74+
75+
error: missing argument when calling macro `thrice`: `param1`
76+
--> MissingArgs2.html:3:2
77+
"- call thrice() -%}"
78+
--> tests/ui/macro_named_argument.rs:51:21
79+
|
80+
51 | #[template(source = "{%- macro thrice(param1, param2=1) -%}
81+
| _____________________^
82+
52 | | {%- endmacro -%}
83+
53 | | {%- call thrice() -%}", ext = "html")]
84+
| |______________________^

0 commit comments

Comments
 (0)