diff --git a/askama_derive/src/generator/expr.rs b/askama_derive/src/generator/expr.rs index 093b054d..a2134b33 100644 --- a/askama_derive/src/generator/expr.rs +++ b/askama_derive/src/generator/expr.rs @@ -485,6 +485,14 @@ impl<'a> Generator<'a, '_> { left: &WithSpan<'a, Expr<'a>>, args: &[WithSpan<'a, Expr<'a>>], ) -> Result { + // ensure that no named args are used in normal rust call expressions + if let Some(arg) = args + .iter() + .find(|&arg| matches!(**arg, Expr::NamedArgument(_, _))) + { + return Err(ctx.generate_error("Unsupported use of named arguments", arg.span())); + } + match &**left { Expr::AssociatedItem(sub_left, AssociatedItem { name, generics }) if ***sub_left == Expr::Var("loop") => diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index e3c17b49..66335a8f 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -251,7 +251,6 @@ impl<'a> Expr<'a> { pub(super) fn arguments( i: &mut &'a str, level: Level<'_>, - is_template_macro: bool, ) -> ParseResult<'a, Vec>> { let _level_guard = level.nest(i)?; let mut named_arguments = HashSet::new(); @@ -269,15 +268,7 @@ impl<'a> Expr<'a> { let has_named_arguments = !named_arguments.is_empty(); let expr = alt(( - move |i: &mut _| { - Self::named_argument( - i, - level, - named_arguments, - start, - is_template_macro, - ) - }, + move |i: &mut _| Self::named_argument(i, level, named_arguments, start), move |i: &mut _| Self::parse(i, level, false), )) .parse_next(i)?; @@ -300,14 +291,7 @@ impl<'a> Expr<'a> { level: Level<'_>, named_arguments: &mut HashSet<&'a str>, start: &'a str, - is_template_macro: bool, ) -> ParseResult<'a, WithSpan<'a, Self>> { - if !is_template_macro { - // If this is not a template macro, we don't want to parse named arguments so - // we instead return an error which will allow to continue the parsing. - return fail.parse_next(i); - } - let (argument, _, value) = (identifier, ws('='), move |i: &mut _| { Self::parse(i, level, false) }) @@ -703,7 +687,7 @@ impl<'a> Filter<'a> { pub(crate) fn parse(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, Self> { let (name, arguments) = ( ws(|i: &mut _| path_or_identifier(i, level)), - opt(|i: &mut _| Expr::arguments(i, level, true)), + opt(|i: &mut _| Expr::arguments(i, level)), ) .parse_next(i)?; Ok(Self { @@ -1126,7 +1110,7 @@ impl<'a> Suffix<'a> { fn call(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, Self> { (opt(|i: &mut _| call_generics(i, level)), |i: &mut _| { - Expr::arguments(i, level, false) + Expr::arguments(i, level) }) .map(|(_generics, args)| Self::Call { args }) .parse_next(i) diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index 15bd26e2..00e5b1fe 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -885,7 +885,7 @@ impl<'a> Call<'a> { ( opt((ws(identifier), ws("::"))), ws(identifier), - opt(ws(|nested: &mut _| Expr::arguments(nested, s.level, true))), + opt(ws(|nested: &mut _| Expr::arguments(nested, s.level))), opt(Whitespace::parse), |i: &mut _| s.tag_block_end(i), ), diff --git a/testing/tests/macro.rs b/testing/tests/macro.rs index 44605107..a8192578 100644 --- a/testing/tests/macro.rs +++ b/testing/tests/macro.rs @@ -580,6 +580,31 @@ fn test_expr_macro_call_importchain_nested() { assert_eq!(ExprMacroCall.render().unwrap(), "foo"); } +#[test] +fn test_expr_macro_call_named_arguments() { + #[derive(Template)] + #[template( + source = r#" +{%- macro testmacro(arg0 = "-", arg1 = 5) -%} + {{ arg0 }} | {{ arg1 }}; +{%- endmacro -%} +{{- testmacro() }} +{{- testmacro("a") }} +{{- testmacro("b", 1337) }} +{{- testmacro("c", arg1 = 1338) }} +{{- testmacro(arg0 = "d", arg1 = 1339) -}} +"#, + ext = "html" + )] + struct ExprMacroCall; + + // primarily checking for compilation + assert_eq!( + ExprMacroCall.render().unwrap(), + "- | 5;a | 5;b | 1337;c | 1338;d | 1339;" + ); +} + #[test] fn test_macro_caller_is_defined_check() { #[derive(Template)] diff --git a/testing/tests/ui/caller_arguments.rs b/testing/tests/ui/caller_arguments.rs index 6a8872b6..ad1f305d 100644 --- a/testing/tests/ui/caller_arguments.rs +++ b/testing/tests/ui/caller_arguments.rs @@ -89,20 +89,4 @@ struct CallerInCaller1 { struct JustCaller{ } -#[derive(Template)] -#[template( - source = r#" - {% macro test() %} - {{ caller("a", one = "b") }} - {%- endmacro -%} - {%- call(two, one) test() -%} - {{- two -}} {{- one -}} - {%- endcall -%} - "#, - ext = "txt" -)] -struct NamedArguments { -} - -fn main() {} - +fn main() {} \ No newline at end of file diff --git a/testing/tests/ui/caller_arguments.stderr b/testing/tests/ui/caller_arguments.stderr index aa44e6ff..eb7ee897 100644 --- a/testing/tests/ui/caller_arguments.stderr +++ b/testing/tests/ui/caller_arguments.stderr @@ -80,18 +80,3 @@ error: block is not defined for `caller` | 86 | source = r#"{{caller()}}"#, | ^^^^^^^^^^^^^^^^^ - -error: failed to parse template source - --> :3:27 - "= \"b\") }}\n {%- endmacro -%}\n {%- call(two, one) test() -%}\n {{- two"... - --> tests/ui/caller_arguments.rs:94:14 - | -94 | source = r#" - | ______________^ -95 | | {% macro test() %} -96 | | {{ caller("a", one = "b") }} -97 | | {%- endmacro -%} -... | -100 | | {%- endcall -%} -101 | | "#, - | |______^ diff --git a/testing/tests/ui/expr_fn_calls.rs b/testing/tests/ui/expr_fn_calls.rs new file mode 100644 index 00000000..88bac327 --- /dev/null +++ b/testing/tests/ui/expr_fn_calls.rs @@ -0,0 +1,35 @@ +#![allow(unused_variables)] + +use askama::Template; + +pub fn static_fn(arg: &str) -> &str { arg } +pub fn static_fn2(arg: &str) -> Option<&str> { Some(arg) } + + +#[derive(Template)] +#[template(source = "{{ test_fn(arg = 5) }}", ext = "html")] +struct NamedArgsInRustExprMemberFn; + +impl NamedArgsInRustExprMemberFn { + pub fn test_fn(&self, arg: u64) -> &'static str { + "This is a test" + } +} + + +#[derive(Template)] +#[template(source = r#"{{ self::static_fn(arg = "test") }}"#, ext = "html")] +struct NamedArgsInRustExprStaticCall; + + +#[derive(Template)] +#[template(source = r#"{{ self::static_fn2("test").unwrap(arg = "test") }}"#, ext = "html")] +struct NamedArgsInRustExprStaticCall2; + + +#[derive(Template)] +#[template(source = r#"{% let test = self::static_fn(arg = "test") %}"#, ext = "html")] +struct NamedArgsInRustExprStaticCall3; + + +fn main() {} diff --git a/testing/tests/ui/expr_fn_calls.stderr b/testing/tests/ui/expr_fn_calls.stderr new file mode 100644 index 00000000..5d763723 --- /dev/null +++ b/testing/tests/ui/expr_fn_calls.stderr @@ -0,0 +1,31 @@ +error: Unsupported use of named arguments + --> NamedArgsInRustExprMemberFn.html:1:10 + "(arg = 5) }}" + --> tests/ui/expr_fn_calls.rs:10:21 + | +10 | #[template(source = "{{ test_fn(arg = 5) }}", ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Unsupported use of named arguments + --> NamedArgsInRustExprStaticCall.html:1:18 + "(arg = \"test\") }}" + --> tests/ui/expr_fn_calls.rs:21:21 + | +21 | #[template(source = r#"{{ self::static_fn(arg = "test") }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Unsupported use of named arguments + --> NamedArgsInRustExprStaticCall2.html:1:34 + "(arg = \"test\") }}" + --> tests/ui/expr_fn_calls.rs:26:21 + | +26 | #[template(source = r#"{{ self::static_fn2("test").unwrap(arg = "test") }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Unsupported use of named arguments + --> NamedArgsInRustExprStaticCall3.html:1:29 + "(arg = \"test\") %}" + --> tests/ui/expr_fn_calls.rs:31:21 + | +31 | #[template(source = r#"{% let test = self::static_fn(arg = "test") %}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^