askama_derive/
generator.rs

1use std::collections::hash_map::{Entry, HashMap};
2use std::path::Path;
3use std::{cmp, hash, mem, str};
4
5use crate::config::WhitespaceHandling;
6use crate::heritage::{Context, Heritage};
7use crate::input::{Source, TemplateInput};
8use crate::CompileError;
9
10use parser::node::{
11    Call, Comment, CondTest, If, Include, Let, Lit, Loop, Match, Target, Whitespace, Ws,
12};
13use parser::{Expr, Node};
14use quote::quote;
15
16pub(crate) struct Generator<'a> {
17    // The template input state: original struct AST and attributes
18    input: &'a TemplateInput<'a>,
19    // All contexts, keyed by the package-relative template path
20    contexts: &'a HashMap<&'a Path, Context<'a>>,
21    // The heritage contains references to blocks and their ancestry
22    heritage: Option<&'a Heritage<'a>>,
23    // Variables accessible directly from the current scope (not redirected to context)
24    locals: MapChain<'a, &'a str, LocalMeta>,
25    // Suffix whitespace from the previous literal. Will be flushed to the
26    // output buffer unless suppressed by whitespace suppression on the next
27    // non-literal.
28    next_ws: Option<&'a str>,
29    // Whitespace suppression from the previous non-literal. Will be used to
30    // determine whether to flush prefix whitespace from the next literal.
31    skip_ws: WhitespaceHandling,
32    // If currently in a block, this will contain the name of a potential parent block
33    super_block: Option<(&'a str, usize)>,
34    // buffer for writable
35    buf_writable: Vec<Writable<'a>>,
36    // Counter for write! hash named arguments
37    named: usize,
38}
39
40impl<'a> Generator<'a> {
41    pub(crate) fn new<'n>(
42        input: &'n TemplateInput<'_>,
43        contexts: &'n HashMap<&'n Path, Context<'n>>,
44        heritage: Option<&'n Heritage<'_>>,
45        locals: MapChain<'n, &'n str, LocalMeta>,
46    ) -> Generator<'n> {
47        Generator {
48            input,
49            contexts,
50            heritage,
51            locals,
52            next_ws: None,
53            skip_ws: WhitespaceHandling::Preserve,
54            super_block: None,
55            buf_writable: vec![],
56            named: 0,
57        }
58    }
59
60    // Takes a Context and generates the relevant implementations.
61    pub(crate) fn build(mut self, ctx: &'a Context<'_>) -> Result<String, CompileError> {
62        let mut buf = Buffer::new(0);
63
64        self.impl_template(ctx, &mut buf)?;
65        self.impl_display(&mut buf)?;
66
67        #[cfg(feature = "with-actix-web")]
68        self.impl_actix_web_responder(&mut buf)?;
69        #[cfg(feature = "with-axum")]
70        self.impl_axum_into_response(&mut buf)?;
71        #[cfg(feature = "with-gotham")]
72        self.impl_gotham_into_response(&mut buf)?;
73        #[cfg(feature = "with-hyper")]
74        self.impl_hyper_into_response(&mut buf)?;
75        #[cfg(feature = "with-mendes")]
76        self.impl_mendes_responder(&mut buf)?;
77        #[cfg(feature = "with-rocket")]
78        self.impl_rocket_responder(&mut buf)?;
79        #[cfg(feature = "with-tide")]
80        self.impl_tide_integrations(&mut buf)?;
81        #[cfg(feature = "with-warp")]
82        self.impl_warp_reply(&mut buf)?;
83
84        Ok(buf.buf)
85    }
86
87    // Implement `Template` for the given context struct.
88    fn impl_template(
89        &mut self,
90        ctx: &'a Context<'_>,
91        buf: &mut Buffer,
92    ) -> Result<(), CompileError> {
93        self.write_header(buf, "::askama::Template", None)?;
94        buf.writeln(
95            "fn render_into(&self, writer: &mut (impl ::std::fmt::Write + ?Sized)) -> \
96             ::askama::Result<()> {",
97        )?;
98
99        // Make sure the compiler understands that the generated code depends on the template files.
100        for path in self.contexts.keys() {
101            // Skip the fake path of templates defined in rust source.
102            let path_is_valid = match self.input.source {
103                Source::Path(_) => true,
104                Source::Source(_) => path != &self.input.path,
105            };
106            if path_is_valid {
107                let path = path.to_str().unwrap();
108                buf.writeln(
109                    &quote! {
110                        include_bytes!(#path);
111                    }
112                    .to_string(),
113                )?;
114            }
115        }
116
117        let size_hint = if let Some(heritage) = self.heritage {
118            self.handle(heritage.root, heritage.root.nodes, buf, AstLevel::Top)
119        } else {
120            self.handle(ctx, ctx.nodes, buf, AstLevel::Top)
121        }?;
122
123        self.flush_ws(Ws(None, None));
124        buf.writeln("::askama::Result::Ok(())")?;
125        buf.writeln("}")?;
126
127        buf.writeln("const EXTENSION: ::std::option::Option<&'static ::std::primitive::str> = ")?;
128        buf.writeln(&format!("{:?}", self.input.extension()))?;
129        buf.writeln(";")?;
130
131        buf.writeln("const SIZE_HINT: ::std::primitive::usize = ")?;
132        buf.writeln(&format!("{size_hint}"))?;
133        buf.writeln(";")?;
134
135        buf.writeln("const MIME_TYPE: &'static ::std::primitive::str = ")?;
136        buf.writeln(&format!("{:?}", &self.input.mime_type))?;
137        buf.writeln(";")?;
138
139        buf.writeln("}")?;
140        Ok(())
141    }
142
143    // Implement `Display` for the given context struct.
144    fn impl_display(&mut self, buf: &mut Buffer) -> Result<(), CompileError> {
145        self.write_header(buf, "::std::fmt::Display", None)?;
146        buf.writeln("#[inline]")?;
147        buf.writeln("fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {")?;
148        buf.writeln("::askama::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {})")?;
149        buf.writeln("}")?;
150        buf.writeln("}")
151    }
152
153    // Implement Actix-web's `Responder`.
154    #[cfg(feature = "with-actix-web")]
155    fn impl_actix_web_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> {
156        self.write_header(buf, "::askama_actix::actix_web::Responder", None)?;
157        buf.writeln("type Body = ::askama_actix::actix_web::body::BoxBody;")?;
158        buf.writeln("#[inline]")?;
159        buf.writeln(
160            "fn respond_to(self, _req: &::askama_actix::actix_web::HttpRequest) \
161             -> ::askama_actix::actix_web::HttpResponse<Self::Body> {",
162        )?;
163        buf.writeln("<Self as ::askama_actix::TemplateToResponse>::to_response(&self)")?;
164        buf.writeln("}")?;
165        buf.writeln("}")
166    }
167
168    // Implement Axum's `IntoResponse`.
169    #[cfg(feature = "with-axum")]
170    fn impl_axum_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> {
171        self.write_header(buf, "::askama_axum::IntoResponse", None)?;
172        buf.writeln("#[inline]")?;
173        buf.writeln(
174            "fn into_response(self)\
175             -> ::askama_axum::Response {",
176        )?;
177        buf.writeln("::askama_axum::into_response(&self)")?;
178        buf.writeln("}")?;
179        buf.writeln("}")
180    }
181
182    // Implement gotham's `IntoResponse`.
183    #[cfg(feature = "with-gotham")]
184    fn impl_gotham_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> {
185        self.write_header(buf, "::askama_gotham::IntoResponse", None)?;
186        buf.writeln("#[inline]")?;
187        buf.writeln(
188            "fn into_response(self, _state: &::askama_gotham::State)\
189             -> ::askama_gotham::Response<::askama_gotham::Body> {",
190        )?;
191        buf.writeln("::askama_gotham::respond(&self)")?;
192        buf.writeln("}")?;
193        buf.writeln("}")
194    }
195
196    // Implement `From<Template> for hyper::Response<Body>` and `From<Template> for hyper::Body.
197    #[cfg(feature = "with-hyper")]
198    fn impl_hyper_into_response(&mut self, buf: &mut Buffer) -> Result<(), CompileError> {
199        let (impl_generics, orig_ty_generics, where_clause) =
200            self.input.ast.generics.split_for_impl();
201        let ident = &self.input.ast.ident;
202        // From<Template> for hyper::Response<Body>
203        buf.writeln(&format!(
204            "{} {{",
205            quote!(
206                impl #impl_generics ::core::convert::From<&#ident #orig_ty_generics>
207                for ::askama_hyper::hyper::Response<::askama_hyper::hyper::Body>
208                #where_clause
209            )
210        ))?;
211        buf.writeln("#[inline]")?;
212        buf.writeln(&format!(
213            "{} {{",
214            quote!(fn from(value: &#ident #orig_ty_generics) -> Self)
215        ))?;
216        buf.writeln("::askama_hyper::respond(value)")?;
217        buf.writeln("}")?;
218        buf.writeln("}")?;
219
220        // TryFrom<Template> for hyper::Body
221        buf.writeln(&format!(
222            "{} {{",
223            quote!(
224                impl #impl_generics ::core::convert::TryFrom<&#ident #orig_ty_generics>
225                for ::askama_hyper::hyper::Body
226                #where_clause
227            )
228        ))?;
229        buf.writeln("type Error = ::askama::Error;")?;
230        buf.writeln("#[inline]")?;
231        buf.writeln(&format!(
232            "{} {{",
233            quote!(fn try_from(value: &#ident #orig_ty_generics) -> Result<Self, Self::Error>)
234        ))?;
235        buf.writeln("::askama::Template::render(value).map(Into::into)")?;
236        buf.writeln("}")?;
237        buf.writeln("}")
238    }
239
240    // Implement mendes' `Responder`.
241    #[cfg(feature = "with-mendes")]
242    fn impl_mendes_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> {
243        let param = syn::parse_str("A: ::mendes::Application").unwrap();
244
245        let mut generics = self.input.ast.generics.clone();
246        generics.params.push(param);
247        let (_, orig_ty_generics, _) = self.input.ast.generics.split_for_impl();
248        let (impl_generics, _, where_clause) = generics.split_for_impl();
249
250        let mut where_clause = match where_clause {
251            Some(clause) => clause.clone(),
252            None => syn::WhereClause {
253                where_token: syn::Token![where](proc_macro2::Span::call_site()),
254                predicates: syn::punctuated::Punctuated::new(),
255            },
256        };
257
258        where_clause
259            .predicates
260            .push(syn::parse_str("A::ResponseBody: From<String>").unwrap());
261        where_clause
262            .predicates
263            .push(syn::parse_str("A::Error: From<::askama_mendes::Error>").unwrap());
264
265        buf.writeln(
266            format!(
267                "{} {} for {} {} {{",
268                quote!(impl #impl_generics),
269                "::mendes::application::IntoResponse<A>",
270                self.input.ast.ident,
271                quote!(#orig_ty_generics #where_clause),
272            )
273            .as_ref(),
274        )?;
275
276        buf.writeln(
277            "fn into_response(self, app: &A, req: &::mendes::http::request::Parts) \
278             -> ::mendes::http::Response<A::ResponseBody> {",
279        )?;
280
281        buf.writeln("::askama_mendes::into_response(app, req, &self)")?;
282        buf.writeln("}")?;
283        buf.writeln("}")?;
284        Ok(())
285    }
286
287    // Implement Rocket's `Responder`.
288    #[cfg(feature = "with-rocket")]
289    fn impl_rocket_responder(&mut self, buf: &mut Buffer) -> Result<(), CompileError> {
290        let lifetime1 = syn::Lifetime::new("'askama1", proc_macro2::Span::call_site());
291        let lifetime2 = syn::Lifetime::new("'askama2", proc_macro2::Span::call_site());
292
293        let mut param2 = syn::LifetimeParam::new(lifetime2);
294        param2.colon_token = Some(syn::Token![:](proc_macro2::Span::call_site()));
295        param2.bounds = syn::punctuated::Punctuated::new();
296        param2.bounds.push_value(lifetime1.clone());
297
298        let param1 = syn::GenericParam::Lifetime(syn::LifetimeParam::new(lifetime1));
299        let param2 = syn::GenericParam::Lifetime(param2);
300
301        self.write_header(
302            buf,
303            "::askama_rocket::Responder<'askama1, 'askama2>",
304            Some(vec![param1, param2]),
305        )?;
306
307        buf.writeln("#[inline]")?;
308        buf.writeln(
309            "fn respond_to(self, _: &'askama1 ::askama_rocket::Request) \
310             -> ::askama_rocket::Result<'askama2> {",
311        )?;
312        buf.writeln("::askama_rocket::respond(&self)")?;
313
314        buf.writeln("}")?;
315        buf.writeln("}")?;
316        Ok(())
317    }
318
319    #[cfg(feature = "with-tide")]
320    fn impl_tide_integrations(&mut self, buf: &mut Buffer) -> Result<(), CompileError> {
321        self.write_header(
322            buf,
323            "::std::convert::TryInto<::askama_tide::tide::Body>",
324            None,
325        )?;
326        buf.writeln(
327            "type Error = ::askama_tide::askama::Error;\n\
328            #[inline]\n\
329            fn try_into(self) -> ::askama_tide::askama::Result<::askama_tide::tide::Body> {",
330        )?;
331        buf.writeln("::askama_tide::try_into_body(&self)")?;
332        buf.writeln("}")?;
333        buf.writeln("}")?;
334
335        buf.writeln("#[allow(clippy::from_over_into)]")?;
336        self.write_header(buf, "Into<::askama_tide::tide::Response>", None)?;
337        buf.writeln("#[inline]")?;
338        buf.writeln("fn into(self) -> ::askama_tide::tide::Response {")?;
339        buf.writeln("::askama_tide::into_response(&self)")?;
340        buf.writeln("}\n}")
341    }
342
343    #[cfg(feature = "with-warp")]
344    fn impl_warp_reply(&mut self, buf: &mut Buffer) -> Result<(), CompileError> {
345        self.write_header(buf, "::askama_warp::warp::reply::Reply", None)?;
346        buf.writeln("#[inline]")?;
347        buf.writeln("fn into_response(self) -> ::askama_warp::warp::reply::Response {")?;
348        buf.writeln("::askama_warp::reply(&self)")?;
349        buf.writeln("}")?;
350        buf.writeln("}")
351    }
352
353    // Writes header for the `impl` for `TraitFromPathName` or `Template`
354    // for the given context struct.
355    fn write_header(
356        &mut self,
357        buf: &mut Buffer,
358        target: &str,
359        params: Option<Vec<syn::GenericParam>>,
360    ) -> Result<(), CompileError> {
361        let mut generics = self.input.ast.generics.clone();
362        if let Some(params) = params {
363            for param in params {
364                generics.params.push(param);
365            }
366        }
367        let (_, orig_ty_generics, _) = self.input.ast.generics.split_for_impl();
368        let (impl_generics, _, where_clause) = generics.split_for_impl();
369        buf.writeln(
370            format!(
371                "{} {} for {}{} {{",
372                quote!(impl #impl_generics),
373                target,
374                self.input.ast.ident,
375                quote!(#orig_ty_generics #where_clause),
376            )
377            .as_ref(),
378        )
379    }
380
381    /* Helper methods for handling node types */
382
383    fn handle(
384        &mut self,
385        ctx: &'a Context<'_>,
386        nodes: &'a [Node<'_>],
387        buf: &mut Buffer,
388        level: AstLevel,
389    ) -> Result<usize, CompileError> {
390        let mut size_hint = 0;
391        for n in nodes {
392            match *n {
393                Node::Lit(ref lit) => {
394                    self.visit_lit(lit);
395                }
396                Node::Comment(ref comment) => {
397                    self.write_comment(comment);
398                }
399                Node::Expr(ws, ref val) => {
400                    self.write_expr(ws, val);
401                }
402                Node::Let(ref l) => {
403                    self.write_let(buf, l)?;
404                }
405                Node::If(ref i) => {
406                    size_hint += self.write_if(ctx, buf, i)?;
407                }
408                Node::Match(ref m) => {
409                    size_hint += self.write_match(ctx, buf, m)?;
410                }
411                Node::Loop(ref loop_block) => {
412                    size_hint += self.write_loop(ctx, buf, loop_block)?;
413                }
414                Node::BlockDef(ref b) => {
415                    size_hint += self.write_block(buf, Some(b.name), Ws(b.ws1.0, b.ws2.1))?;
416                }
417                Node::Include(ref i) => {
418                    size_hint += self.handle_include(ctx, buf, i)?;
419                }
420                Node::Call(ref call) => {
421                    size_hint += self.write_call(ctx, buf, call)?;
422                }
423                Node::Macro(ref m) => {
424                    if level != AstLevel::Top {
425                        return Err("macro blocks only allowed at the top level".into());
426                    }
427                    self.flush_ws(m.ws1);
428                    self.prepare_ws(m.ws2);
429                }
430                Node::Raw(ref raw) => {
431                    self.handle_ws(raw.ws1);
432                    self.visit_lit(&raw.lit);
433                    self.handle_ws(raw.ws2);
434                }
435                Node::Import(ref i) => {
436                    if level != AstLevel::Top {
437                        return Err("import blocks only allowed at the top level".into());
438                    }
439                    self.handle_ws(i.ws);
440                }
441                Node::Extends(_) => {
442                    if level != AstLevel::Top {
443                        return Err("extend blocks only allowed at the top level".into());
444                    }
445                    // No whitespace handling: child template top-level is not used,
446                    // except for the blocks defined in it.
447                }
448                Node::Break(ws) => {
449                    self.handle_ws(ws);
450                    self.write_buf_writable(buf)?;
451                    buf.writeln("break;")?;
452                }
453                Node::Continue(ws) => {
454                    self.handle_ws(ws);
455                    self.write_buf_writable(buf)?;
456                    buf.writeln("continue;")?;
457                }
458            }
459        }
460
461        if AstLevel::Top == level {
462            // Handle any pending whitespace.
463            if self.next_ws.is_some() {
464                self.flush_ws(Ws(Some(self.skip_ws.into()), None));
465            }
466
467            size_hint += self.write_buf_writable(buf)?;
468        }
469        Ok(size_hint)
470    }
471
472    fn write_if(
473        &mut self,
474        ctx: &'a Context<'_>,
475        buf: &mut Buffer,
476        i: &'a If<'_>,
477    ) -> Result<usize, CompileError> {
478        let mut flushed = 0;
479        let mut arm_sizes = Vec::new();
480        let mut has_else = false;
481        for (i, cond) in i.branches.iter().enumerate() {
482            self.handle_ws(cond.ws);
483            flushed += self.write_buf_writable(buf)?;
484            if i > 0 {
485                self.locals.pop();
486            }
487
488            self.locals.push();
489            let mut arm_size = 0;
490            if let Some(CondTest { target, expr }) = &cond.cond {
491                if i == 0 {
492                    buf.write("if ");
493                } else {
494                    buf.dedent()?;
495                    buf.write("} else if ");
496                }
497
498                if let Some(target) = target {
499                    let mut expr_buf = Buffer::new(0);
500                    self.visit_expr(&mut expr_buf, expr)?;
501                    buf.write("let ");
502                    self.visit_target(buf, true, true, target);
503                    buf.write(" = &(");
504                    buf.write(&expr_buf.buf);
505                    buf.write(")");
506                } else {
507                    // The following syntax `*(&(...) as &bool)` is used to
508                    // trigger Rust's automatic dereferencing, to coerce
509                    // e.g. `&&&&&bool` to `bool`. First `&(...) as &bool`
510                    // coerces e.g. `&&&bool` to `&bool`. Then `*(&bool)`
511                    // finally dereferences it to `bool`.
512                    buf.write("*(&(");
513                    let expr_code = self.visit_expr_root(expr)?;
514                    buf.write(&expr_code);
515                    buf.write(") as &bool)");
516                }
517            } else {
518                buf.dedent()?;
519                buf.write("} else");
520                has_else = true;
521            }
522
523            buf.writeln(" {")?;
524
525            arm_size += self.handle(ctx, &cond.nodes, buf, AstLevel::Nested)?;
526            arm_sizes.push(arm_size);
527        }
528        self.handle_ws(i.ws);
529        flushed += self.write_buf_writable(buf)?;
530        buf.writeln("}")?;
531
532        self.locals.pop();
533
534        if !has_else {
535            arm_sizes.push(0);
536        }
537        Ok(flushed + median(&mut arm_sizes))
538    }
539
540    #[allow(clippy::too_many_arguments)]
541    fn write_match(
542        &mut self,
543        ctx: &'a Context<'_>,
544        buf: &mut Buffer,
545        m: &'a Match<'a>,
546    ) -> Result<usize, CompileError> {
547        let Match {
548            ws1,
549            ref expr,
550            ref arms,
551            ws2,
552        } = *m;
553
554        self.flush_ws(ws1);
555        let flushed = self.write_buf_writable(buf)?;
556        let mut arm_sizes = Vec::new();
557
558        let expr_code = self.visit_expr_root(expr)?;
559        buf.writeln(&format!("match &{expr_code} {{"))?;
560
561        let mut arm_size = 0;
562        for (i, arm) in arms.iter().enumerate() {
563            self.handle_ws(arm.ws);
564
565            if i > 0 {
566                arm_sizes.push(arm_size + self.write_buf_writable(buf)?);
567
568                buf.writeln("}")?;
569                self.locals.pop();
570            }
571
572            self.locals.push();
573            self.visit_target(buf, true, true, &arm.target);
574            buf.writeln(" => {")?;
575
576            arm_size = self.handle(ctx, &arm.nodes, buf, AstLevel::Nested)?;
577        }
578
579        self.handle_ws(ws2);
580        arm_sizes.push(arm_size + self.write_buf_writable(buf)?);
581        buf.writeln("}")?;
582        self.locals.pop();
583
584        buf.writeln("}")?;
585
586        Ok(flushed + median(&mut arm_sizes))
587    }
588
589    #[allow(clippy::too_many_arguments)]
590    fn write_loop(
591        &mut self,
592        ctx: &'a Context<'_>,
593        buf: &mut Buffer,
594        loop_block: &'a Loop<'_>,
595    ) -> Result<usize, CompileError> {
596        self.handle_ws(loop_block.ws1);
597        self.locals.push();
598
599        let expr_code = self.visit_expr_root(&loop_block.iter)?;
600
601        let has_else_nodes = !loop_block.else_nodes.is_empty();
602
603        let flushed = self.write_buf_writable(buf)?;
604        buf.writeln("{")?;
605        if has_else_nodes {
606            buf.writeln("let mut _did_loop = false;")?;
607        }
608        match loop_block.iter {
609            Expr::Range(_, _, _) => buf.writeln(&format!("let _iter = {expr_code};")),
610            Expr::Array(..) => buf.writeln(&format!("let _iter = {expr_code}.iter();")),
611            // If `iter` is a call then we assume it's something that returns
612            // an iterator. If not then the user can explicitly add the needed
613            // call without issues.
614            Expr::Call(..) | Expr::Index(..) => {
615                buf.writeln(&format!("let _iter = ({expr_code}).into_iter();"))
616            }
617            // If accessing `self` then it most likely needs to be
618            // borrowed, to prevent an attempt of moving.
619            _ if expr_code.starts_with("self.") => {
620                buf.writeln(&format!("let _iter = (&{expr_code}).into_iter();"))
621            }
622            // If accessing a field then it most likely needs to be
623            // borrowed, to prevent an attempt of moving.
624            Expr::Attr(..) => buf.writeln(&format!("let _iter = (&{expr_code}).into_iter();")),
625            // Otherwise, we borrow `iter` assuming that it implements `IntoIterator`.
626            _ => buf.writeln(&format!("let _iter = ({expr_code}).into_iter();")),
627        }?;
628        if let Some(cond) = &loop_block.cond {
629            self.locals.push();
630            buf.write("let _iter = _iter.filter(|");
631            self.visit_target(buf, true, true, &loop_block.var);
632            buf.write("| -> bool {");
633            self.visit_expr(buf, cond)?;
634            buf.writeln("});")?;
635            self.locals.pop();
636        }
637
638        self.locals.push();
639        buf.write("for (");
640        self.visit_target(buf, true, true, &loop_block.var);
641        buf.writeln(", _loop_item) in ::askama::helpers::TemplateLoop::new(_iter) {")?;
642
643        if has_else_nodes {
644            buf.writeln("_did_loop = true;")?;
645        }
646        let mut size_hint1 = self.handle(ctx, &loop_block.body, buf, AstLevel::Nested)?;
647        self.handle_ws(loop_block.ws2);
648        size_hint1 += self.write_buf_writable(buf)?;
649        self.locals.pop();
650        buf.writeln("}")?;
651
652        let mut size_hint2;
653        if has_else_nodes {
654            buf.writeln("if !_did_loop {")?;
655            self.locals.push();
656            size_hint2 = self.handle(ctx, &loop_block.else_nodes, buf, AstLevel::Nested)?;
657            self.handle_ws(loop_block.ws3);
658            size_hint2 += self.write_buf_writable(buf)?;
659            self.locals.pop();
660            buf.writeln("}")?;
661        } else {
662            self.handle_ws(loop_block.ws3);
663            size_hint2 = self.write_buf_writable(buf)?;
664        }
665
666        buf.writeln("}")?;
667
668        Ok(flushed + ((size_hint1 * 3) + size_hint2) / 2)
669    }
670
671    fn write_call(
672        &mut self,
673        ctx: &'a Context<'_>,
674        buf: &mut Buffer,
675        call: &'a Call<'_>,
676    ) -> Result<usize, CompileError> {
677        let Call {
678            ws,
679            scope,
680            name,
681            ref args,
682        } = *call;
683        if name == "super" {
684            return self.write_block(buf, None, ws);
685        }
686
687        let (def, own_ctx) = match scope {
688            Some(s) => {
689                let path = ctx.imports.get(s).ok_or_else(|| {
690                    CompileError::from(format!("no import found for scope {s:?}"))
691                })?;
692                let mctx = self
693                    .contexts
694                    .get(path.as_path())
695                    .ok_or_else(|| CompileError::from(format!("context for {path:?} not found")))?;
696                let def = mctx.macros.get(name).ok_or_else(|| {
697                    CompileError::from(format!("macro {name:?} not found in scope {s:?}"))
698                })?;
699                (def, mctx)
700            }
701            None => {
702                let def = ctx
703                    .macros
704                    .get(name)
705                    .ok_or_else(|| CompileError::from(format!("macro {name:?} not found")))?;
706                (def, ctx)
707            }
708        };
709
710        self.flush_ws(ws); // Cannot handle_ws() here: whitespace from macro definition comes first
711        self.locals.push();
712        self.write_buf_writable(buf)?;
713        buf.writeln("{")?;
714        self.prepare_ws(def.ws1);
715
716        let mut names = Buffer::new(0);
717        let mut values = Buffer::new(0);
718        let mut is_first_variable = true;
719        if args.len() != def.args.len() {
720            return Err(CompileError::from(format!(
721                "macro {name:?} expected {} argument{}, found {}",
722                def.args.len(),
723                if def.args.len() != 1 { "s" } else { "" },
724                args.len()
725            )));
726        }
727        let mut named_arguments = HashMap::new();
728        // Since named arguments can only be passed last, we only need to check if the last argument
729        // is a named one.
730        if let Some(Expr::NamedArgument(_, _)) = args.last() {
731            // First we check that all named arguments actually exist in the called item.
732            for arg in args.iter().rev() {
733                let Expr::NamedArgument(arg_name, _) = arg else {
734                    break;
735                };
736                if !def.args.iter().any(|arg| arg == arg_name) {
737                    return Err(CompileError::from(format!(
738                        "no argument named `{arg_name}` in macro {name:?}"
739                    )));
740                }
741                named_arguments.insert(arg_name, arg);
742            }
743        }
744
745        // Handling both named and unnamed arguments requires to be careful of the named arguments
746        // order. To do so, we iterate through the macro defined arguments and then check if we have
747        // a named argument with this name:
748        //
749        // * If there is one, we add it and move to the next argument.
750        // * If there isn't one, then we pick the next argument (we can do it without checking
751        //   anything since named arguments are always last).
752        let mut allow_positional = true;
753        for (index, arg) in def.args.iter().enumerate() {
754            let expr = match named_arguments.get(&arg) {
755                Some(expr) => {
756                    allow_positional = false;
757                    expr
758                }
759                None => {
760                    if !allow_positional {
761                        // If there is already at least one named argument, then it's not allowed
762                        // to use unnamed ones at this point anymore.
763                        return Err(CompileError::from(format!(
764                            "cannot have unnamed argument (`{arg}`) after named argument in macro \
765                             {name:?}"
766                        )));
767                    }
768                    &args[index]
769                }
770            };
771            match expr {
772                // If `expr` is already a form of variable then
773                // don't reintroduce a new variable. This is
774                // to avoid moving non-copyable values.
775                &Expr::Var(name) if name != "self" => {
776                    let var = self.locals.resolve_or_self(name);
777                    self.locals.insert(arg, LocalMeta::with_ref(var));
778                }
779                Expr::Attr(obj, attr) => {
780                    let mut attr_buf = Buffer::new(0);
781                    self.visit_attr(&mut attr_buf, obj, attr)?;
782
783                    let var = self.locals.resolve(&attr_buf.buf).unwrap_or(attr_buf.buf);
784                    self.locals.insert(arg, LocalMeta::with_ref(var));
785                }
786                // Everything else still needs to become variables,
787                // to avoid having the same logic be executed
788                // multiple times, e.g. in the case of macro
789                // parameters being used multiple times.
790                _ => {
791                    if is_first_variable {
792                        is_first_variable = false
793                    } else {
794                        names.write(", ");
795                        values.write(", ");
796                    }
797                    names.write(arg);
798
799                    values.write("(");
800                    values.write(&self.visit_expr_root(expr)?);
801                    values.write(")");
802                    self.locals.insert_with_default(arg);
803                }
804            }
805        }
806
807        debug_assert_eq!(names.buf.is_empty(), values.buf.is_empty());
808        if !names.buf.is_empty() {
809            buf.writeln(&format!("let ({}) = ({});", names.buf, values.buf))?;
810        }
811
812        let mut size_hint = self.handle(own_ctx, &def.nodes, buf, AstLevel::Nested)?;
813
814        self.flush_ws(def.ws2);
815        size_hint += self.write_buf_writable(buf)?;
816        buf.writeln("}")?;
817        self.locals.pop();
818        self.prepare_ws(ws);
819        Ok(size_hint)
820    }
821
822    fn handle_include(
823        &mut self,
824        ctx: &'a Context<'_>,
825        buf: &mut Buffer,
826        i: &'a Include<'_>,
827    ) -> Result<usize, CompileError> {
828        self.flush_ws(i.ws);
829        self.write_buf_writable(buf)?;
830        let path = self
831            .input
832            .config
833            .find_template(i.path, Some(&self.input.path))?;
834
835        // Make sure the compiler understands that the generated code depends on the template file.
836        {
837            let path = path.to_str().unwrap();
838            buf.writeln(
839                &quote! {
840                    include_bytes!(#path);
841                }
842                .to_string(),
843            )?;
844        }
845
846        // We clone the context of the child in order to preserve their macros and imports.
847        // But also add all the imports and macros from this template that don't override the
848        // child's ones to preserve this template's context.
849        let child_ctx = &mut self.contexts[path.as_path()].clone();
850        for (name, mac) in &ctx.macros {
851            child_ctx.macros.entry(name).or_insert(mac);
852        }
853        for (name, import) in &ctx.imports {
854            child_ctx
855                .imports
856                .entry(name)
857                .or_insert_with(|| import.clone());
858        }
859
860        // Create a new generator for the child, and call it like in `impl_template` as if it were
861        // a full template, while preserving the context.
862        let heritage = if !child_ctx.blocks.is_empty() || child_ctx.extends.is_some() {
863            Some(Heritage::new(child_ctx, self.contexts))
864        } else {
865            None
866        };
867
868        let handle_ctx = match &heritage {
869            Some(heritage) => heritage.root,
870            None => child_ctx,
871        };
872        let locals = MapChain::with_parent(&self.locals);
873        let mut child = Self::new(self.input, self.contexts, heritage.as_ref(), locals);
874        let mut size_hint = child.handle(handle_ctx, handle_ctx.nodes, buf, AstLevel::Top)?;
875        size_hint += child.write_buf_writable(buf)?;
876        self.prepare_ws(i.ws);
877
878        Ok(size_hint)
879    }
880
881    fn is_shadowing_variable(&self, var: &Target<'a>) -> Result<bool, CompileError> {
882        match var {
883            Target::Name(name) => {
884                let name = normalize_identifier(name);
885                match self.locals.get(&name) {
886                    // declares a new variable
887                    None => Ok(false),
888                    // an initialized variable gets shadowed
889                    Some(meta) if meta.initialized => Ok(true),
890                    // initializes a variable that was introduced in a LetDecl before
891                    _ => Ok(false),
892                }
893            }
894            Target::Tuple(_, targets) => {
895                for target in targets {
896                    match self.is_shadowing_variable(target) {
897                        Ok(false) => continue,
898                        outcome => return outcome,
899                    }
900                }
901                Ok(false)
902            }
903            Target::Struct(_, named_targets) => {
904                for (_, target) in named_targets {
905                    match self.is_shadowing_variable(target) {
906                        Ok(false) => continue,
907                        outcome => return outcome,
908                    }
909                }
910                Ok(false)
911            }
912            _ => Err("literals are not allowed on the left-hand side of an assignment".into()),
913        }
914    }
915
916    fn write_let(&mut self, buf: &mut Buffer, l: &'a Let<'_>) -> Result<(), CompileError> {
917        self.handle_ws(l.ws);
918
919        let Some(val) = &l.val else {
920            self.write_buf_writable(buf)?;
921            buf.write("let ");
922            self.visit_target(buf, false, true, &l.var);
923            return buf.writeln(";");
924        };
925
926        let mut expr_buf = Buffer::new(0);
927        self.visit_expr(&mut expr_buf, val)?;
928
929        let shadowed = self.is_shadowing_variable(&l.var)?;
930        if shadowed {
931            // Need to flush the buffer if the variable is being shadowed,
932            // to ensure the old variable is used.
933            self.write_buf_writable(buf)?;
934        }
935        if shadowed
936            || !matches!(l.var, Target::Name(_))
937            || matches!(&l.var, Target::Name(name) if self.locals.get(name).is_none())
938        {
939            buf.write("let ");
940        }
941
942        self.visit_target(buf, true, true, &l.var);
943        buf.writeln(&format!(" = {};", &expr_buf.buf))
944    }
945
946    // If `name` is `Some`, this is a call to a block definition, and we have to find
947    // the first block for that name from the ancestry chain. If name is `None`, this
948    // is from a `super()` call, and we can get the name from `self.super_block`.
949    fn write_block(
950        &mut self,
951        buf: &mut Buffer,
952        name: Option<&'a str>,
953        outer: Ws,
954    ) -> Result<usize, CompileError> {
955        // Flush preceding whitespace according to the outer WS spec
956        self.flush_ws(outer);
957
958        let prev_block = self.super_block;
959        let cur = match (name, prev_block) {
960            // The top-level context contains a block definition
961            (Some(cur_name), None) => (cur_name, 0),
962            // A block definition contains a block definition of the same name
963            (Some(cur_name), Some((prev_name, _))) if cur_name == prev_name => {
964                return Err(format!("cannot define recursive blocks ({cur_name})").into());
965            }
966            // A block definition contains a definition of another block
967            (Some(cur_name), Some((_, _))) => (cur_name, 0),
968            // `super()` was called inside a block
969            (None, Some((prev_name, gen))) => (prev_name, gen + 1),
970            // `super()` is called from outside a block
971            (None, None) => return Err("cannot call 'super()' outside block".into()),
972        };
973        self.super_block = Some(cur);
974
975        // Get the block definition from the heritage chain
976        let heritage = self
977            .heritage
978            .ok_or_else(|| CompileError::from("no block ancestors available"))?;
979        let (ctx, def) = *heritage.blocks[cur.0].get(cur.1).ok_or_else(|| {
980            CompileError::from(match name {
981                None => format!("no super() block found for block '{}'", cur.0),
982                Some(name) => format!("no block found for name '{name}'"),
983            })
984        })?;
985
986        // Handle inner whitespace suppression spec and process block nodes
987        self.prepare_ws(def.ws1);
988        self.locals.push();
989        let size_hint = self.handle(ctx, &def.nodes, buf, AstLevel::Block)?;
990
991        if !self.locals.is_current_empty() {
992            // Need to flush the buffer before popping the variable stack
993            self.write_buf_writable(buf)?;
994        }
995
996        self.locals.pop();
997        self.flush_ws(def.ws2);
998
999        // Restore original block context and set whitespace suppression for
1000        // succeeding whitespace according to the outer WS spec
1001        self.super_block = prev_block;
1002        self.prepare_ws(outer);
1003        Ok(size_hint)
1004    }
1005
1006    fn write_expr(&mut self, ws: Ws, s: &'a Expr<'a>) {
1007        self.handle_ws(ws);
1008        self.buf_writable.push(Writable::Expr(s));
1009    }
1010
1011    // Write expression buffer and empty
1012    fn write_buf_writable(&mut self, buf: &mut Buffer) -> Result<usize, CompileError> {
1013        if self.buf_writable.is_empty() {
1014            return Ok(0);
1015        }
1016
1017        if self
1018            .buf_writable
1019            .iter()
1020            .all(|w| matches!(w, Writable::Lit(_)))
1021        {
1022            let mut buf_lit = Buffer::new(0);
1023            for s in mem::take(&mut self.buf_writable) {
1024                if let Writable::Lit(s) = s {
1025                    buf_lit.write(s);
1026                };
1027            }
1028            buf.writeln(&format!("writer.write_str({:#?})?;", &buf_lit.buf))?;
1029            return Ok(buf_lit.buf.len());
1030        }
1031
1032        let mut size_hint = 0;
1033        let mut buf_format = Buffer::new(0);
1034        let mut buf_expr = Buffer::new(buf.indent + 1);
1035        let mut expr_cache = HashMap::with_capacity(self.buf_writable.len());
1036        for s in mem::take(&mut self.buf_writable) {
1037            match s {
1038                Writable::Lit(s) => {
1039                    buf_format.write(&s.replace('{', "{{").replace('}', "}}"));
1040                    size_hint += s.len();
1041                }
1042                Writable::Expr(s) => {
1043                    use self::DisplayWrap::*;
1044                    let mut expr_buf = Buffer::new(0);
1045                    let wrapped = self.visit_expr(&mut expr_buf, s)?;
1046                    let expression = match wrapped {
1047                        Wrapped => expr_buf.buf,
1048                        Unwrapped => format!(
1049                            "::askama::MarkupDisplay::new_unsafe(&({}), {})",
1050                            expr_buf.buf, self.input.escaper
1051                        ),
1052                    };
1053
1054                    let id = match expr_cache.entry(expression.clone()) {
1055                        Entry::Occupied(e) if is_cacheable(s) => *e.get(),
1056                        e => {
1057                            let id = self.named;
1058                            self.named += 1;
1059
1060                            buf_expr.write(&format!("expr{id} = "));
1061                            buf_expr.write("&");
1062                            buf_expr.write(&expression);
1063                            buf_expr.writeln(",")?;
1064
1065                            if let Entry::Vacant(e) = e {
1066                                e.insert(id);
1067                            }
1068
1069                            id
1070                        }
1071                    };
1072
1073                    buf_format.write(&format!("{{expr{id}}}"));
1074                    size_hint += 3;
1075                }
1076            }
1077        }
1078
1079        buf.writeln("::std::write!(")?;
1080        buf.indent();
1081        buf.writeln("writer,")?;
1082        buf.writeln(&format!("{:#?},", &buf_format.buf))?;
1083        buf.writeln(buf_expr.buf.trim())?;
1084        buf.dedent()?;
1085        buf.writeln(")?;")?;
1086        Ok(size_hint)
1087    }
1088
1089    fn visit_lit(&mut self, lit: &'a Lit<'_>) {
1090        assert!(self.next_ws.is_none());
1091        let Lit { lws, val, rws } = *lit;
1092        if !lws.is_empty() {
1093            match self.skip_ws {
1094                WhitespaceHandling::Suppress => {}
1095                _ if val.is_empty() => {
1096                    assert!(rws.is_empty());
1097                    self.next_ws = Some(lws);
1098                }
1099                WhitespaceHandling::Preserve => self.buf_writable.push(Writable::Lit(lws)),
1100                WhitespaceHandling::Minimize => {
1101                    self.buf_writable
1102                        .push(Writable::Lit(match lws.contains('\n') {
1103                            true => "\n",
1104                            false => " ",
1105                        }));
1106                }
1107            }
1108        }
1109
1110        if !val.is_empty() {
1111            self.skip_ws = WhitespaceHandling::Preserve;
1112            self.buf_writable.push(Writable::Lit(val));
1113        }
1114
1115        if !rws.is_empty() {
1116            self.next_ws = Some(rws);
1117        }
1118    }
1119
1120    fn write_comment(&mut self, comment: &'a Comment<'_>) {
1121        self.handle_ws(comment.ws);
1122    }
1123
1124    /* Visitor methods for expression types */
1125
1126    fn visit_expr_root(&mut self, expr: &Expr<'_>) -> Result<String, CompileError> {
1127        let mut buf = Buffer::new(0);
1128        self.visit_expr(&mut buf, expr)?;
1129        Ok(buf.buf)
1130    }
1131
1132    fn visit_expr(
1133        &mut self,
1134        buf: &mut Buffer,
1135        expr: &Expr<'_>,
1136    ) -> Result<DisplayWrap, CompileError> {
1137        Ok(match *expr {
1138            Expr::BoolLit(s) => self.visit_bool_lit(buf, s),
1139            Expr::NumLit(s) => self.visit_num_lit(buf, s),
1140            Expr::StrLit(s) => self.visit_str_lit(buf, s),
1141            Expr::CharLit(s) => self.visit_char_lit(buf, s),
1142            Expr::Var(s) => self.visit_var(buf, s),
1143            Expr::Path(ref path) => self.visit_path(buf, path),
1144            Expr::Array(ref elements) => self.visit_array(buf, elements)?,
1145            Expr::Attr(ref obj, name) => self.visit_attr(buf, obj, name)?,
1146            Expr::Index(ref obj, ref key) => self.visit_index(buf, obj, key)?,
1147            Expr::Filter(name, ref args) => self.visit_filter(buf, name, args)?,
1148            Expr::Unary(op, ref inner) => self.visit_unary(buf, op, inner)?,
1149            Expr::BinOp(op, ref left, ref right) => self.visit_binop(buf, op, left, right)?,
1150            Expr::Range(op, ref left, ref right) => {
1151                self.visit_range(buf, op, left.as_deref(), right.as_deref())?
1152            }
1153            Expr::Group(ref inner) => self.visit_group(buf, inner)?,
1154            Expr::Call(ref obj, ref args) => self.visit_call(buf, obj, args)?,
1155            Expr::RustMacro(ref path, args) => self.visit_rust_macro(buf, path, args),
1156            Expr::Try(ref expr) => self.visit_try(buf, expr.as_ref())?,
1157            Expr::Tuple(ref exprs) => self.visit_tuple(buf, exprs)?,
1158            Expr::NamedArgument(_, ref expr) => self.visit_named_argument(buf, expr)?,
1159        })
1160    }
1161
1162    fn visit_try(
1163        &mut self,
1164        buf: &mut Buffer,
1165        expr: &Expr<'_>,
1166    ) -> Result<DisplayWrap, CompileError> {
1167        buf.write("::core::result::Result::map_err(");
1168        self.visit_expr(buf, expr)?;
1169        buf.write(", |err| ::askama::shared::Error::Custom(::core::convert::Into::into(err)))?");
1170        Ok(DisplayWrap::Unwrapped)
1171    }
1172
1173    fn visit_rust_macro(&mut self, buf: &mut Buffer, path: &[&str], args: &str) -> DisplayWrap {
1174        self.visit_path(buf, path);
1175        buf.write("!(");
1176        buf.write(args);
1177        buf.write(")");
1178
1179        DisplayWrap::Unwrapped
1180    }
1181
1182    #[cfg(not(feature = "markdown"))]
1183    fn _visit_markdown_filter(
1184        &mut self,
1185        _buf: &mut Buffer,
1186        _args: &[Expr<'_>],
1187    ) -> Result<DisplayWrap, CompileError> {
1188        Err("the `markdown` filter requires the `markdown` feature to be enabled".into())
1189    }
1190
1191    #[cfg(feature = "markdown")]
1192    fn _visit_markdown_filter(
1193        &mut self,
1194        buf: &mut Buffer,
1195        args: &[Expr<'_>],
1196    ) -> Result<DisplayWrap, CompileError> {
1197        let (md, options) = match args {
1198            [md] => (md, None),
1199            [md, options] => (md, Some(options)),
1200            _ => return Err("markdown filter expects no more than one option argument".into()),
1201        };
1202
1203        buf.write(&format!(
1204            "::askama::filters::markdown({}, &",
1205            self.input.escaper
1206        ));
1207        self.visit_expr(buf, md)?;
1208        match options {
1209            Some(options) => {
1210                buf.write(", ::core::option::Option::Some(");
1211                self.visit_expr(buf, options)?;
1212                buf.write(")");
1213            }
1214            None => buf.write(", ::core::option::Option::None"),
1215        }
1216        buf.write(")?");
1217
1218        Ok(DisplayWrap::Wrapped)
1219    }
1220
1221    fn _visit_as_ref_filter(
1222        &mut self,
1223        buf: &mut Buffer,
1224        args: &[Expr<'_>],
1225    ) -> Result<(), CompileError> {
1226        let arg = match args {
1227            [arg] => arg,
1228            _ => return Err("unexpected argument(s) in `as_ref` filter".into()),
1229        };
1230        buf.write("&");
1231        self.visit_expr(buf, arg)?;
1232        Ok(())
1233    }
1234
1235    fn visit_filter(
1236        &mut self,
1237        buf: &mut Buffer,
1238        mut name: &str,
1239        args: &[Expr<'_>],
1240    ) -> Result<DisplayWrap, CompileError> {
1241        if matches!(name, "escape" | "e") {
1242            self._visit_escape_filter(buf, args)?;
1243            return Ok(DisplayWrap::Wrapped);
1244        } else if name == "format" {
1245            self._visit_format_filter(buf, args)?;
1246            return Ok(DisplayWrap::Unwrapped);
1247        } else if name == "fmt" {
1248            self._visit_fmt_filter(buf, args)?;
1249            return Ok(DisplayWrap::Unwrapped);
1250        } else if name == "join" {
1251            self._visit_join_filter(buf, args)?;
1252            return Ok(DisplayWrap::Unwrapped);
1253        } else if name == "markdown" {
1254            return self._visit_markdown_filter(buf, args);
1255        } else if name == "as_ref" {
1256            self._visit_as_ref_filter(buf, args)?;
1257            return Ok(DisplayWrap::Wrapped);
1258        }
1259
1260        if name == "tojson" {
1261            name = "json";
1262        }
1263
1264        #[cfg(not(feature = "serde-json"))]
1265        if name == "json" {
1266            return Err("the `json` filter requires the `serde-json` feature to be enabled".into());
1267        }
1268        #[cfg(not(feature = "serde-yaml"))]
1269        if name == "yaml" {
1270            return Err("the `yaml` filter requires the `serde-yaml` feature to be enabled".into());
1271        }
1272
1273        const FILTERS: [&str; 2] = ["safe", "yaml"];
1274        if FILTERS.contains(&name) {
1275            buf.write(&format!(
1276                "::askama::filters::{}({}, ",
1277                name, self.input.escaper
1278            ));
1279        } else if crate::BUILT_IN_FILTERS.contains(&name) {
1280            buf.write(&format!("::askama::filters::{name}("));
1281        } else {
1282            buf.write(&format!("filters::{name}("));
1283        }
1284
1285        self._visit_args(buf, args)?;
1286        buf.write(")?");
1287        Ok(match FILTERS.contains(&name) {
1288            true => DisplayWrap::Wrapped,
1289            false => DisplayWrap::Unwrapped,
1290        })
1291    }
1292
1293    fn _visit_escape_filter(
1294        &mut self,
1295        buf: &mut Buffer,
1296        args: &[Expr<'_>],
1297    ) -> Result<(), CompileError> {
1298        if args.len() > 2 {
1299            return Err("only two arguments allowed to escape filter".into());
1300        }
1301        let opt_escaper = match args.get(1) {
1302            Some(Expr::StrLit(name)) => Some(*name),
1303            Some(_) => return Err("invalid escaper type for escape filter".into()),
1304            None => None,
1305        };
1306        let escaper = match opt_escaper {
1307            Some(name) => self
1308                .input
1309                .config
1310                .escapers
1311                .iter()
1312                .find_map(|(escapers, escaper)| escapers.contains(name).then_some(escaper))
1313                .ok_or_else(|| CompileError::from("invalid escaper for escape filter"))?,
1314            None => self.input.escaper,
1315        };
1316        buf.write("::askama::filters::escape(");
1317        buf.write(escaper);
1318        buf.write(", ");
1319        self._visit_args(buf, &args[..1])?;
1320        buf.write(")?");
1321        Ok(())
1322    }
1323
1324    fn _visit_format_filter(
1325        &mut self,
1326        buf: &mut Buffer,
1327        args: &[Expr<'_>],
1328    ) -> Result<(), CompileError> {
1329        buf.write("format!(");
1330        if let Some(Expr::StrLit(v)) = args.first() {
1331            self.visit_str_lit(buf, v);
1332            if args.len() > 1 {
1333                buf.write(", ");
1334            }
1335        } else {
1336            return Err("invalid expression type for format filter".into());
1337        }
1338        self._visit_args(buf, &args[1..])?;
1339        buf.write(")");
1340        Ok(())
1341    }
1342
1343    fn _visit_fmt_filter(
1344        &mut self,
1345        buf: &mut Buffer,
1346        args: &[Expr<'_>],
1347    ) -> Result<(), CompileError> {
1348        buf.write("format!(");
1349        if let Some(Expr::StrLit(v)) = args.get(1) {
1350            self.visit_str_lit(buf, v);
1351            buf.write(", ");
1352        } else {
1353            return Err("invalid expression type for fmt filter".into());
1354        }
1355        self._visit_args(buf, &args[0..1])?;
1356        if args.len() > 2 {
1357            return Err("only two arguments allowed to fmt filter".into());
1358        }
1359        buf.write(")");
1360        Ok(())
1361    }
1362
1363    // Force type coercion on first argument to `join` filter (see #39).
1364    fn _visit_join_filter(
1365        &mut self,
1366        buf: &mut Buffer,
1367        args: &[Expr<'_>],
1368    ) -> Result<(), CompileError> {
1369        buf.write("::askama::filters::join((&");
1370        for (i, arg) in args.iter().enumerate() {
1371            if i > 0 {
1372                buf.write(", &");
1373            }
1374            self.visit_expr(buf, arg)?;
1375            if i == 0 {
1376                buf.write(").into_iter()");
1377            }
1378        }
1379        buf.write(")?");
1380        Ok(())
1381    }
1382
1383    fn _visit_args(&mut self, buf: &mut Buffer, args: &[Expr<'_>]) -> Result<(), CompileError> {
1384        if args.is_empty() {
1385            return Ok(());
1386        }
1387
1388        for (i, arg) in args.iter().enumerate() {
1389            if i > 0 {
1390                buf.write(", ");
1391            }
1392
1393            let borrow = !is_copyable(arg);
1394            if borrow {
1395                buf.write("&(");
1396            }
1397
1398            match arg {
1399                Expr::Call(left, _) if !matches!(left.as_ref(), Expr::Path(_)) => {
1400                    buf.writeln("{")?;
1401                    self.visit_expr(buf, arg)?;
1402                    buf.writeln("}")?;
1403                }
1404                _ => {
1405                    self.visit_expr(buf, arg)?;
1406                }
1407            }
1408
1409            if borrow {
1410                buf.write(")");
1411            }
1412        }
1413        Ok(())
1414    }
1415
1416    fn visit_attr(
1417        &mut self,
1418        buf: &mut Buffer,
1419        obj: &Expr<'_>,
1420        attr: &str,
1421    ) -> Result<DisplayWrap, CompileError> {
1422        if let Expr::Var(name) = *obj {
1423            if name == "loop" {
1424                if attr == "index" {
1425                    buf.write("(_loop_item.index + 1)");
1426                    return Ok(DisplayWrap::Unwrapped);
1427                } else if attr == "index0" {
1428                    buf.write("_loop_item.index");
1429                    return Ok(DisplayWrap::Unwrapped);
1430                } else if attr == "first" {
1431                    buf.write("_loop_item.first");
1432                    return Ok(DisplayWrap::Unwrapped);
1433                } else if attr == "last" {
1434                    buf.write("_loop_item.last");
1435                    return Ok(DisplayWrap::Unwrapped);
1436                } else {
1437                    return Err("unknown loop variable".into());
1438                }
1439            }
1440        }
1441        self.visit_expr(buf, obj)?;
1442        buf.write(&format!(".{}", normalize_identifier(attr)));
1443        Ok(DisplayWrap::Unwrapped)
1444    }
1445
1446    fn visit_index(
1447        &mut self,
1448        buf: &mut Buffer,
1449        obj: &Expr<'_>,
1450        key: &Expr<'_>,
1451    ) -> Result<DisplayWrap, CompileError> {
1452        buf.write("&");
1453        self.visit_expr(buf, obj)?;
1454        buf.write("[");
1455        self.visit_expr(buf, key)?;
1456        buf.write("]");
1457        Ok(DisplayWrap::Unwrapped)
1458    }
1459
1460    fn visit_call(
1461        &mut self,
1462        buf: &mut Buffer,
1463        left: &Expr<'_>,
1464        args: &[Expr<'_>],
1465    ) -> Result<DisplayWrap, CompileError> {
1466        match left {
1467            Expr::Attr(left, method) if **left == Expr::Var("loop") => match *method {
1468                "cycle" => match args {
1469                    [arg] => {
1470                        if matches!(arg, Expr::Array(arr) if arr.is_empty()) {
1471                            return Err("loop.cycle(…) cannot use an empty array".into());
1472                        }
1473                        buf.write("({");
1474                        buf.write("let _cycle = &(");
1475                        self.visit_expr(buf, arg)?;
1476                        buf.writeln(");")?;
1477                        buf.writeln("let _len = _cycle.len();")?;
1478                        buf.writeln("if _len == 0 {")?;
1479                        buf.writeln("return ::core::result::Result::Err(::askama::Error::Fmt(::core::fmt::Error));")?;
1480                        buf.writeln("}")?;
1481                        buf.writeln("_cycle[_loop_item.index % _len]")?;
1482                        buf.writeln("})")?;
1483                    }
1484                    _ => return Err("loop.cycle(…) expects exactly one argument".into()),
1485                },
1486                s => return Err(format!("unknown loop method: {s:?}").into()),
1487            },
1488            left => {
1489                match left {
1490                    Expr::Var(name) => match self.locals.resolve(name) {
1491                        Some(resolved) => buf.write(&resolved),
1492                        None => buf.write(&format!("(&self.{})", normalize_identifier(name))),
1493                    },
1494                    left => {
1495                        self.visit_expr(buf, left)?;
1496                    }
1497                }
1498
1499                buf.write("(");
1500                self._visit_args(buf, args)?;
1501                buf.write(")");
1502            }
1503        }
1504        Ok(DisplayWrap::Unwrapped)
1505    }
1506
1507    fn visit_unary(
1508        &mut self,
1509        buf: &mut Buffer,
1510        op: &str,
1511        inner: &Expr<'_>,
1512    ) -> Result<DisplayWrap, CompileError> {
1513        buf.write(op);
1514        self.visit_expr(buf, inner)?;
1515        Ok(DisplayWrap::Unwrapped)
1516    }
1517
1518    fn visit_range(
1519        &mut self,
1520        buf: &mut Buffer,
1521        op: &str,
1522        left: Option<&Expr<'_>>,
1523        right: Option<&Expr<'_>>,
1524    ) -> Result<DisplayWrap, CompileError> {
1525        if let Some(left) = left {
1526            self.visit_expr(buf, left)?;
1527        }
1528        buf.write(op);
1529        if let Some(right) = right {
1530            self.visit_expr(buf, right)?;
1531        }
1532        Ok(DisplayWrap::Unwrapped)
1533    }
1534
1535    fn visit_binop(
1536        &mut self,
1537        buf: &mut Buffer,
1538        op: &str,
1539        left: &Expr<'_>,
1540        right: &Expr<'_>,
1541    ) -> Result<DisplayWrap, CompileError> {
1542        self.visit_expr(buf, left)?;
1543        buf.write(&format!(" {op} "));
1544        self.visit_expr(buf, right)?;
1545        Ok(DisplayWrap::Unwrapped)
1546    }
1547
1548    fn visit_group(
1549        &mut self,
1550        buf: &mut Buffer,
1551        inner: &Expr<'_>,
1552    ) -> Result<DisplayWrap, CompileError> {
1553        buf.write("(");
1554        self.visit_expr(buf, inner)?;
1555        buf.write(")");
1556        Ok(DisplayWrap::Unwrapped)
1557    }
1558
1559    fn visit_tuple(
1560        &mut self,
1561        buf: &mut Buffer,
1562        exprs: &[Expr<'_>],
1563    ) -> Result<DisplayWrap, CompileError> {
1564        buf.write("(");
1565        for (index, expr) in exprs.iter().enumerate() {
1566            if index > 0 {
1567                buf.write(" ");
1568            }
1569            self.visit_expr(buf, expr)?;
1570            buf.write(",");
1571        }
1572        buf.write(")");
1573        Ok(DisplayWrap::Unwrapped)
1574    }
1575
1576    fn visit_named_argument(
1577        &mut self,
1578        buf: &mut Buffer,
1579        expr: &Expr<'_>,
1580    ) -> Result<DisplayWrap, CompileError> {
1581        self.visit_expr(buf, expr)?;
1582        Ok(DisplayWrap::Unwrapped)
1583    }
1584
1585    fn visit_array(
1586        &mut self,
1587        buf: &mut Buffer,
1588        elements: &[Expr<'_>],
1589    ) -> Result<DisplayWrap, CompileError> {
1590        buf.write("[");
1591        for (i, el) in elements.iter().enumerate() {
1592            if i > 0 {
1593                buf.write(", ");
1594            }
1595            self.visit_expr(buf, el)?;
1596        }
1597        buf.write("]");
1598        Ok(DisplayWrap::Unwrapped)
1599    }
1600
1601    fn visit_path(&mut self, buf: &mut Buffer, path: &[&str]) -> DisplayWrap {
1602        for (i, part) in path.iter().enumerate() {
1603            if i > 0 {
1604                buf.write("::");
1605            }
1606            buf.write(part);
1607        }
1608        DisplayWrap::Unwrapped
1609    }
1610
1611    fn visit_var(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap {
1612        if s == "self" {
1613            buf.write(s);
1614            return DisplayWrap::Unwrapped;
1615        }
1616
1617        buf.write(normalize_identifier(&self.locals.resolve_or_self(s)));
1618        DisplayWrap::Unwrapped
1619    }
1620
1621    fn visit_bool_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap {
1622        buf.write(s);
1623        DisplayWrap::Unwrapped
1624    }
1625
1626    fn visit_str_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap {
1627        buf.write(&format!("\"{s}\""));
1628        DisplayWrap::Unwrapped
1629    }
1630
1631    fn visit_char_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap {
1632        buf.write(&format!("'{s}'"));
1633        DisplayWrap::Unwrapped
1634    }
1635
1636    fn visit_num_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap {
1637        buf.write(s);
1638        DisplayWrap::Unwrapped
1639    }
1640
1641    fn visit_target(
1642        &mut self,
1643        buf: &mut Buffer,
1644        initialized: bool,
1645        first_level: bool,
1646        target: &Target<'a>,
1647    ) {
1648        match target {
1649            Target::Name("_") => {
1650                buf.write("_");
1651            }
1652            Target::Name(name) => {
1653                let name = normalize_identifier(name);
1654                match initialized {
1655                    true => self.locals.insert(name, LocalMeta::initialized()),
1656                    false => self.locals.insert_with_default(name),
1657                }
1658                buf.write(name);
1659            }
1660            Target::OrChain(targets) => match targets.first() {
1661                None => buf.write("_"),
1662                Some(first_target) => {
1663                    self.visit_target(buf, initialized, first_level, first_target);
1664                    for target in &targets[1..] {
1665                        buf.write(" | ");
1666                        self.visit_target(buf, initialized, first_level, target);
1667                    }
1668                }
1669            },
1670            Target::Tuple(path, targets) => {
1671                buf.write(&path.join("::"));
1672                buf.write("(");
1673                for target in targets {
1674                    self.visit_target(buf, initialized, false, target);
1675                    buf.write(",");
1676                }
1677                buf.write(")");
1678            }
1679            Target::Struct(path, targets) => {
1680                buf.write(&path.join("::"));
1681                buf.write(" { ");
1682                for (name, target) in targets {
1683                    buf.write(normalize_identifier(name));
1684                    buf.write(": ");
1685                    self.visit_target(buf, initialized, false, target);
1686                    buf.write(",");
1687                }
1688                buf.write(" }");
1689            }
1690            Target::Path(path) => {
1691                self.visit_path(buf, path);
1692            }
1693            Target::StrLit(s) => {
1694                if first_level {
1695                    buf.write("&");
1696                }
1697                self.visit_str_lit(buf, s);
1698            }
1699            Target::NumLit(s) => {
1700                if first_level {
1701                    buf.write("&");
1702                }
1703                self.visit_num_lit(buf, s);
1704            }
1705            Target::CharLit(s) => {
1706                if first_level {
1707                    buf.write("&");
1708                }
1709                self.visit_char_lit(buf, s);
1710            }
1711            Target::BoolLit(s) => {
1712                if first_level {
1713                    buf.write("&");
1714                }
1715                buf.write(s);
1716            }
1717        }
1718    }
1719
1720    /* Helper methods for dealing with whitespace nodes */
1721
1722    // Combines `flush_ws()` and `prepare_ws()` to handle both trailing whitespace from the
1723    // preceding literal and leading whitespace from the succeeding literal.
1724    fn handle_ws(&mut self, ws: Ws) {
1725        self.flush_ws(ws);
1726        self.prepare_ws(ws);
1727    }
1728
1729    fn should_trim_ws(&self, ws: Option<Whitespace>) -> WhitespaceHandling {
1730        match ws {
1731            Some(Whitespace::Suppress) => WhitespaceHandling::Suppress,
1732            Some(Whitespace::Preserve) => WhitespaceHandling::Preserve,
1733            Some(Whitespace::Minimize) => WhitespaceHandling::Minimize,
1734            None => self.input.config.whitespace,
1735        }
1736    }
1737
1738    // If the previous literal left some trailing whitespace in `next_ws` and the
1739    // prefix whitespace suppressor from the given argument, flush that whitespace.
1740    // In either case, `next_ws` is reset to `None` (no trailing whitespace).
1741    fn flush_ws(&mut self, ws: Ws) {
1742        if self.next_ws.is_none() {
1743            return;
1744        }
1745
1746        // If `whitespace` is set to `suppress`, we keep the whitespace characters only if there is
1747        // a `+` character.
1748        match self.should_trim_ws(ws.0) {
1749            WhitespaceHandling::Preserve => {
1750                let val = self.next_ws.unwrap();
1751                if !val.is_empty() {
1752                    self.buf_writable.push(Writable::Lit(val));
1753                }
1754            }
1755            WhitespaceHandling::Minimize => {
1756                let val = self.next_ws.unwrap();
1757                if !val.is_empty() {
1758                    self.buf_writable
1759                        .push(Writable::Lit(match val.contains('\n') {
1760                            true => "\n",
1761                            false => " ",
1762                        }));
1763                }
1764            }
1765            WhitespaceHandling::Suppress => {}
1766        }
1767        self.next_ws = None;
1768    }
1769
1770    // Sets `skip_ws` to match the suffix whitespace suppressor from the given
1771    // argument, to determine whether to suppress leading whitespace from the
1772    // next literal.
1773    fn prepare_ws(&mut self, ws: Ws) {
1774        self.skip_ws = self.should_trim_ws(ws.1);
1775    }
1776}
1777
1778struct Buffer {
1779    // The buffer to generate the code into
1780    buf: String,
1781    // The current level of indentation (in spaces)
1782    indent: u8,
1783    // Whether the output buffer is currently at the start of a line
1784    start: bool,
1785}
1786
1787impl Buffer {
1788    fn new(indent: u8) -> Self {
1789        Self {
1790            buf: String::new(),
1791            indent,
1792            start: true,
1793        }
1794    }
1795
1796    fn writeln(&mut self, s: &str) -> Result<(), CompileError> {
1797        if s == "}" {
1798            self.dedent()?;
1799        }
1800        if !s.is_empty() {
1801            self.write(s);
1802        }
1803        self.buf.push('\n');
1804        if s.ends_with('{') {
1805            self.indent();
1806        }
1807        self.start = true;
1808        Ok(())
1809    }
1810
1811    fn write(&mut self, s: &str) {
1812        if self.start {
1813            for _ in 0..(self.indent * 4) {
1814                self.buf.push(' ');
1815            }
1816            self.start = false;
1817        }
1818        self.buf.push_str(s);
1819    }
1820
1821    fn indent(&mut self) {
1822        self.indent += 1;
1823    }
1824
1825    fn dedent(&mut self) -> Result<(), CompileError> {
1826        if self.indent == 0 {
1827            return Err("dedent() called while indentation == 0".into());
1828        }
1829        self.indent -= 1;
1830        Ok(())
1831    }
1832}
1833
1834#[derive(Clone, Default)]
1835pub(crate) struct LocalMeta {
1836    refs: Option<String>,
1837    initialized: bool,
1838}
1839
1840impl LocalMeta {
1841    fn initialized() -> Self {
1842        Self {
1843            refs: None,
1844            initialized: true,
1845        }
1846    }
1847
1848    fn with_ref(refs: String) -> Self {
1849        Self {
1850            refs: Some(refs),
1851            initialized: true,
1852        }
1853    }
1854}
1855
1856// type SetChain<'a, T> = MapChain<'a, T, ()>;
1857
1858#[derive(Debug)]
1859pub(crate) struct MapChain<'a, K, V>
1860where
1861    K: cmp::Eq + hash::Hash,
1862{
1863    parent: Option<&'a MapChain<'a, K, V>>,
1864    scopes: Vec<HashMap<K, V>>,
1865}
1866
1867impl<'a, K: 'a, V: 'a> MapChain<'a, K, V>
1868where
1869    K: cmp::Eq + hash::Hash,
1870{
1871    fn with_parent<'p>(parent: &'p MapChain<'_, K, V>) -> MapChain<'p, K, V> {
1872        MapChain {
1873            parent: Some(parent),
1874            scopes: vec![HashMap::new()],
1875        }
1876    }
1877
1878    /// Iterates the scopes in reverse and returns `Some(LocalMeta)`
1879    /// from the first scope where `key` exists.
1880    fn get(&self, key: &K) -> Option<&V> {
1881        let mut scopes = self.scopes.iter().rev();
1882        scopes
1883            .find_map(|set| set.get(key))
1884            .or_else(|| self.parent.and_then(|set| set.get(key)))
1885    }
1886
1887    fn is_current_empty(&self) -> bool {
1888        self.scopes.last().unwrap().is_empty()
1889    }
1890
1891    fn insert(&mut self, key: K, val: V) {
1892        self.scopes.last_mut().unwrap().insert(key, val);
1893
1894        // Note that if `insert` returns `Some` then it implies
1895        // an identifier is reused. For e.g. `{% macro f(a, a) %}`
1896        // and `{% let (a, a) = ... %}` then this results in a
1897        // generated template, which when compiled fails with the
1898        // compile error "identifier `a` used more than once".
1899    }
1900
1901    fn insert_with_default(&mut self, key: K)
1902    where
1903        V: Default,
1904    {
1905        self.insert(key, V::default());
1906    }
1907
1908    fn push(&mut self) {
1909        self.scopes.push(HashMap::new());
1910    }
1911
1912    fn pop(&mut self) {
1913        self.scopes.pop().unwrap();
1914        assert!(!self.scopes.is_empty());
1915    }
1916}
1917
1918impl MapChain<'_, &str, LocalMeta> {
1919    fn resolve(&self, name: &str) -> Option<String> {
1920        let name = normalize_identifier(name);
1921        self.get(&name).map(|meta| match &meta.refs {
1922            Some(expr) => expr.clone(),
1923            None => name.to_string(),
1924        })
1925    }
1926
1927    fn resolve_or_self(&self, name: &str) -> String {
1928        let name = normalize_identifier(name);
1929        self.resolve(name).unwrap_or_else(|| format!("self.{name}"))
1930    }
1931}
1932
1933impl<'a, K: Eq + hash::Hash, V> Default for MapChain<'a, K, V> {
1934    fn default() -> Self {
1935        Self {
1936            parent: None,
1937            scopes: vec![HashMap::new()],
1938        }
1939    }
1940}
1941
1942/// Returns `true` if enough assumptions can be made,
1943/// to determine that `self` is copyable.
1944fn is_copyable(expr: &Expr<'_>) -> bool {
1945    is_copyable_within_op(expr, false)
1946}
1947
1948fn is_copyable_within_op(expr: &Expr<'_>, within_op: bool) -> bool {
1949    use Expr::*;
1950    match expr {
1951        BoolLit(_) | NumLit(_) | StrLit(_) | CharLit(_) => true,
1952        Unary(.., expr) => is_copyable_within_op(expr, true),
1953        BinOp(_, lhs, rhs) => is_copyable_within_op(lhs, true) && is_copyable_within_op(rhs, true),
1954        Range(..) => true,
1955        // The result of a call likely doesn't need to be borrowed,
1956        // as in that case the call is more likely to return a
1957        // reference in the first place then.
1958        Call(..) | Path(..) => true,
1959        // If the `expr` is within a `Unary` or `BinOp` then
1960        // an assumption can be made that the operand is copy.
1961        // If not, then the value is moved and adding `.clone()`
1962        // will solve that issue. However, if the operand is
1963        // implicitly borrowed, then it's likely not even possible
1964        // to get the template to compile.
1965        _ => within_op && is_attr_self(expr),
1966    }
1967}
1968
1969/// Returns `true` if this is an `Attr` where the `obj` is `"self"`.
1970pub(crate) fn is_attr_self(expr: &Expr<'_>) -> bool {
1971    match expr {
1972        Expr::Attr(obj, _) if matches!(obj.as_ref(), Expr::Var("self")) => true,
1973        Expr::Attr(obj, _) if matches!(obj.as_ref(), Expr::Attr(..)) => is_attr_self(obj),
1974        _ => false,
1975    }
1976}
1977
1978/// Returns `true` if the outcome of this expression may be used multiple times in the same
1979/// `write!()` call, without evaluating the expression again, i.e. the expression should be
1980/// side-effect free.
1981pub(crate) fn is_cacheable(expr: &Expr<'_>) -> bool {
1982    match expr {
1983        // Literals are the definition of pure:
1984        Expr::BoolLit(_) => true,
1985        Expr::NumLit(_) => true,
1986        Expr::StrLit(_) => true,
1987        Expr::CharLit(_) => true,
1988        // fmt::Display should have no effects:
1989        Expr::Var(_) => true,
1990        Expr::Path(_) => true,
1991        // Check recursively:
1992        Expr::Array(args) => args.iter().all(is_cacheable),
1993        Expr::Attr(lhs, _) => is_cacheable(lhs),
1994        Expr::Index(lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs),
1995        Expr::Filter(_, args) => args.iter().all(is_cacheable),
1996        Expr::Unary(_, arg) => is_cacheable(arg),
1997        Expr::BinOp(_, lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs),
1998        Expr::Range(_, lhs, rhs) => {
1999            lhs.as_ref().map_or(true, |v| is_cacheable(v))
2000                && rhs.as_ref().map_or(true, |v| is_cacheable(v))
2001        }
2002        Expr::Group(arg) => is_cacheable(arg),
2003        Expr::Tuple(args) => args.iter().all(is_cacheable),
2004        Expr::NamedArgument(_, expr) => is_cacheable(expr),
2005        // We have too little information to tell if the expression is pure:
2006        Expr::Call(_, _) => false,
2007        Expr::RustMacro(_, _) => false,
2008        Expr::Try(_) => false,
2009    }
2010}
2011
2012fn median(sizes: &mut [usize]) -> usize {
2013    sizes.sort_unstable();
2014    if sizes.len() % 2 == 1 {
2015        sizes[sizes.len() / 2]
2016    } else {
2017        (sizes[sizes.len() / 2 - 1] + sizes[sizes.len() / 2]) / 2
2018    }
2019}
2020
2021#[derive(Clone, Copy, PartialEq)]
2022enum AstLevel {
2023    Top,
2024    Block,
2025    Nested,
2026}
2027
2028#[derive(Clone, Copy)]
2029enum DisplayWrap {
2030    Wrapped,
2031    Unwrapped,
2032}
2033
2034#[derive(Debug)]
2035enum Writable<'a> {
2036    Lit(&'a str),
2037    Expr(&'a Expr<'a>),
2038}
2039
2040// Identifiers to be replaced with raw identifiers, so as to avoid
2041// collisions between template syntax and Rust's syntax. In particular
2042// [Rust keywords](https://doc.rust-lang.org/reference/keywords.html)
2043// should be replaced, since they're not reserved words in Askama
2044// syntax but have a high probability of causing problems in the
2045// generated code.
2046//
2047// This list excludes the Rust keywords *self*, *Self*, and *super*
2048// because they are not allowed to be raw identifiers, and *loop*
2049// because it's used something like a keyword in the template
2050// language.
2051fn normalize_identifier(ident: &str) -> &str {
2052    // This table works for as long as the replacement string is the original string
2053    // prepended with "r#". The strings get right-padded to the same length with b'_'.
2054    // While the code does not need it, please keep the list sorted when adding new
2055    // keywords.
2056
2057    // FIXME: Replace with `[core:ascii::Char; MAX_REPL_LEN]` once
2058    //        <https://github.com/rust-lang/rust/issues/110998> is stable.
2059
2060    const MAX_KW_LEN: usize = 8;
2061    const MAX_REPL_LEN: usize = MAX_KW_LEN + 2;
2062
2063    const KW0: &[[u8; MAX_REPL_LEN]] = &[];
2064    const KW1: &[[u8; MAX_REPL_LEN]] = &[];
2065    const KW2: &[[u8; MAX_REPL_LEN]] = &[
2066        *b"r#as______",
2067        *b"r#do______",
2068        *b"r#fn______",
2069        *b"r#if______",
2070        *b"r#in______",
2071    ];
2072    const KW3: &[[u8; MAX_REPL_LEN]] = &[
2073        *b"r#box_____",
2074        *b"r#dyn_____",
2075        *b"r#for_____",
2076        *b"r#let_____",
2077        *b"r#mod_____",
2078        *b"r#mut_____",
2079        *b"r#pub_____",
2080        *b"r#ref_____",
2081        *b"r#try_____",
2082        *b"r#use_____",
2083    ];
2084    const KW4: &[[u8; MAX_REPL_LEN]] = &[
2085        *b"r#else____",
2086        *b"r#enum____",
2087        *b"r#impl____",
2088        *b"r#move____",
2089        *b"r#priv____",
2090        *b"r#true____",
2091        *b"r#type____",
2092    ];
2093    const KW5: &[[u8; MAX_REPL_LEN]] = &[
2094        *b"r#async___",
2095        *b"r#await___",
2096        *b"r#break___",
2097        *b"r#const___",
2098        *b"r#crate___",
2099        *b"r#false___",
2100        *b"r#final___",
2101        *b"r#macro___",
2102        *b"r#match___",
2103        *b"r#trait___",
2104        *b"r#where___",
2105        *b"r#while___",
2106        *b"r#yield___",
2107    ];
2108    const KW6: &[[u8; MAX_REPL_LEN]] = &[
2109        *b"r#become__",
2110        *b"r#extern__",
2111        *b"r#return__",
2112        *b"r#static__",
2113        *b"r#struct__",
2114        *b"r#typeof__",
2115        *b"r#unsafe__",
2116    ];
2117    const KW7: &[[u8; MAX_REPL_LEN]] = &[*b"r#unsized_", *b"r#virtual_"];
2118    const KW8: &[[u8; MAX_REPL_LEN]] = &[*b"r#abstract", *b"r#continue", *b"r#override"];
2119
2120    const KWS: &[&[[u8; MAX_REPL_LEN]]] = &[KW0, KW1, KW2, KW3, KW4, KW5, KW6, KW7, KW8];
2121
2122    // Ensure that all strings are ASCII, because we use `from_utf8_unchecked()` further down.
2123    const _: () = {
2124        let mut i = 0;
2125        while i < KWS.len() {
2126            let mut j = 0;
2127            while KWS[i].len() < j {
2128                let mut k = 0;
2129                while KWS[i][j].len() < k {
2130                    assert!(KWS[i][j][k].is_ascii());
2131                    k += 1;
2132                }
2133                j += 1;
2134            }
2135            i += 1;
2136        }
2137    };
2138
2139    if ident.len() > MAX_KW_LEN {
2140        return ident;
2141    }
2142    let kws = KWS[ident.len()];
2143
2144    let mut padded_ident = [b'_'; MAX_KW_LEN];
2145    padded_ident[..ident.len()].copy_from_slice(ident.as_bytes());
2146
2147    // Since the individual buckets are quite short, a linear search is faster than a binary search.
2148    let replacement = match kws
2149        .iter()
2150        .find(|probe| padded_ident == <[u8; MAX_KW_LEN]>::try_from(&probe[2..]).unwrap())
2151    {
2152        Some(replacement) => replacement,
2153        None => return ident,
2154    };
2155
2156    // SAFETY: We know that the input byte slice is pure-ASCII.
2157    unsafe { std::str::from_utf8_unchecked(&replacement[..ident.len() + 2]) }
2158}