Skip to content

Commit bd6bf17

Browse files
Add support for macro args type annotations
1 parent 7e42946 commit bd6bf17

7 files changed

Lines changed: 209 additions & 32 deletions

File tree

askama_derive/src/generator/expr.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -509,14 +509,17 @@ impl<'a> Generator<'a, '_> {
509509
buf: &mut Buffer,
510510
generics: &WithSpan<Vec<WithSpan<TyGenerics<'a>>>>,
511511
) {
512-
let mut tmp = Buffer::new();
512+
if generics.is_empty() {
513+
return;
514+
}
515+
let generics_span = ctx.span_for_node(generics.span());
516+
buf.write_token(Token![<], generics_span);
513517
for generic in &**generics {
514518
let span = ctx.span_for_node(generic.span());
515-
self.visit_ty_generic(ctx, &mut tmp, generic, span);
516-
tmp.write_token(Token![,], span);
519+
self.visit_ty_generic(ctx, buf, generic, span);
520+
buf.write_token(Token![,], span);
517521
}
518-
let tmp = tmp.into_token_stream();
519-
quote_into!(buf, ctx.span_for_node(generics.span()), { <#tmp> });
522+
buf.write_token(Token![>], generics_span);
520523
}
521524

522525
pub(super) fn visit_ty_generic(
@@ -530,7 +533,7 @@ impl<'a> Generator<'a, '_> {
530533
for _ in 0..refs {
531534
buf.write_token(Token![&], span);
532535
}
533-
match kind {
536+
match &**kind {
534537
TyGenericsKind::Path { path, args } => {
535538
self.visit_macro_path(buf, path, span);
536539
if let Some(generics) = args.as_ref() {

askama_derive/src/generator/helpers/macro_invocation.rs

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ use core::fmt;
22
use std::borrow::Cow;
33
use std::fmt::Write;
44
use std::mem;
5+
use std::str::FromStr;
56

6-
use parser::node::{Call, Macro, Ws};
7+
use parser::node::{Call, Macro, MacroArg, Ws};
78
use parser::{Expr, Span, WithSpan};
9+
use proc_macro2::TokenStream;
810
use quote::quote_spanned;
911

1012
use crate::generator::node::AstLevel;
@@ -95,19 +97,37 @@ impl<'a, 'b> MacroInvocation<'a, 'b> {
9597
fn handle_macro_arg<'h>(
9698
&self,
9799
expr: &WithSpan<Box<Expr<'a>>>,
98-
arg_name: &WithSpan<&'a str>,
100+
arg: &MacroArg<'a>,
99101
buf: &mut Buffer,
100102
generator: &mut Generator<'a, 'h>,
101103
) -> Result<(), CompileError> {
104+
let mut ty_buf = Buffer::new();
105+
let span = self.callsite_ctx.span_for_node(arg.name.span());
106+
if let Some(ref ty) = arg.ty {
107+
ty_buf.write_token(syn::Token![:], span);
108+
// To prevent moving the value/variable, we take a reference of it.
109+
ty_buf.write_token(syn::Token![&], span);
110+
generator.visit_ty_generic(self.callsite_ctx, &mut ty_buf, ty, span);
111+
}
112+
102113
match &***expr {
103114
// If `expr` is already a form of variable then
104115
// don't reintroduce a new variable. This is
105116
// to avoid moving non-copyable values.
106117
&Expr::Var(name) if name != "self" => {
107118
let var = generator.locals.resolve_or_self(name);
108-
generator
109-
.locals
110-
.insert(Cow::Borrowed(**arg_name), LocalMeta::var_with_ref(var));
119+
if arg.ty.is_none() {
120+
generator
121+
.locals
122+
.insert(Cow::Borrowed(*arg.name), LocalMeta::var_with_ref(var));
123+
} else {
124+
let id = field_new(&arg.name, span);
125+
let var = TokenStream::from_str(&var).expect("invalid variable name");
126+
buf.write_tokens(quote_spanned! { span => let #id #ty_buf = &#var; });
127+
generator
128+
.locals
129+
.insert_with_default(Cow::Borrowed(*arg.name));
130+
}
111131
}
112132
Expr::AssociatedItem(obj, associated_item) => {
113133
let mut associated_item_buf = Buffer::new();
@@ -125,9 +145,18 @@ impl<'a, 'b> MacroInvocation<'a, 'b> {
125145
.locals
126146
.resolve(&associated_item)
127147
.unwrap_or(associated_item);
128-
generator
129-
.locals
130-
.insert(Cow::Borrowed(**arg_name), LocalMeta::var_with_ref(var));
148+
if arg.ty.is_none() {
149+
generator
150+
.locals
151+
.insert(Cow::Borrowed(*arg.name), LocalMeta::var_with_ref(var));
152+
} else {
153+
let id = field_new(&arg.name, span);
154+
let var = TokenStream::from_str(&var).expect("invalid variable name");
155+
buf.write_tokens(quote_spanned! { span => let #id #ty_buf = &#var; });
156+
generator
157+
.locals
158+
.insert_with_default(Cow::Borrowed(*arg.name));
159+
}
131160
}
132161
// Everything else still needs to become variables,
133162
// to avoid having the same logic be executed
@@ -136,17 +165,16 @@ impl<'a, 'b> MacroInvocation<'a, 'b> {
136165
_ => {
137166
let mut value = Buffer::new();
138167
value.write_tokens(generator.visit_expr_root(self.callsite_ctx, expr)?);
139-
let span = self.callsite_ctx.span_for_node(arg_name.span());
140-
let id = field_new(arg_name, span);
141-
buf.write_tokens(if !is_copyable(expr) {
142-
quote_spanned! { span => let #id = &(#value); }
168+
let id = field_new(&arg.name, span);
169+
buf.write_tokens(if !is_copyable(expr) || arg.ty.is_some() {
170+
quote_spanned! { span => let #id #ty_buf = &(#value); }
143171
} else {
144-
quote_spanned! { span => let #id = #value; }
172+
quote_spanned! { span => let #id #ty_buf = #value; }
145173
});
146174

147175
generator
148176
.locals
149-
.insert_with_default(Cow::Borrowed(**arg_name));
177+
.insert_with_default(Cow::Borrowed(*arg.name));
150178
}
151179
}
152180

@@ -216,7 +244,7 @@ impl<'a, 'b> MacroInvocation<'a, 'b> {
216244
}
217245
}
218246
};
219-
self.handle_macro_arg(expr, &arg.name, buf, generator)?;
247+
self.handle_macro_arg(expr, arg, buf, generator)?;
220248
}
221249

222250
Ok(())

askama_parser/src/expr.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ macro_rules! expr_prec_layer {
2323
};
2424
}
2525

26+
const MAX_REFS: usize = 20;
27+
2628
fn expr_prec_layer<'a: 'l, 'l>(
2729
i: &mut InputStream<'a, 'l>,
2830
inner: fn(&mut InputStream<'a, 'l>) -> ParseResult<'a, WithSpan<Box<Expr<'a>>>>,
@@ -1495,19 +1497,24 @@ fn ensure_macro_name<'a>(name: &WithSpan<&'a str>) -> ParseResult<'a, ()> {
14951497
#[derive(Clone, Debug, PartialEq)]
14961498
pub struct TyGenerics<'a> {
14971499
pub refs: usize,
1498-
pub kind: TyGenericsKind<'a>,
1500+
pub kind: WithSpan<TyGenericsKind<'a>>,
14991501
}
15001502

15011503
impl<'a: 'l, 'l> TyGenerics<'a> {
1502-
fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, WithSpan<Self>> {
1503-
let p = ws((repeat(0.., ws('&')), TyGenericsKind::parse));
1504-
let ((refs, kind), span) = p.with_span().parse_next(i)?;
1505-
let max_refs = 20;
1506-
if refs > max_refs {
1507-
return cut_error!(format!("too many references (> {max_refs})"), span);
1504+
pub(crate) fn parse(i: &mut InputStream<'a, 'l>) -> ParseResult<'a, WithSpan<Self>> {
1505+
let p = ws((repeat(0.., ws('&')), TyGenericsKind::parse.with_span()));
1506+
let ((refs, (kind, kind_span)), span) = p.with_span().parse_next(i)?;
1507+
if refs > MAX_REFS {
1508+
return cut_error!(format!("too many references (> {MAX_REFS})"), span);
15081509
}
15091510

1510-
Ok(WithSpan::new(TyGenerics { refs, kind }, span))
1511+
Ok(WithSpan::new(
1512+
TyGenerics {
1513+
refs,
1514+
kind: WithSpan::new(kind, kind_span),
1515+
},
1516+
span,
1517+
))
15111518
}
15121519

15131520
fn args(

askama_parser/src/node.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use winnow::{ModalParser, Parser};
1212
use crate::expr::BinOp;
1313
use crate::{
1414
ErrorContext, Expr, Filter, HashSet, InputStream, ParseErr, ParseResult, Span, Target,
15-
WithSpan, block_end, block_start, cut_error, deny_any_rust_token, expr_end, expr_start, filter,
16-
identifier, is_rust_keyword, keyword, skip_ws0, str_lit_without_prefix, ws,
15+
TyGenerics, WithSpan, block_end, block_start, cut_error, deny_any_rust_token, expr_end,
16+
expr_start, filter, identifier, is_rust_keyword, keyword, skip_ws0, str_lit_without_prefix, ws,
1717
};
1818

1919
#[derive(Debug, PartialEq)]
@@ -655,6 +655,7 @@ pub struct Macro<'a> {
655655
#[derive(Debug, PartialEq)]
656656
pub struct MacroArg<'a> {
657657
pub name: WithSpan<&'a str>,
658+
pub ty: Option<WithSpan<TyGenerics<'a>>>,
658659
pub default: Option<WithSpan<Box<Expr<'a>>>>,
659660
}
660661

@@ -690,11 +691,13 @@ impl<'a: 'l, 'l> Macro<'a> {
690691
let macro_arg = |i: &mut _| {
691692
let mut p = (
692693
ws(identifier.with_span()),
694+
opt(preceded(':', ws(|i: &mut _| TyGenerics::parse(i)))),
693695
opt(preceded('=', ws(|i: &mut _| Expr::parse(i, false)))),
694696
);
695-
let ((name, name_span), default) = p.parse_next(i)?;
697+
let ((name, name_span), ty, default) = p.parse_next(i)?;
696698
Ok(MacroArg {
697699
name: WithSpan::new(name, name_span),
700+
ty,
698701
default,
699702
})
700703
};

testing/tests/macro.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,3 +705,103 @@ val4: {{val4}}
705705
"val1: aa\nval2: x\nval3: default\nval4: c"
706706
);
707707
}
708+
709+
// Goal of these tests is to ensure that the argument info is working as expected.
710+
#[test]
711+
fn test_macro_with_args_type_info() {
712+
#[derive(Template)]
713+
#[template(
714+
source = r#"
715+
{%- macro test(title: Option<u32> = None) -%}
716+
-> {{title|fmt("{:?}")}}
717+
{% endmacro -%}
718+
719+
{% let y = Some(12) -%}
720+
{% call test(y) %}{% endcall -%}
721+
{% call test(x) %}{% endcall -%}
722+
{% call test() %}{% endcall -%}
723+
"#,
724+
ext = "txt"
725+
)]
726+
struct F {
727+
x: Option<u32>,
728+
}
729+
730+
assert_eq!(
731+
F { x: Some(4) }.render().unwrap(),
732+
"-> Some(12)\n-> Some(4)\n-> None\n"
733+
);
734+
}
735+
736+
#[test]
737+
fn test_macro_with_args_type_info2() {
738+
#[derive(Template)]
739+
#[template(
740+
source = r#"
741+
{%- macro test(entries: &[u32]) -%}
742+
{%- for entry in entries -%}
743+
-> {{entry}}
744+
{% endfor -%}
745+
{% endmacro -%}
746+
{{- test(x.as_slice()) -}}
747+
"#,
748+
ext = "txt"
749+
)]
750+
struct F {
751+
x: Vec<u32>,
752+
}
753+
754+
assert_eq!(F { x: vec![4, 2] }.render().unwrap(), "-> 4\n-> 2\n");
755+
}
756+
757+
#[test]
758+
fn test_macro_with_args_type_info3() {
759+
#[derive(Template)]
760+
#[template(
761+
source = r#"
762+
{%- macro test(entries: std::vec::Vec<u32>) -%}
763+
{%- for entry in entries -%}
764+
-> {{entry}}
765+
{% endfor -%}
766+
{% endmacro -%}
767+
{{- test(x) -}}
768+
"#,
769+
ext = "txt"
770+
)]
771+
struct F {
772+
x: Vec<u32>,
773+
}
774+
assert_eq!(F { x: vec![4, 2] }.render().unwrap(), "-> 4\n-> 2\n");
775+
}
776+
777+
#[test]
778+
fn test_macro_with_args_type_info4() {
779+
#[derive(Template)]
780+
#[template(
781+
source = r#"
782+
{%- macro test(entries: &[Vec<u32>]) -%}
783+
{%- for entry in entries -%}
784+
+>
785+
{%- for sub_entry in entry -%}
786+
-> {{sub_entry}}
787+
{% endfor -%}
788+
{% endfor -%}
789+
{% endmacro -%}
790+
791+
{{- test(x.as_slice()) -}}
792+
"#,
793+
ext = "txt"
794+
)]
795+
struct F {
796+
x: Vec<Vec<u32>>,
797+
}
798+
799+
assert_eq!(
800+
F {
801+
x: vec![vec![4, 2], vec![5, 1]]
802+
}
803+
.render()
804+
.unwrap(),
805+
"+>-> 4\n-> 2\n+>-> 5\n-> 1\n",
806+
);
807+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use askama::Template;
2+
3+
#[derive(Template)]
4+
#[template(
5+
source = r#"
6+
{%- macro test(title: Option<u32> = None) -%}{% endmacro -%}
7+
{%- let y = 12 -%}
8+
{% call test(y) %}{% endcall -%}
9+
{% call test(x) %}{% endcall -%}
10+
"#,
11+
ext = "txt",
12+
)]
13+
struct F {
14+
x: u32,
15+
}
16+
17+
fn main() {}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
error[E0308]: mismatched types
2+
--> tests/ui/macro-args-with-type.rs:3:10
3+
|
4+
3 | #[derive(Template)]
5+
| ^^^^^^^^ expected `&Option<u32>`, found `&{integer}`
6+
|
7+
= note: expected reference `&Option<u32>`
8+
found reference `&{integer}`
9+
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
10+
11+
error[E0308]: mismatched types
12+
--> tests/ui/macro-args-with-type.rs:3:10
13+
|
14+
3 | #[derive(Template)]
15+
| ^^^^^^^^ expected `&Option<u32>`, found `&u32`
16+
|
17+
= note: expected reference `&Option<u32>`
18+
found reference `&u32`
19+
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)

0 commit comments

Comments
 (0)