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: &str = normalize_identifier(ident:name);
1921 self.get(&name).map(|meta: &LocalMeta| match &meta.refs {
1922 Some(expr: &String) => expr.clone(),
1923 None => name.to_string(),
1924 })
1925 }
1926
1927 fn resolve_or_self(&self, name: &str) -> String {
1928 let name: &str = normalize_identifier(ident: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, within_op: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: &Box>) => is_copyable_within_op(expr, within_op:true),
1953 BinOp(_, lhs: &Box>, rhs: &Box>) => is_copyable_within_op(expr:lhs, within_op:true) && is_copyable_within_op(expr:rhs, within_op: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: &Box>, _) if matches!(obj.as_ref(), Expr::Var("self")) => true,
1973 Expr::Attr(obj: &Box>, _) if matches!(obj.as_ref(), Expr::Attr(..)) => is_attr_self(expr: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}
2159