Repository: rinja-rs/rinja (rust)

Issue:
Add support for macro argument default value
It's possible in [jinja](https://jinja.palletsprojects.com/en/3.1.x/templates/#macros) to have default value for macro arguments:

```jinja
{% macro input(name, value='', type='text', size=20) -%}
    <input type="{{ type }}" name="{{ name }}" value="{{value|e }}" size="{{ size }}">
{%- endmacro %}
```

Pull request description:
Add support for default value in macros
Fixes #198.

So I went the easy way and allowed to have an expression as default value. Not sure if it's a good or bad idea.

I still need to update the book, but before doing that, I want to ensure with you if expression as default value is ok with you or if I should limit it.

Patch:
diff --git a/book/src/template_syntax.md b/book/src/template_syntax.md
index 30cce324..c30a7954 100644
--- a/book/src/template_syntax.md
+++ b/book/src/template_syntax.md
@@ -863,7 +863,7 @@ You can mix named and non-named arguments when calling a macro:
 {% call heading("title", bold="something") %}
 ```
 
-However please note than named arguments must always come **last**.
+However please note that named arguments must always come **last**.
 
 Another thing to note, if a named argument is referring to an argument that would
 be used for a non-named argument, it will error:
@@ -884,6 +884,24 @@ In here it's invalid because `arg2` is the second argument and would be used by
 {% call heading("something", "title", "b", arg4="ah") %}
 ```
 
+### Default value
+
+You can specify default values for your macro arguments:
+
+```jinja
+{% macro heading(arg1, arg2 = "something") %}
+{% endmacro %}
+```
+
+Then if you don't pass a value for this argument, its default value will be used:
+
+```jinja
+{# We only specify `arg1`, so `arg2` will be "something" #}
+{% call heading(1) %}
+{# We specify both `arg1` and `arg2` so no default value is used #}
+{% call heading(1, 2) %}
+```
+
 ## Calling Rust macros
 
 It is possible to call rust macros directly in your templates:
diff --git a/rinja_derive/src/generator.rs b/rinja_derive/src/generator.rs
index f7124c24..7afc4289 100644
--- a/rinja_derive/src/generator.rs
+++ b/rinja_derive/src/generator.rs
@@ -7,7 +7,8 @@ use std::sync::Arc;
 use std::{cmp, hash, mem, str};
 
 use parser::node::{
-    Call, Comment, Cond, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Match, Whitespace, Ws,
+    Call, Comment, Cond, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Macro, Match,
+    Whitespace, Ws,
 };
 use parser::{
     CharLit, CharPrefix, Expr, Filter, FloatKind, IntKind, Node, Num, StrLit, StrPrefix, Target,
@@ -866,43 +867,33 @@ impl<'a> Generator<'a> {
 
         self.flush_ws(ws); // Cannot handle_ws() here: whitespace from macro definition comes first
         let size_hint = self.push_locals(|this| {
+            macro_call_ensure_arg_count(call, def, ctx)?;
+
             this.write_buf_writable(ctx, buf)?;
             buf.write('{');
             this.prepare_ws(def.ws1);
 
-            let mut names = Buffer::new();
-            let mut values = Buffer::new();
-            let mut is_first_variable = true;
-            if args.len() != def.args.len() {
-                return Err(ctx.generate_error(
-                    &format!(
-                        "macro {name:?} expected {} argument{}, found {}",
-                        def.args.len(),
-                        if def.args.len() != 1 { "s" } else { "" },
-                        args.len()
-                    ),
-                    call,
-                ));
-            }
-            let mut named_arguments = HashMap::new();
+            let mut named_arguments: HashMap<&str, _, FxBuildHasher> = HashMap::default();
             // Since named arguments can only be passed last, we only need to check if the last argument
             // is a named one.
             if let Some(Expr::NamedArgument(_, _)) = args.last().map(|expr| &**expr) {
                 // First we check that all named arguments actually exist in the called item.
-                for arg in args.iter().rev() {
+                for (index, arg) in args.iter().enumerate().rev() {
                     let Expr::NamedArgument(arg_name, _) = &**arg else {
                         break;
                     };
-                    if !def.args.iter().any(|arg| arg == arg_name) {
+                    if !def.args.iter().any(|(arg, _)| arg == arg_name) {
                         return Err(ctx.generate_error(
                             &format!("no argument named `{arg_name}` in macro {name:?}"),
                             call,
                         ));
                     }
-                    named_arguments.insert(Cow::Borrowed(arg_name), arg);
+                    named_arguments.insert(arg_name, (index, arg));
                 }
             }
 
+            let mut value = Buffer::new();
+
             // Handling both named and unnamed arguments requires to be careful of the named arguments
             // order. To do so, we iterate through the macro defined arguments and then check if we have
             // a named argument with this name:
@@ -911,23 +902,43 @@ impl<'a> Generator<'a> {
             // * If there isn't one, then we pick the next argument (we can do it without checking
             //   anything since named arguments are always last).
             let mut allow_positional = true;
-            for (index, arg) in def.args.iter().enumerate() {
-                let expr = if let Some(expr) = named_arguments.get(&Cow::Borrowed(arg)) {
+            let mut used_named_args = vec![false; args.len()];
+            for (index, (arg, default_value)) in def.args.iter().enumerate() {
+                let expr = if let Some((index, expr)) = named_arguments.get(arg) {
+                    used_named_args[*index] = true;
                     allow_positional = false;
                     expr
                 } else {
-                    if !allow_positional {
-                        // If there is already at least one named argument, then it's not allowed
-                        // to use unnamed ones at this point anymore.
-                        return Err(ctx.generate_error(
-                            &format!(
-                            "cannot have unnamed argument (`{arg}`) after named argument in macro \
-                             {name:?}"
-                        ),
-                            call,
-                        ));
+                    match args.get(index) {
+                        Some(arg_expr) if !matches!(**arg_expr, Expr::NamedArgument(_, _)) => {
+                            // If there is already at least one named argument, then it's not allowed
+                            // to use unnamed ones at this point anymore.
+                            if !allow_positional {
+                                return Err(ctx.generate_error(
+                                    &format!(
+                                        "cannot have unnamed argument (`{arg}`) after named argument \
+                                         in call to macro {name:?}"
+                                    ),
+                                    call,
+                                ));
+                            }
+                            arg_expr
+                        }
+                        Some(arg_expr) if used_named_args[index] => {
+                            let Expr::NamedArgument(name, _) = **arg_expr else { unreachable!() };
+                            return Err(ctx.generate_error(
+                                &format!("`{name}` is passed more than once"),
+                                call,
+                            ));
+                        }
+                        _ => {
+                            if let Some(default_value) = default_value {
+                                default_value
+                            } else {
+                                return Err(ctx.generate_error(&format!("missing `{arg}` argument"), call));
+                            }
+                        }
                     }
-                    &args[index]
                 };
                 match &**expr {
                     // If `expr` is already a form of variable then
@@ -951,30 +962,19 @@ impl<'a> Generator<'a> {
                     // multiple times, e.g. in the case of macro
                     // parameters being used multiple times.
                     _ => {
-                        if is_first_variable {
-                            is_first_variable = false;
+                        value.clear();
+                        let (before, after) = if !is_copyable(expr) {
+                            ("&(", ")")
                         } else {
-                            names.write(',');
-                            values.write(',');
-                        }
-                        names.write(arg);
-
-                        values.write('(');
-                        if !is_copyable(expr) {
-                            values.write('&');
-                        }
-                        values.write(this.visit_expr_root(ctx, expr)?);
-                        values.write(')');
+                            ("", "")
+                        };
+                        value.write(this.visit_expr_root(ctx, expr)?);
+                        buf.write(format_args!("let {arg} = {before}{}{after};", value.buf));
                         this.locals.insert_with_default(Cow::Borrowed(arg));
                     }
                 }
             }
 
-            debug_assert_eq!(names.buf.is_empty(), values.buf.is_empty());
-            if !names.buf.is_empty() {
-                buf.write(format_args!("let ({}) = ({});", names.buf, values.buf));
-            }
-
             let mut size_hint = this.handle(own_ctx, &def.nodes, buf, AstLevel::Nested)?;
 
             this.flush_ws(def.ws2);
@@ -2458,6 +2458,43 @@ impl<'a> Generator<'a> {
     }
 }
 
+fn macro_call_ensure_arg_count(
+    call: &WithSpan<'_, Call<'_>>,
+    def: &Macro<'_>,
+    ctx: &Context<'_>,
+) -> Result<(), CompileError> {
+    if call.args.len() == def.args.len() {
+        // exactly enough arguments were provided
+        return Ok(());
+    }
+
+    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(());
+    }
+
+    // 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 "),
+    };
+    Err(ctx.generate_error(
+        &format!(
+            "macro {:?} expected {extra}{expected_args} argument{}, found {}",
+            def.name,
+            if expected_args != 1 { "s" } else { "" },
+            call.args.len(),
+        ),
+        call,
+    ))
+}
+
 #[cfg(target_pointer_width = "16")]
 type TargetIsize = i16;
 #[cfg(target_pointer_width = "32")]
@@ -2716,6 +2753,11 @@ impl Buffer {
         }
         s.len()
     }
+
+    fn clear(&mut self) {
+        self.buf.clear();
+        self.last_was_write_str = false;
+    }
 }
 
 trait BufferFmt {
@@ -2986,11 +3028,12 @@ fn is_copyable(expr: &Expr<'_>) -> bool {
 
 fn is_copyable_within_op(expr: &Expr<'_>, within_op: bool) -> bool {
     match expr {
-        Expr::BoolLit(_) | Expr::NumLit(_, _) | Expr::StrLit(_) | Expr::CharLit(_) => true,
+        Expr::BoolLit(_)
+        | Expr::NumLit(_, _)
+        | Expr::StrLit(_)
+        | Expr::CharLit(_)
+        | Expr::BinOp(_, _, _) => true,
         Expr::Unary(.., expr) => is_copyable_within_op(expr, true),
-        Expr::BinOp(_, lhs, rhs) => {
-            is_copyable_within_op(lhs, true) && is_copyable_within_op(rhs, true)
-        }
         Expr::Range(..) => true,
         // The result of a call likely doesn't need to be borrowed,
         // as in that case the call is more likely to return a
diff --git a/rinja_parser/src/node.rs b/rinja_parser/src/node.rs
index b120d57e..4cf4da2f 100644
--- a/rinja_parser/src/node.rs
+++ b/rinja_parser/src/node.rs
@@ -1,9 +1,11 @@
+use std::collections::HashSet;
 use std::str;
 
 use winnow::Parser;
 use winnow::combinator::{
-    alt, cut_err, delimited, eof, fail, not, opt, peek, preceded, repeat, separated0, separated1,
+    alt, cut_err, delimited, eof, fail, not, opt, peek, preceded, repeat, separated1,
 };
+use winnow::sequence::terminated;
 use winnow::token::{any, tag, take_till0};
 
 use crate::memchr_splitter::{Splitter1, Splitter2, Splitter3};
@@ -581,21 +583,59 @@ impl<'a> Loop<'a> {
 pub struct Macro<'a> {
     pub ws1: Ws,
     pub name: &'a str,
-    pub args: Vec<&'a str>,
+    pub args: Vec<(&'a str, Option<WithSpan<'a, Expr<'a>>>)>,
     pub nodes: Vec<Node<'a>>,
     pub ws2: Ws,
 }
 
+fn check_duplicated_name<'a>(
+    names: &mut HashSet<&'a str>,
+    arg_name: &'a str,
+    i: &'a str,
+) -> Result<(), crate::ParseErr<'a>> {
+    if !names.insert(arg_name) {
+        return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
+            format!("duplicated argument `{arg_name}`"),
+            i,
+        )));
+    }
+    Ok(())
+}
+
 impl<'a> Macro<'a> {
     fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
-        fn parameters(i: &str) -> ParseResult<'_, Vec<&str>> {
-            delimited(
-                ws('('),
-                separated0(ws(identifier), ','),
-                (opt(ws(',')), ')'),
-            )
-            .parse_next(i)
-        }
+        let level = s.level.get();
+        #[allow(clippy::type_complexity)]
+        let parameters =
+            |i| -> ParseResult<'_, Option<Vec<(&str, Option<WithSpan<'_, Expr<'_>>>)>>> {
+                let (i, args) = opt(preceded(
+                    '(',
+                    (
+                        opt(terminated(
+                            separated1(
+                                (
+                                    ws(identifier),
+                                    opt(preceded('=', ws(|i| Expr::parse(i, level)))),
+                                ),
+                                ',',
+                            ),
+                            opt(','),
+                        )),
+                        ws(opt(')')),
+                    ),
+                ))
+                .parse_next(i)?;
+                match args {
+                    Some((args, Some(_))) => Ok((i, args)),
+                    Some((_, None)) => {
+                        return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
+                            "expected `)` to close macro argument list",
+                            i,
+                        )));
+                    }
+                    None => Ok((i, None)),
+                }
+            };
 
         let start_s = i;
         let mut start = (
@@ -603,12 +643,9 @@ impl<'a> Macro<'a> {
             ws(keyword("macro")),
             cut_node(
                 Some("macro"),
-                (
-                    ws(identifier),
-                    opt(ws(parameters)),
-                    opt(Whitespace::parse),
-                    |i| s.tag_block_end(i),
-                ),
+                (ws(identifier), parameters, opt(Whitespace::parse), |i| {
+                    s.tag_block_end(i)
+                }),
             ),
         );
         let (j, (pws1, _, (name, params, nws1, _))) = start.parse_next(i)?;
@@ -619,6 +656,29 @@ impl<'a> Macro<'a> {
             )));
         }
 
+        if let Some(ref params) = params {
+            let mut names = HashSet::new();
+
+            let mut iter = params.iter();
+            while let Some((arg_name, default_value)) = iter.next() {
+                check_duplicated_name(&mut names, arg_name, i)?;
+                if default_value.is_some() {
+                    for (new_arg_name, default_value) in iter.by_ref() {
+                        check_duplicated_name(&mut names, new_arg_name, i)?;
+                        if default_value.is_none() {
+                            return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
+                                format!(
+                                    "all arguments following `{arg_name}` should have a default \
+                                         value, `{new_arg_name}` doesn't have a default value"
+                                ),
+                                i,
+                            )));
+                        }
+                    }
+                }
+            }
+        }
+
         let mut end = cut_node(
             Some("macro"),
             (


Test patch:
diff --git a/testing/tests/macro.rs b/testing/tests/macro.rs
index 1ed83c79..39a3c00b 100644
--- a/testing/tests/macro.rs
+++ b/testing/tests/macro.rs
@@ -170,3 +170,115 @@ struct TrailingComma;
 fn test_trailing_comma() {
     assert_eq!(TrailingComma.render().unwrap(), "hihihihihi");
 }
+
+#[derive(Template)]
+#[template(
+    source = "{%- macro thrice(param1=0, param2=1) -%}
+{{ param1 }} {{ param2 }}
+{% endmacro -%}
+
+{%- call thrice() -%}
+{%- call thrice(param1=4) -%}
+{%- call thrice(param2=4) -%}
+{%- call thrice(param2=4, param1=5) -%}
+{%- call thrice(4) -%}
+",
+    ext = "html"
+)]
+struct MacroDefaultValue;
+
+#[test]
+fn test_default_value() {
+    assert_eq!(
+        MacroDefaultValue.render().unwrap(),
+        "0 1\n4 1\n0 4\n5 4\n4 1\n"
+    );
+}
+
+// This test ensures that the mix of named argument and default value generates
+// the expected result.
+#[derive(Template)]
+#[template(
+    source = "{%- macro thrice(param1=0, param2=1, param3=2) -%}
+{{ param1 }} {{ param2 }} {{ param3 }}
+{% endmacro -%}
+
+{%- call thrice(4, param3=5) -%}
+",
+    ext = "html"
+)]
+struct MacroDefaultValue2;
+
+#[test]
+fn test_default_value2() {
+    assert_eq!(MacroDefaultValue2.render().unwrap(), "4 1 5\n");
+}
+
+// This test ensures that we can use the macro arguments as default value.
+#[derive(Template)]
+#[template(
+    source = "{%- macro thrice(a=1, b=a + 1, c=a + b + 2) -%}
+{{ a }} {{ b }} {{ c }}
+{% endmacro -%}
+
+{%- call thrice() -%}
+{%- call thrice(b=6) -%}
+{%- call thrice(c=3) -%}
+{%- call thrice(a=3) -%}
+",
+    ext = "html"
+)]
+struct MacroDefaultValue3;
+
+#[test]
+fn test_default_value3() {
+    assert_eq!(
+        MacroDefaultValue3.render().unwrap(),
+        "1 2 5\n1 6 9\n1 2 3\n3 4 9\n"
+    );
+}
+
+// This test ensures that we can use declared variables as default value for
+// macro arguments.
+#[derive(Template)]
+#[template(
+    source = "{% let x = 12 %}
+{%- macro thrice(a=x, b=y) -%}
+{{ a }} {{ b }}
+{% endmacro -%}
+
+{%- let y = 4 -%}
+{%- call thrice() -%}
+{%- call thrice(1) -%}
+{%- call thrice(b=1) -%}
+",
+    ext = "html"
+)]
+struct MacroDefaultValue4;
+
+#[test]
+fn test_default_value4() {
+    assert_eq!(MacroDefaultValue4.render().unwrap(), "12 4\n1 4\n12 1\n");
+}
+
+// This test ensures that we can macro arguments take precedence over declared
+// variables when a macro argument default value is using a variable.
+#[derive(Template)]
+#[template(
+    source = "{% let a = 12 %}
+{%- macro thrice(a=3, b=a) -%}
+{{ a }} {{ b }}
+{% endmacro -%}
+
+{%- call thrice() -%}
+{%- call thrice(1) -%}
+{%- call thrice(1, 2) -%}
+",
+    ext = "html"
+)]
+struct MacroDefaultValue5;
+
+#[test]
+fn test_default_value5() {
+    assert_eq!(MacroDefaultValue5.render().unwrap(), "3 3\n1 1\n1 2\n");
+}
diff --git a/testing/tests/ui/macro.rs b/testing/tests/ui/macro.rs
index 395cea5a..13ff4297 100644
--- a/testing/tests/ui/macro.rs
+++ b/testing/tests/ui/macro.rs
@@ -23,5 +23,21 @@ struct InvalidNumberOfArgs2;
 {%- call thrice(1, 2) -%}", ext = "html")]
 struct InvalidNumberOfArgs3;
 
+#[derive(Template)]
+#[template(source = "{% macro thrice( %}{% endmacro %}", ext = "html")]
+struct NoClosingParen1;
+
+#[derive(Template)]
+#[template(source = "{% macro thrice(a, b, c %}{% endmacro %}", ext = "html")]
+struct NoClosingParen2;
+
+#[derive(Template)]
+#[template(source = "{% macro thrice(a, b, c= %}{% endmacro %}", ext = "html")]
+struct NoClosingParen3;
+
+#[derive(Template)]
+#[template(source = "{% macro thrice(a, b, c = %}{% endmacro %}", ext = "html")]
+struct NoClosingParen4;
+
 fn main() {
 }
diff --git a/testing/tests/ui/macro.stderr b/testing/tests/ui/macro.stderr
index 3539e432..8abc59e5 100644
--- a/testing/tests/ui/macro.stderr
+++ b/testing/tests/ui/macro.stderr
@@ -35,3 +35,35 @@ error: macro "thrice" expected 0 arguments, found 2
 22 | |
 23 | | {%- call thrice(1, 2) -%}", ext = "html")]
    | |__________________________^
+
+error: expected `)` to close macro argument list
+ --> <source attribute>:1:17
+       "%}{% endmacro %}"
+  --> tests/ui/macro.rs:27:21
+   |
+27 | #[template(source = "{% macro thrice( %}{% endmacro %}", ext = "html")]
+   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: expected `)` to close macro argument list
+ --> <source attribute>:1:24
+       "%}{% endmacro %}"
+  --> tests/ui/macro.rs:31:21
+   |
+31 | #[template(source = "{% macro thrice(a, b, c %}{% endmacro %}", ext = "html")]
+   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: expected `)` to close macro argument list
+ --> <source attribute>:1:23
+       "= %}{% endmacro %}"
+  --> tests/ui/macro.rs:35:21
+   |
+35 | #[template(source = "{% macro thrice(a, b, c= %}{% endmacro %}", ext = "html")]
+   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: expected `)` to close macro argument list
+ --> <source attribute>:1:24
+       "= %}{% endmacro %}"
+  --> tests/ui/macro.rs:39:21
+   |
+39 | #[template(source = "{% macro thrice(a, b, c = %}{% endmacro %}", ext = "html")]
+   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/testing/tests/ui/macro_default_value.rs b/testing/tests/ui/macro_default_value.rs
new file mode 100644
index 00000000..11e0ee35
--- /dev/null
+++ b/testing/tests/ui/macro_default_value.rs
@@ -0,0 +1,18 @@
+use rinja::Template;
+
+#[derive(Template)]
+#[template(source = "{%- macro thrice(param1, param2=0) -%}
+{{ param1 }} {{ param2 }}
+{%- endmacro -%}
+{%- call thrice() -%}", ext = "html")]
+struct InvalidDefault1;
+
+#[derive(Template)]
+#[template(source = "{%- macro thrice(param1, param2=0) -%}
+{{ param1 }} {{ param2 }}
+{%- endmacro -%}
+{%- call thrice(1, 2, 3) -%}", ext = "html")]
+struct InvalidDefault2;
+
+fn main() {
+}
diff --git a/testing/tests/ui/macro_default_value.stderr b/testing/tests/ui/macro_default_value.stderr
new file mode 100644
index 00000000..599ac37f
--- /dev/null
+++ b/testing/tests/ui/macro_default_value.stderr
@@ -0,0 +1,23 @@
+error: macro "thrice" expected at least 1 argument, found 0
+ --> InvalidDefault1.html:4:2
+       "- call thrice() -%}"
+ --> tests/ui/macro_default_value.rs:4:21
+  |
+4 |   #[template(source = "{%- macro thrice(param1, param2=0) -%}
+  |  _____________________^
+5 | | {{ param1 }} {{ param2 }}
+6 | | {%- endmacro -%}
+7 | | {%- call thrice() -%}", ext = "html")]
+  | |______________________^
+
+error: macro "thrice" expected at least 1 argument, found 3
+ --> InvalidDefault2.html:4:2
+       "- call thrice(1, 2, 3) -%}"
+  --> tests/ui/macro_default_value.rs:11:21
+   |
+11 |   #[template(source = "{%- macro thrice(param1, param2=0) -%}
+   |  _____________________^
+12 | | {{ param1 }} {{ param2 }}
+13 | | {%- endmacro -%}
+14 | | {%- call thrice(1, 2, 3) -%}", ext = "html")]
+   | |_____________________________^
diff --git a/testing/tests/ui/macro_named_argument.rs b/testing/tests/ui/macro_named_argument.rs
index 5e218d80..bce059e7 100644
--- a/testing/tests/ui/macro_named_argument.rs
+++ b/testing/tests/ui/macro_named_argument.rs
@@ -22,7 +22,7 @@ struct InvalidNamedArg2;
 {{ param1 }} {{ param2 }}
 {%- endmacro -%}
 
-{%- call thrice(3, param1=2) | filter(param1=12) -%}", ext = "html")]
+{%- call thrice(3, param1=2)|filter(param1=12) -%}", ext = "html")]
 struct InvalidNamedArg3;
 
 // Ensures that named arguments can only be passed last.
diff --git a/testing/tests/ui/macro_named_argument.stderr b/testing/tests/ui/macro_named_argument.stderr
index 8a5e4d7f..4a0bd4ef 100644
--- a/testing/tests/ui/macro_named_argument.stderr
+++ b/testing/tests/ui/macro_named_argument.stderr
@@ -25,8 +25,8 @@ error: named argument `param1` was passed more than once
    | |________________________________________^
 
 error: failed to parse template source
- --> <source attribute>:5:29
-       "| filter(param1=12) -%}"
+ --> <source attribute>:5:28
+       "|filter(param1=12) -%}"
   --> tests/ui/macro_named_argument.rs:21:21
    |
 21 |   #[template(source = "{%- macro thrice(param1, param2) -%}
@@ -34,8 +34,8 @@ error: failed to parse template source
 22 | | {{ param1 }} {{ param2 }}
 23 | | {%- endmacro -%}
 24 | |
-25 | | {%- call thrice(3, param1=2) | filter(param1=12) -%}", ext = "html")]
-   | |_____________________________________________________^
+25 | | {%- call thrice(3, param1=2)|filter(param1=12) -%}", ext = "html")]
+   | |___________________________________________________^
 
 error: named arguments must always be passed last
  --> <source attribute>:4:15
@@ -49,7 +49,7 @@ error: named arguments must always be passed last
 33 | | {%- call thrice(param1=2, 3) -%}", ext = "html")]
    | |_________________________________^
 
-error: cannot have unnamed argument (`param2`) after named argument in macro "thrice"
+error: `param1` is passed more than once
  --> InvalidNamedArg5.html:4:2
        "- call thrice(3, param1=2) -%}"
   --> tests/ui/macro_named_argument.rs:38:21