55
66use std:: ops:: ControlFlow ;
77
8- use proc_macro2:: { Ident , Span , TokenStream } ;
8+ use proc_macro2:: { Ident , Span , TokenStream , TokenTree } ;
99use quote:: { ToTokens , format_ident, quote, quote_spanned} ;
1010use syn:: punctuated:: Punctuated ;
1111use syn:: spanned:: Spanned ;
@@ -79,19 +79,45 @@ struct FilterArgumentOptional {
7979 default : Expr ,
8080}
8181
82+ /// Internal representation for a filter function's lifetime.
83+ #[ derive( Clone ) ]
84+ struct FilterLifetime {
85+ lifetime : Lifetime ,
86+ bounds : Punctuated < Lifetime , Token ! [ +] > ,
87+ used_by_extra_args : bool ,
88+ }
89+
8290/// Internal representation for a filter function's generic argument.
8391#[ derive( Clone ) ]
8492struct FilterArgumentGeneric {
8593 ident : Ident ,
8694 bounds : Punctuated < TypeParamBound , Token ! [ +] > ,
8795}
8896
97+ fn get_lifetimes ( stream : TokenStream , lifetimes : & mut HashSet < Ident > ) {
98+ let mut iterator = stream. into_iter ( ) . peekable ( ) ;
99+ while let Some ( token) = iterator. next ( ) {
100+ match token {
101+ TokenTree :: Group ( g) => get_lifetimes ( g. stream ( ) , lifetimes) ,
102+ TokenTree :: Punct ( p) if p. as_char ( ) == '\'' => {
103+ // Lifetimes are represented as `[Punct('), Ident("a")]` in the `TokenStream`.
104+ if let Some ( TokenTree :: Ident ( i) ) = iterator. peek ( ) {
105+ lifetimes. insert ( i. clone ( ) ) ;
106+ }
107+ }
108+ TokenTree :: Punct ( _) | TokenTree :: Ident ( _) | TokenTree :: Literal ( _) => continue ,
109+ }
110+ }
111+ }
112+
89113/// A freestanding method annotated with `askama::filter_fn` is parsed into an instance of this
90114/// struct, and then the resulting code is generated from there.
91115/// This struct serves as an intermediate representation after some preprocessing on the raw AST.
92116struct FilterSignature {
93117 /// Name of the annotated freestanding filter function
94118 ident : Ident ,
119+ /// Lifetime bounds.
120+ lifetimes : Vec < FilterLifetime > ,
95121 /// Name of the input variable
96122 arg_input : FilterArgumentRequired ,
97123 /// Name of the askama environment variable
@@ -127,9 +153,6 @@ impl FilterSignature {
127153 if let Some ( gc_arg) = sig. generics . const_params ( ) . next ( ) {
128154 p_err ! ( gc_arg. span( ) => "Const generics are currently not supported for filters" ) ?;
129155 }
130- if let Some ( gl_arg) = sig. generics . lifetimes ( ) . next ( ) {
131- p_err ! ( gl_arg. span( ) => "Lifetime generics are currently not supported for filters" ) ?;
132- }
133156 p_assert ! (
134157 matches!( sig. output, ReturnType :: Type ( _, _) ) ,
135158 sig. paren_token. span. close( ) => "Filter function is missing return type"
@@ -161,6 +184,7 @@ impl FilterSignature {
161184 let mut args_required = vec ! [ ] ;
162185 let mut args_optional = vec ! [ ] ;
163186 let mut args_required_generics = HashMap :: default ( ) ;
187+ let mut lifetimes_used_in_non_required = HashSet :: default ( ) ;
164188 for ( arg_idx, arg) in sig. inputs . iter ( ) . skip ( 2 ) . enumerate ( ) {
165189 let FnArg :: Typed ( arg) = arg else {
166190 continue ;
@@ -172,6 +196,7 @@ impl FilterSignature {
172196 !matches!( * arg. ty, Type :: ImplTrait ( _) ) ,
173197 arg. ty. span( ) => "Impl generics are currently not supported for filters"
174198 ) ?;
199+ get_lifetimes ( arg. to_token_stream ( ) , & mut lifetimes_used_in_non_required) ;
175200
176201 // reference-parameters without explicit lifetime, inherit the 'filter lifetime
177202 let arg_type = patch_ref_with_lifetime ( & arg. ty , & format_ident ! ( "filter" ) ) ;
@@ -220,11 +245,27 @@ impl FilterSignature {
220245 }
221246 }
222247 }
248+ // lifetimes
249+ let lifetimes = sig
250+ . generics
251+ . lifetimes ( )
252+ . map ( |lt| {
253+ let lifetime = lt. lifetime . clone ( ) ;
254+ let bounds = lt. bounds . clone ( ) ;
255+ let used_by_extra_args = lifetimes_used_in_non_required. contains ( & lifetime. ident ) ;
256+ FilterLifetime {
257+ lifetime,
258+ bounds,
259+ used_by_extra_args,
260+ }
261+ } )
262+ . collect :: < Vec < _ > > ( ) ;
223263
224264 // ########################################
225265
226266 Ok ( FilterSignature {
227267 ident : sig. ident . clone ( ) ,
268+ lifetimes,
228269 arg_input,
229270 arg_input_generics,
230271 arg_env,
@@ -284,6 +325,36 @@ impl FilterSignature {
284325// code generation
285326// ##############################################################################################
286327impl FilterSignature {
328+ /// Returns a tuple containing two items:
329+ ///
330+ /// 1. The list of lifetimes with their bounds.
331+ /// 2. The list of lifetimes without their bounds.
332+ fn lifetimes_bounds < F : Fn ( & FilterLifetime ) -> bool > (
333+ & self ,
334+ filter : F ,
335+ ) -> ( Vec < TokenStream > , Vec < & Lifetime > ) {
336+ let mut lifetimes = Vec :: with_capacity ( self . lifetimes . len ( ) ) ;
337+ let mut lifetimes_no_bounds = Vec :: with_capacity ( self . lifetimes . len ( ) ) ;
338+ for lt in & self . lifetimes {
339+ if !filter ( lt) {
340+ continue ;
341+ }
342+ let name = & lt. lifetime ;
343+ let bounds = & lt. bounds ;
344+ lifetimes. push ( quote ! { #name: #bounds } ) ;
345+ lifetimes_no_bounds. push ( name) ;
346+ }
347+ ( lifetimes, lifetimes_no_bounds)
348+ }
349+
350+ fn lifetimes_fillers < F : Fn ( & FilterLifetime ) -> bool > ( & self , filter : F ) -> Vec < TokenStream > {
351+ self . lifetimes
352+ . iter ( )
353+ . filter ( |l| filter ( l) )
354+ . map ( |_| quote ! { ' _ } )
355+ . collect ( )
356+ }
357+
287358 /// Generates a struct named after the filter function.
288359 /// This struct will contain all the filter's arguments (except input and env).
289360 /// The struct is basically a builder pattern for the custom filter arguments.
@@ -325,16 +396,18 @@ impl FilterSignature {
325396 let required_arg_cnt = self . args_required . len ( ) ;
326397 let optional_arg_cnt = self . args_optional . len ( ) ;
327398 let arg_cnt = required_arg_cnt + optional_arg_cnt;
399+ let lifetimes_fillers = self . lifetimes_fillers ( |l| l. used_by_extra_args ) ;
328400 let valid_arg_impls = ( 0 ..arg_cnt) . map ( |idx| {
329401 quote ! {
330402 #[ diagnostic:: do_not_recommend]
331- impl askama:: filters:: ValidArgIdx <#idx> for #ident<' _> { }
403+ impl askama:: filters:: ValidArgIdx <#idx> for #ident<' _, # ( #lifetimes_fillers , ) * > { }
332404 }
333405 } ) ;
334406
407+ let ( _, lifetimes) = self . lifetimes_bounds ( |l| l. used_by_extra_args ) ;
335408 quote ! {
336409 #[ allow( non_camel_case_types) ]
337- #vis struct #ident<' filter, #( #struct_generics = ( ) , ) * #( const #required_flags : bool = false , ) * > {
410+ #vis struct #ident<' filter, #( #lifetimes , ) * # ( # struct_generics = ( ) , ) * #( const #required_flags : bool = false , ) * > {
338411 _lifetime: std:: marker:: PhantomData <& ' filter ( ) >,
339412 /* required fields */
340413 #( #required_fields, ) *
@@ -366,9 +439,10 @@ impl FilterSignature {
366439 let value = & a. default ;
367440 quote ! { #ident: #value }
368441 } ) ;
442+ let lifetimes_fillers = self . lifetimes_fillers ( |l| l. used_by_extra_args ) ;
369443
370444 quote ! {
371- impl std:: default :: Default for #ident<' _> {
445+ impl std:: default :: Default for #ident<' _, # ( #lifetimes_fillers , ) * > {
372446 fn default ( ) -> Self {
373447 Self {
374448 _lifetime: std:: marker:: PhantomData :: default ( ) ,
@@ -441,6 +515,7 @@ impl FilterSignature {
441515 quote ! { #ident: #bounds }
442516 } )
443517 . collect ( ) ;
518+ let ( _, lifetimes_no_bounds) = self . lifetimes_bounds ( |l| l. used_by_extra_args ) ;
444519 // return type
445520 let fn_return_ty = {
446521 let required_generics_result =
@@ -456,7 +531,7 @@ impl FilterSignature {
456531 false => format_ident ! ( "REQUIRED_ARG_FLAG_{}" , a. idx) . to_token_stream ( ) ,
457532 }
458533 } ) ;
459- quote ! { #ident<' filter, #( #required_generics_result, ) * #( #required_flags_result, ) * > }
534+ quote ! { #ident<' filter, #( #lifetimes_no_bounds , ) * # ( # required_generics_result, ) * #( #required_flags_result, ) * > }
460535 } ;
461536 // struct fields - (all fields, except that of current argument)
462537 let other_required_fields = self
@@ -469,8 +544,8 @@ impl FilterSignature {
469544
470545 quote ! {
471546 #[ allow( non_camel_case_types) ]
472- impl <' filter, #( #required_generics_impl, ) * #( const #required_flags: bool , ) * >
473- #ident<' filter, #( #required_generics_impl, ) * #( #required_flags, ) * > {
547+ impl <' filter, #( #lifetimes_no_bounds , ) * # ( # required_generics_impl, ) * #( const #required_flags: bool , ) * >
548+ #ident<' filter, #( #lifetimes_no_bounds , ) * # ( # required_generics_impl, ) * #( #required_flags, ) * > {
474549 // named setter
475550 #[ inline( always) ]
476551 pub fn #named_ident<#( #required_generics_fn, ) * >( self , new_value: #cur_arg_ty) -> #fn_return_ty {
@@ -530,10 +605,11 @@ impl FilterSignature {
530605 }
531606 } ) ;
532607
608+ let ( _, lifetimes_no_bounds) = self . lifetimes_bounds ( |l| l. used_by_extra_args ) ;
533609 quote ! {
534610 #[ allow( non_camel_case_types) ]
535- impl <' filter, #( #required_generics, ) * #( const #required_flags: bool , ) * >
536- #ident<' filter, #( #required_generics, ) * #( #required_flags, ) * > {
611+ impl <' filter, #( #lifetimes_no_bounds , ) * # ( # required_generics, ) * #( const #required_flags: bool , ) * >
612+ #ident<' filter, #( #lifetimes_no_bounds , ) * # ( # required_generics, ) * #( #required_flags, ) * > {
537613 #( #optional_setters) *
538614 }
539615 }
@@ -565,6 +641,8 @@ impl FilterSignature {
565641 let bounds = & g. bounds ;
566642 quote ! { #ident: #bounds }
567643 } ) ;
644+ let ( all_lifetimes, _) = self . lifetimes_bounds ( |_| true ) ;
645+ let ( _, type_lifetimes) = self . lifetimes_bounds ( |l| l. used_by_extra_args ) ;
568646 // env variable
569647 let env_ident = & self . arg_env . ident ;
570648 let env_ty = & self . arg_env . ty ;
@@ -596,13 +674,14 @@ impl FilterSignature {
596674 } ) ;
597675
598676 let impl_generics = quote ! { #( #required_generics: #required_generic_bounds, ) * } ;
599- let impl_struct_generics = quote ! { ' _, #( #required_generics, ) * #( #required_flags, ) * } ;
677+ let impl_struct_generics = quote ! { #( #required_generics, ) * #( #required_flags, ) * } ;
678+ let lifetimes_fillers = self . lifetimes_fillers ( |l| l. used_by_extra_args ) ;
600679 quote ! {
601680 // if all required arguments have been supplied (P0 == true, P1 == true)
602681 // ... the execute() method is "unlocked":
603- impl <#impl_generics> #ident<#impl_struct_generics> {
682+ impl <#( #all_lifetimes , ) * # impl_generics> #ident<' _ , # ( #type_lifetimes , ) * #impl_struct_generics> {
604683 #[ inline( always) ]
605- pub fn execute<#( #input_bounds, ) * >( self , #input_mutability #input_ident: #input_ty, #env_ident: #env_ty) #result_ty {
684+ pub fn execute< #( #input_bounds, ) * >( self , #input_mutability #input_ident: #input_ty, #env_ident: #env_ty) #result_ty {
606685 // map filter variables with original name into scope
607686 #( #required_args ) *
608687 #( #optional_args ) *
@@ -611,7 +690,7 @@ impl FilterSignature {
611690 }
612691 }
613692
614- impl <#impl_generics> askama:: filters:: ValidFilterInvocation for #ident<#impl_struct_generics> { }
693+ impl <#impl_generics> askama:: filters:: ValidFilterInvocation for #ident<' _ , # ( #lifetimes_fillers , ) * #impl_struct_generics> { }
615694 }
616695 }
617696}
@@ -626,12 +705,12 @@ fn filter_fn_impl(attr: TokenStream, ffn: &ItemFn) -> Result<TokenStream, Compil
626705
627706 let fsig = FilterSignature :: try_from_signature ( & ffn. sig ) ?;
628707
629- let mut arg_generics = HashMap :: default ( ) ;
630708 for gp in & ffn. sig . generics . params {
631- if let GenericParam :: Type ( gp) = gp {
632- arg_generics. insert ( gp. ident . clone ( ) , gp. clone ( ) ) ;
633- } else {
634- p_err ! ( gp. span( ) => "Only type generic arguments supported for now" ) ?;
709+ match gp {
710+ GenericParam :: Type ( _) | GenericParam :: Lifetime ( _) => { }
711+ GenericParam :: Const ( _) => {
712+ p_err ! ( gp. span( ) => "Const generic arguments are not supported for now" ) ?;
713+ }
635714 }
636715 }
637716
0 commit comments