aboutsummaryrefslogtreecommitdiffstats
path: root/askama_derive/src/generator.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--askama_derive/src/generator.rs56
1 files changed, 55 insertions, 1 deletions
diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs
index c6c5d10..65136c6 100644
--- a/askama_derive/src/generator.rs
+++ b/askama_derive/src/generator.rs
@@ -727,7 +727,50 @@ impl<'a> Generator<'a> {
args.len()
)));
}
- for (expr, arg) in std::iter::zip(args, &def.args) {
+ let mut named_arguments = HashMap::new();
+ // 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() {
+ // First we check that all named arguments actually exist in the called item.
+ for arg in args.iter().rev() {
+ let Expr::NamedArgument(arg_name, _) = arg else {
+ break;
+ };
+ if !def.args.iter().any(|arg| arg == arg_name) {
+ return Err(CompileError::from(format!(
+ "no argument named `{arg_name}` in macro {name:?}"
+ )));
+ }
+ named_arguments.insert(arg_name, arg);
+ }
+ }
+
+ // 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:
+ //
+ // * If there is one, we add it and move to the next argument.
+ // * 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 = match named_arguments.get(&arg) {
+ Some(expr) => {
+ allow_positional = false;
+ expr
+ }
+ None => {
+ 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(CompileError::from(format!(
+ "cannot have unnamed argument (`{arg}`) after named argument in macro \
+ {name:?}"
+ )));
+ }
+ &args[index]
+ }
+ };
match expr {
// If `expr` is already a form of variable then
// don't reintroduce a new variable. This is
@@ -1104,6 +1147,7 @@ impl<'a> Generator<'a> {
Expr::RustMacro(ref path, args) => self.visit_rust_macro(buf, path, args),
Expr::Try(ref expr) => self.visit_try(buf, expr.as_ref())?,
Expr::Tuple(ref exprs) => self.visit_tuple(buf, exprs)?,
+ Expr::NamedArgument(_, ref expr) => self.visit_named_argument(buf, expr)?,
})
}
@@ -1504,6 +1548,15 @@ impl<'a> Generator<'a> {
Ok(DisplayWrap::Unwrapped)
}
+ fn visit_named_argument(
+ &mut self,
+ buf: &mut Buffer,
+ expr: &Expr<'_>,
+ ) -> Result<DisplayWrap, CompileError> {
+ self.visit_expr(buf, expr)?;
+ Ok(DisplayWrap::Unwrapped)
+ }
+
fn visit_array(
&mut self,
buf: &mut Buffer,
@@ -1923,6 +1976,7 @@ pub(crate) fn is_cacheable(expr: &Expr<'_>) -> bool {
}
Expr::Group(arg) => is_cacheable(arg),
Expr::Tuple(args) => args.iter().all(is_cacheable),
+ Expr::NamedArgument(_, expr) => is_cacheable(expr),
// We have too little information to tell if the expression is pure:
Expr::Call(_, _) => false,
Expr::RustMacro(_, _) => false,