@@ -10,7 +10,7 @@ use parser::node::{
1010 Call , Comment , Compound , Cond , CondTest , Declare , FilterBlock , If , Include , Let , Lit , Loop ,
1111 Match , Whitespace , Ws ,
1212} ;
13- use parser:: { Expr , Node , Span , Target , WithSpan } ;
13+ use parser:: { Expr , LetValueOrBlock , Node , Span , Target , WithSpan } ;
1414use proc_macro2:: TokenStream ;
1515use quote:: quote_spanned;
1616use rustc_hash:: FxBuildHasher ;
@@ -82,7 +82,7 @@ impl<'a> Generator<'a, '_> {
8282 heritage,
8383 locals,
8484 self . buf_writable . discard ,
85- self . is_in_filter_block ,
85+ self . is_in_block ,
8686 ) ;
8787 child. buf_writable = buf_writable;
8888 let res = callback ( & mut child) ;
@@ -727,7 +727,7 @@ impl<'a> Generator<'a, '_> {
727727
728728 let mut size_hint = self . write_buf_writable ( ctx, buf) ?;
729729 self . flush_ws ( filter. ws1 ) ;
730- self . is_in_filter_block += 1 ;
730+ self . is_in_block . increase ( "filter" ) ;
731731 size_hint += self . write_buf_writable ( ctx, buf) ?;
732732 let span = ctx. span_for_node ( filter. span ( ) ) ;
733733
@@ -785,7 +785,7 @@ impl<'a> Generator<'a, '_> {
785785 }
786786 } } ) ;
787787
788- self . is_in_filter_block -= 1 ;
788+ self . is_in_block . decrease ( ) ;
789789 self . prepare_ws ( filter. ws2 ) ;
790790 Ok ( size_hint)
791791 }
@@ -926,47 +926,25 @@ impl<'a> Generator<'a, '_> {
926926
927927 fn write_let (
928928 & mut self ,
929- ctx : & Context < ' _ > ,
929+ ctx : & Context < ' a > ,
930930 buf : & mut Buffer ,
931931 l : & ' a WithSpan < Let < ' _ > > ,
932932 ) -> Result < SizeHint , CompileError > {
933933 self . handle_ws ( l. ws ) ;
934- let span = ctx. span_for_node ( l. span ( ) ) ;
935-
936- let Some ( val) = & l. val else {
937- let file_info = ctx
938- . file_info_of ( l. span ( ) )
939- . map ( |info| format ! ( " {info}:" ) )
940- . unwrap_or_default ( ) ;
941- eprintln ! (
942- "⚠️{file_info} `let` tag will stop supporting declaring variables without value. \
943- Use `create` instead for this case",
944- ) ;
945- let size_hint = self . write_buf_writable ( ctx, buf) ?;
946- buf. write_token ( Token ! [ let ] , span) ;
947- if l. is_mutable {
948- buf. write_token ( Token ! [ mut ] , span) ;
949- }
950- self . visit_target ( ctx, buf, false , true , & l. var , span) ;
951- buf. write_token ( Token ! [ ; ] , span) ;
952- return Ok ( size_hint) ;
953- } ;
954934
955- // Handle when this statement creates a new alias of a caller variable (or of another alias),
956- if let Target :: Name ( dstvar) = l. var
957- && let Expr :: Var ( srcvar) = * * * val
958- && let Some ( caller_alias) = self . locals . get_caller ( srcvar)
959- {
960- self . locals . insert (
961- Cow :: Borrowed ( * dstvar) ,
962- LocalMeta :: CallerAlias ( caller_alias. clone ( ) ) ,
963- ) ;
964- return Ok ( SizeHint :: EMPTY ) ;
935+ match & l. val {
936+ LetValueOrBlock :: Value ( val) => self . write_let_value ( ctx, buf, l, val) ,
937+ LetValueOrBlock :: Block { nodes, ws } => self . write_let_block ( ctx, buf, l, nodes, * ws) ,
965938 }
939+ }
966940
967- let mut expr_buf = Buffer :: new ( ) ;
968- self . visit_expr ( ctx, & mut expr_buf, val) ?;
969-
941+ fn write_let_target (
942+ & mut self ,
943+ ctx : & Context < ' _ > ,
944+ buf : & mut Buffer ,
945+ l : & ' a WithSpan < Let < ' _ > > ,
946+ span : proc_macro2:: Span ,
947+ ) -> Result < SizeHint , CompileError > {
970948 let shadowed = self . is_shadowing_variable ( ctx, & l. var , l. span ( ) ) ?;
971949 let size_hint = if shadowed {
972950 // Need to flush the buffer if the variable is being shadowed,
@@ -986,6 +964,104 @@ impl<'a> Generator<'a, '_> {
986964 }
987965
988966 self . visit_target ( ctx, buf, true , true , & l. var , span) ;
967+ Ok ( size_hint)
968+ }
969+
970+ fn write_let_block (
971+ & mut self ,
972+ ctx : & Context < ' a > ,
973+ buf : & mut Buffer ,
974+ l : & ' a WithSpan < Let < ' _ > > ,
975+ nodes : & ' a [ Box < Node < ' a > > ] ,
976+ ws : Ws ,
977+ ) -> Result < SizeHint , CompileError > {
978+ let var_let_source = crate :: var_let_source ( ) ;
979+
980+ let mut size_hint = self . write_buf_writable ( ctx, buf) ?;
981+ self . flush_ws ( l. ws ) ;
982+ self . is_in_block . increase ( "let/set" ) ;
983+ size_hint += self . write_buf_writable ( ctx, buf) ?;
984+ let span = ctx. span_for_node ( l. span ( ) ) ;
985+
986+ // build `FmtCell` that contains the inner block
987+ let mut filter_def_buf = Buffer :: new ( ) ;
988+ size_hint += self . push_locals ( |this| {
989+ this. prepare_ws ( l. ws ) ;
990+ let mut size_hint = this. handle (
991+ ctx,
992+ nodes,
993+ & mut filter_def_buf,
994+ AstLevel :: Nested ,
995+ RenderFor :: Template ,
996+ ) ?;
997+ this. flush_ws ( ws) ;
998+ size_hint += this. write_buf_writable ( ctx, & mut filter_def_buf) ?;
999+ Ok ( size_hint)
1000+ } ) ?;
1001+ let filter_def_buf = filter_def_buf. into_token_stream ( ) ;
1002+
1003+ size_hint += self . write_let_target ( ctx, buf, l, span) ?;
1004+ buf. write_token ( Token ! [ =] , span) ;
1005+
1006+ let var_writer = crate :: var_writer ( ) ;
1007+ let filter_def_buf = quote_spanned ! ( span=>
1008+ let #var_let_source = askama:: helpers:: FmtCell :: new(
1009+ |#var_writer: & mut askama:: helpers:: core:: fmt:: Formatter <' _>| -> askama:: Result <( ) > {
1010+ #filter_def_buf
1011+ askama:: Result :: Ok ( ( ) )
1012+ }
1013+ ) ;
1014+ ) ;
1015+
1016+ // display the `FmtCell`
1017+ let mut filter_buf = Buffer :: new ( ) ;
1018+ quote_into ! ( & mut filter_buf, span, { askama:: filters:: Safe ( & #var_let_source) } ) ;
1019+ let filter_buf = filter_buf. into_token_stream ( ) ;
1020+ let escaper = TokenStream :: from_str ( self . input . escaper ) . unwrap ( ) ;
1021+ let filter_buf = quote_spanned ! ( span=>
1022+ ( &&askama:: filters:: AutoEscaper :: new(
1023+ & ( #filter_buf) , #escaper
1024+ ) ) . askama_auto_escape( ) ?
1025+ ) ;
1026+ quote_into ! ( buf, span, { {
1027+ #filter_def_buf
1028+ let mut __askama_tmp_write = String :: new( ) ;
1029+ if askama:: helpers:: core:: write!( & mut __askama_tmp_write, "{}" , #filter_buf) . is_err( ) {
1030+ return #var_let_source. take_err( ) ;
1031+ }
1032+ __askama_tmp_write
1033+ } ; } ) ;
1034+
1035+ self . is_in_block . decrease ( ) ;
1036+ self . prepare_ws ( ws) ;
1037+ Ok ( size_hint)
1038+ }
1039+
1040+ fn write_let_value (
1041+ & mut self ,
1042+ ctx : & Context < ' _ > ,
1043+ buf : & mut Buffer ,
1044+ l : & ' a WithSpan < Let < ' _ > > ,
1045+ val : & WithSpan < Box < Expr < ' a > > > ,
1046+ ) -> Result < SizeHint , CompileError > {
1047+ let span = ctx. span_for_node ( l. span ( ) ) ;
1048+ // Handle when this statement creates a new alias of a caller variable (or of another alias),
1049+ if let Target :: Name ( dstvar) = l. var
1050+ && let Expr :: Var ( srcvar) = * * * val
1051+ && let Some ( caller_alias) = self . locals . get_caller ( srcvar)
1052+ {
1053+ self . locals . insert (
1054+ Cow :: Borrowed ( * dstvar) ,
1055+ LocalMeta :: CallerAlias ( caller_alias. clone ( ) ) ,
1056+ ) ;
1057+ return Ok ( SizeHint :: EMPTY ) ;
1058+ }
1059+
1060+ let mut expr_buf = Buffer :: new ( ) ;
1061+ self . visit_expr ( ctx, & mut expr_buf, val) ?;
1062+
1063+ let size_hint = self . write_let_target ( ctx, buf, l, span) ?;
1064+
9891065 // If it's not taking the ownership of a local variable or copyable, then we need to add
9901066 // a reference.
9911067 let borrow = !matches ! ( * * * val, Expr :: Try ( ..) )
@@ -1036,8 +1112,14 @@ impl<'a> Generator<'a, '_> {
10361112 outer : Ws ,
10371113 node : Span ,
10381114 ) -> Result < SizeHint , CompileError > {
1039- if self . is_in_filter_block > 0 {
1040- return Err ( ctx. generate_error ( "cannot have a block inside a filter block" , node) ) ;
1115+ if self . is_in_block . level > 0 {
1116+ return Err ( ctx. generate_error (
1117+ format ! (
1118+ "cannot have a block inside a {} block" ,
1119+ self . is_in_block. block_name
1120+ ) ,
1121+ node,
1122+ ) ) ;
10411123 }
10421124 // Flush preceding whitespace according to the outer WS spec
10431125 self . flush_ws ( outer) ;
0 commit comments