1use std::borrow::Cow;
2use std::collections::hash_map::{Entry, HashMap};
3use std::fmt::{Arguments, Display, Write};
4use std::ops::Deref;
5use std::path::Path;
6use std::sync::Arc;
7use std::{cmp, hash, mem, str};
8
9use parser::node::{
10 Call, Comment, Cond, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Match, Whitespace, Ws,
11};
12use parser::{
13 CharLit, CharPrefix, Expr, Filter, FloatKind, IntKind, Node, Num, StrLit, StrPrefix, Target,
14 WithSpan,
15};
16use quote::quote;
17use rustc_hash::FxBuildHasher;
18
19use crate::config::WhitespaceHandling;
20use crate::heritage::{Context, Heritage};
21use crate::html::write_escaped_str;
22use crate::input::{Source, TemplateInput};
23use crate::{BUILT_IN_FILTERS, CRATE, CompileError, FileInfo, MsgValidEscapers};
24
25#[derive(Clone, Copy, PartialEq, Debug)]
26enum EvaluatedResult {
27 AlwaysTrue,
28 AlwaysFalse,
29 Unknown,
30}
31
32pub(crate) struct Generator<'a> {
33 // The template input state: original struct AST and attributes
34 input: &'a TemplateInput<'a>,
35 // All contexts, keyed by the package-relative template path
36 contexts: &'a HashMap<&'a Arc<Path>, Context<'a>, FxBuildHasher>,
37 // The heritage contains references to blocks and their ancestry
38 heritage: Option<&'a Heritage<'a>>,
39 // Variables accessible directly from the current scope (not redirected to context)
40 locals: MapChain<'a, Cow<'a, str>, LocalMeta>,
41 // Suffix whitespace from the previous literal. Will be flushed to the
42 // output buffer unless suppressed by whitespace suppression on the next
43 // non-literal.
44 next_ws: Option<&'a str>,
45 // Whitespace suppression from the previous non-literal. Will be used to
46 // determine whether to flush prefix whitespace from the next literal.
47 skip_ws: WhitespaceHandling,
48 // If currently in a block, this will contain the name of a potential parent block
49 super_block: Option<(&'a str, usize)>,
50 // Buffer for writable
51 buf_writable: WritableBuffer<'a>,
52 // Used in blocks to check if we are inside a filter block.
53 is_in_filter_block: usize,
54}
55
56impl<'a> Generator<'a> {
57 pub(crate) fn new<'n>(
58 input: &'n TemplateInput<'_>,
59 contexts: &'n HashMap<&'n Arc<Path>, Context<'n>, FxBuildHasher>,
60 heritage: Option<&'n Heritage<'_>>,
61 locals: MapChain<'n, Cow<'n, str>, LocalMeta>,
62 buf_writable_discard: bool,
63 is_in_filter_block: usize,
64 ) -> Generator<'n> {
65 Generator {
66 input,
67 contexts,
68 heritage,
69 locals,
70 next_ws: None,
71 skip_ws: WhitespaceHandling::Preserve,
72 super_block: None,
73 buf_writable: WritableBuffer {
74 discard: buf_writable_discard,
75 ..Default::default()
76 },
77 is_in_filter_block,
78 }
79 }
80
81 // Takes a Context and generates the relevant implementations.
82 pub(crate) fn build(mut self, ctx: &Context<'a>) -> Result<String, CompileError> {
83 let mut buf = Buffer::new();
84 buf.write(format_args!(
85 "\
86 const _: () = {{\
87 extern crate {CRATE} as rinja;\
88 "
89 ));
90
91 if let Err(mut err) = self.impl_template(ctx, &mut buf) {
92 if err.span.is_none() {
93 err.span = self.input.source_span;
94 }
95 return Err(err);
96 }
97
98 self.impl_display(&mut buf);
99 self.impl_fast_writable(&mut buf);
100
101 #[cfg(feature = "with-actix-web")]
102 self.impl_actix_web_responder(&mut buf);
103 #[cfg(feature = "with-axum")]
104 self.impl_axum_into_response(&mut buf);
105 #[cfg(feature = "with-rocket")]
106 self.impl_rocket_responder(&mut buf);
107 #[cfg(feature = "with-warp")]
108 self.impl_warp_reply(&mut buf);
109
110 buf.write("};");
111
112 Ok(buf.buf)
113 }
114
115 fn push_locals<T, F: FnOnce(&mut Self) -> Result<T, CompileError>>(
116 &mut self,
117 callback: F,
118 ) -> Result<T, CompileError> {
119 self.locals.scopes.push(HashMap::default());
120 let res = callback(self);
121 self.locals.scopes.pop().unwrap();
122 res
123 }
124
125 // Implement `Template` for the given context struct.
126 fn impl_template(&mut self, ctx: &Context<'a>, buf: &mut Buffer) -> Result<(), CompileError> {
127 self.write_header(buf, "rinja::Template", None);
128 buf.write(
129 "fn render_into<RinjaW>(&self, writer: &mut RinjaW) -> rinja::Result<()>\
130 where \
131 RinjaW: rinja::helpers::core::fmt::Write + ?rinja::helpers::core::marker::Sized\
132 {\
133 use rinja::filters::{AutoEscape as _, WriteWritable as _};\
134 use rinja::helpers::core::fmt::Write as _;",
135 );
136
137 buf.set_discard(self.buf_writable.discard);
138 // Make sure the compiler understands that the generated code depends on the template files.
139 let mut paths = self
140 .contexts
141 .keys()
142 .map(|path| -> &Path { path })
143 .collect::<Vec<_>>();
144 paths.sort();
145 for path in paths {
146 // Skip the fake path of templates defined in rust source.
147 let path_is_valid = match self.input.source {
148 Source::Path(_) => true,
149 Source::Source(_) => path != &*self.input.path,
150 };
151 if path_is_valid {
152 let path = path.to_str().unwrap();
153 buf.write(format_args!(
154 "const _: &[rinja::helpers::core::primitive::u8] =\
155 rinja::helpers::core::include_bytes!({path:#?});",
156 ));
157 }
158 }
159
160 let size_hint = if let Some(heritage) = self.heritage {
161 self.handle(heritage.root, heritage.root.nodes, buf, AstLevel::Top)
162 } else {
163 self.handle(ctx, ctx.nodes, buf, AstLevel::Top)
164 }?;
165 buf.set_discard(false);
166
167 self.flush_ws(Ws(None, None));
168
169 buf.write(format_args!(
170 "\
171 rinja::Result::Ok(())\
172 }}\
173 const EXTENSION:\
174 rinja::helpers::core::option::Option<&'static rinja::helpers::core::primitive::str> =\
175 rinja::helpers::core::option::Option::{:?};\
176 const SIZE_HINT: rinja::helpers::core::primitive::usize = {size_hint}usize;\
177 const MIME_TYPE: &'static rinja::helpers::core::primitive::str = {:?};",
178 self.input.extension(),
179 self.input.mime_type,
180 ));
181
182 buf.write('}');
183 Ok(())
184 }
185
186 // Implement `Display` for the given context struct.
187 fn impl_display(&mut self, buf: &mut Buffer) {
188 let ident = &self.input.ast.ident;
189 buf.write(format_args!(
190 "\
191 /// Implement the [`format!()`][rinja::helpers::std::format] trait for [`{}`]\n\
192 ///\n\
193 /// Please be aware of the rendering performance notice in the \
194 [`Template`][rinja::Template] trait.\n\
195 ",
196 quote!(#ident),
197 ));
198 self.write_header(buf, "rinja::helpers::core::fmt::Display", None);
199 buf.write(
200 "\
201 #[inline]\
202 fn fmt(\
203 &self,\
204 f: &mut rinja::helpers::core::fmt::Formatter<'_>\
205 ) -> rinja::helpers::core::fmt::Result {\
206 rinja::Template::render_into(self, f)\
207 .map_err(|_| rinja::helpers::core::fmt::Error)\
208 }\
209 }",
210 );
211 }
212
213 // Implement `FastWritable` for the given context struct.
214 fn impl_fast_writable(&mut self, buf: &mut Buffer) {
215 self.write_header(buf, "rinja::filters::FastWritable", None);
216 buf.write(
217 "\
218 #[inline]\
219 fn write_into<RinjaW>(&self, dest: &mut RinjaW) -> rinja::helpers::core::fmt::Result \
220 where \
221 RinjaW: rinja::helpers::core::fmt::Write + ?rinja::helpers::core::marker::Sized,\
222 {\
223 rinja::Template::render_into(self, dest)\
224 .map_err(|_| rinja::helpers::core::fmt::Error)\
225 }\
226 }",
227 );
228 }
229
230 // Implement Actix-web's `Responder`.
231 #[cfg(feature = "with-actix-web")]
232 fn impl_actix_web_responder(&mut self, buf: &mut Buffer) {
233 self.write_header(buf, "::rinja_actix::actix_web::Responder", None);
234 buf.write(
235 "\
236 type Body = ::rinja_actix::actix_web::body::BoxBody;\
237 #[inline]\
238 fn respond_to(self, _req: &::rinja_actix::actix_web::HttpRequest)\
239 -> ::rinja_actix::actix_web::HttpResponse<Self::Body> {\
240 ::rinja_actix::into_response(&self)\
241 }\
242 }",
243 );
244 }
245
246 // Implement Axum's `IntoResponse`.
247 #[cfg(feature = "with-axum")]
248 fn impl_axum_into_response(&mut self, buf: &mut Buffer) {
249 self.write_header(buf, "::rinja_axum::axum_core::response::IntoResponse", None);
250 buf.write(
251 "\
252 #[inline]\
253 fn into_response(self) -> ::rinja_axum::axum_core::response::Response {\
254 ::rinja_axum::into_response(&self)\
255 }\
256 }",
257 );
258 }
259
260 // Implement Rocket's `Responder`.
261 #[cfg(feature = "with-rocket")]
262 fn impl_rocket_responder(&mut self, buf: &mut Buffer) {
263 let lifetime1 = syn::Lifetime::new("'rinja1", proc_macro2::Span::call_site());
264 let param1 = syn::GenericParam::Lifetime(syn::LifetimeParam::new(lifetime1));
265
266 self.write_header(
267 buf,
268 "::rinja_rocket::rocket::response::Responder<'rinja1, 'static>",
269 Some(vec![param1]),
270 );
271 buf.write(
272 "\
273 #[inline]\
274 fn respond_to(self, _: &'rinja1 ::rinja_rocket::rocket::request::Request<'_>)\
275 -> ::rinja_rocket::rocket::response::Result<'static>\
276 {\
277 ::rinja_rocket::respond(&self)\
278 }\
279 }",
280 );
281 }
282
283 #[cfg(feature = "with-warp")]
284 fn impl_warp_reply(&mut self, buf: &mut Buffer) {
285 self.write_header(buf, "::rinja_warp::warp::reply::Reply", None);
286 buf.write(
287 "\
288 #[inline]\
289 fn into_response(self) -> ::rinja_warp::warp::reply::Response {\
290 ::rinja_warp::into_response(&self)\
291 }\
292 }",
293 );
294 }
295
296 // Writes header for the `impl` for `TraitFromPathName` or `Template`
297 // for the given context struct.
298 fn write_header(
299 &mut self,
300 buf: &mut Buffer,
301 target: impl Display,
302 params: Option<Vec<syn::GenericParam>>,
303 ) {
304 let mut generics;
305 let (impl_generics, orig_ty_generics, where_clause) = if let Some(params) = params {
306 generics = self.input.ast.generics.clone();
307 for param in params {
308 generics.params.push(param);
309 }
310
311 let (_, orig_ty_generics, _) = self.input.ast.generics.split_for_impl();
312 let (impl_generics, _, where_clause) = generics.split_for_impl();
313 (impl_generics, orig_ty_generics, where_clause)
314 } else {
315 self.input.ast.generics.split_for_impl()
316 };
317
318 let ident = &self.input.ast.ident;
319 buf.write(format_args!(
320 "impl {} {} for {} {{",
321 quote!(#impl_generics),
322 target,
323 quote!(#ident #orig_ty_generics #where_clause),
324 ));
325 }
326
327 // Helper methods for handling node types
328
329 fn handle(
330 &mut self,
331 ctx: &Context<'a>,
332 nodes: &'a [Node<'_>],
333 buf: &mut Buffer,
334 level: AstLevel,
335 ) -> Result<usize, CompileError> {
336 let mut size_hint = 0;
337 for n in nodes {
338 match *n {
339 Node::Lit(ref lit) => {
340 self.visit_lit(lit);
341 }
342 Node::Comment(ref comment) => {
343 self.write_comment(comment);
344 }
345 Node::Expr(ws, ref val) => {
346 self.write_expr(ws, val);
347 }
348 Node::Let(ref l) => {
349 self.write_let(ctx, buf, l)?;
350 }
351 Node::If(ref i) => {
352 size_hint += self.write_if(ctx, buf, i)?;
353 }
354 Node::Match(ref m) => {
355 size_hint += self.write_match(ctx, buf, m)?;
356 }
357 Node::Loop(ref loop_block) => {
358 size_hint += self.write_loop(ctx, buf, loop_block)?;
359 }
360 Node::BlockDef(ref b) => {
361 size_hint +=
362 self.write_block(ctx, buf, Some(b.name), Ws(b.ws1.0, b.ws2.1), b)?;
363 }
364 Node::Include(ref i) => {
365 size_hint += self.handle_include(ctx, buf, i)?;
366 }
367 Node::Call(ref call) => {
368 size_hint += self.write_call(ctx, buf, call)?;
369 }
370 Node::FilterBlock(ref filter) => {
371 size_hint += self.write_filter_block(ctx, buf, filter)?;
372 }
373 Node::Macro(ref m) => {
374 if level != AstLevel::Top {
375 return Err(
376 ctx.generate_error("macro blocks only allowed at the top level", m)
377 );
378 }
379 self.flush_ws(m.ws1);
380 self.prepare_ws(m.ws2);
381 }
382 Node::Raw(ref raw) => {
383 self.handle_ws(raw.ws1);
384 self.visit_lit(&raw.lit);
385 self.handle_ws(raw.ws2);
386 }
387 Node::Import(ref i) => {
388 if level != AstLevel::Top {
389 return Err(
390 ctx.generate_error("import blocks only allowed at the top level", i)
391 );
392 }
393 self.handle_ws(i.ws);
394 }
395 Node::Extends(ref e) => {
396 if level != AstLevel::Top {
397 return Err(
398 ctx.generate_error("extend blocks only allowed at the top level", e)
399 );
400 }
401 // No whitespace handling: child template top-level is not used,
402 // except for the blocks defined in it.
403 }
404 Node::Break(ref ws) => {
405 self.handle_ws(**ws);
406 self.write_buf_writable(ctx, buf)?;
407 buf.write("break;");
408 }
409 Node::Continue(ref ws) => {
410 self.handle_ws(**ws);
411 self.write_buf_writable(ctx, buf)?;
412 buf.write("continue;");
413 }
414 }
415 }
416
417 if AstLevel::Top == level {
418 // Handle any pending whitespace.
419 if self.next_ws.is_some() {
420 self.flush_ws(Ws(Some(self.skip_ws.into()), None));
421 }
422
423 size_hint += self.write_buf_writable(ctx, buf)?;
424 }
425 Ok(size_hint)
426 }
427
428 fn is_var_defined(&self, var_name: &str) -> bool {
429 self.locals.get(&var_name.into()).is_some()
430 || self.input.fields.iter().any(|f| f == var_name)
431 }
432
433 fn evaluate_condition(
434 &self,
435 expr: WithSpan<'a, Expr<'a>>,
436 only_contains_is_defined: &mut bool,
437 ) -> (EvaluatedResult, WithSpan<'a, Expr<'a>>) {
438 let (expr, span) = expr.deconstruct();
439
440 match expr {
441 Expr::NumLit(_, _)
442 | Expr::StrLit(_)
443 | Expr::CharLit(_)
444 | Expr::Var(_)
445 | Expr::Path(_)
446 | Expr::Array(_)
447 | Expr::Attr(_, _)
448 | Expr::Index(_, _)
449 | Expr::Filter(_)
450 | Expr::Range(_, _, _)
451 | Expr::Call(_, _)
452 | Expr::RustMacro(_, _)
453 | Expr::Try(_)
454 | Expr::Tuple(_)
455 | Expr::NamedArgument(_, _)
456 | Expr::FilterSource
457 | Expr::As(_, _) => {
458 *only_contains_is_defined = false;
459 (EvaluatedResult::Unknown, WithSpan::new(expr, span))
460 }
461 Expr::BoolLit(true) => (EvaluatedResult::AlwaysTrue, WithSpan::new(expr, span)),
462 Expr::BoolLit(false) => (EvaluatedResult::AlwaysFalse, WithSpan::new(expr, span)),
463 Expr::Unary("!", inner) => {
464 let (result, expr) = self.evaluate_condition(*inner, only_contains_is_defined);
465 match result {
466 EvaluatedResult::AlwaysTrue => (
467 EvaluatedResult::AlwaysFalse,
468 WithSpan::new(Expr::BoolLit(false), ""),
469 ),
470 EvaluatedResult::AlwaysFalse => (
471 EvaluatedResult::AlwaysTrue,
472 WithSpan::new(Expr::BoolLit(true), ""),
473 ),
474 EvaluatedResult::Unknown => (
475 EvaluatedResult::Unknown,
476 WithSpan::new(Expr::Unary("!", Box::new(expr)), span),
477 ),
478 }
479 }
480 Expr::Unary(_, _) => (EvaluatedResult::Unknown, WithSpan::new(expr, span)),
481 Expr::BinOp("&&", left, right) => {
482 let (result_left, expr_left) =
483 self.evaluate_condition(*left, only_contains_is_defined);
484 if result_left == EvaluatedResult::AlwaysFalse {
485 // The right side of the `&&` won't be evaluated, no need to go any further.
486 return (result_left, WithSpan::new(Expr::BoolLit(false), ""));
487 }
488 let (result_right, expr_right) =
489 self.evaluate_condition(*right, only_contains_is_defined);
490 match (result_left, result_right) {
491 (EvaluatedResult::AlwaysTrue, EvaluatedResult::AlwaysTrue) => (
492 EvaluatedResult::AlwaysTrue,
493 WithSpan::new(Expr::BoolLit(true), ""),
494 ),
495 (_, EvaluatedResult::AlwaysFalse) => (
496 EvaluatedResult::AlwaysFalse,
497 WithSpan::new(
498 Expr::BinOp("&&", Box::new(expr_left), Box::new(expr_right)),
499 span,
500 ),
501 ),
502 (EvaluatedResult::AlwaysTrue, _) => (result_right, expr_right),
503 (_, EvaluatedResult::AlwaysTrue) => (result_left, expr_left),
504 _ => (
505 EvaluatedResult::Unknown,
506 WithSpan::new(
507 Expr::BinOp("&&", Box::new(expr_left), Box::new(expr_right)),
508 span,
509 ),
510 ),
511 }
512 }
513 Expr::BinOp("||", left, right) => {
514 let (result_left, expr_left) =
515 self.evaluate_condition(*left, only_contains_is_defined);
516 if result_left == EvaluatedResult::AlwaysTrue {
517 // The right side of the `||` won't be evaluated, no need to go any further.
518 return (result_left, WithSpan::new(Expr::BoolLit(true), ""));
519 }
520 let (result_right, expr_right) =
521 self.evaluate_condition(*right, only_contains_is_defined);
522 match (result_left, result_right) {
523 (EvaluatedResult::AlwaysFalse, EvaluatedResult::AlwaysFalse) => (
524 EvaluatedResult::AlwaysFalse,
525 WithSpan::new(Expr::BoolLit(false), ""),
526 ),
527 (_, EvaluatedResult::AlwaysTrue) => (
528 EvaluatedResult::AlwaysTrue,
529 WithSpan::new(
530 Expr::BinOp("||", Box::new(expr_left), Box::new(expr_right)),
531 span,
532 ),
533 ),
534 (EvaluatedResult::AlwaysFalse, _) => (result_right, expr_right),
535 (_, EvaluatedResult::AlwaysFalse) => (result_left, expr_left),
536 _ => (
537 EvaluatedResult::Unknown,
538 WithSpan::new(
539 Expr::BinOp("||", Box::new(expr_left), Box::new(expr_right)),
540 span,
541 ),
542 ),
543 }
544 }
545 Expr::BinOp(_, _, _) => {
546 *only_contains_is_defined = false;
547 (EvaluatedResult::Unknown, WithSpan::new(expr, span))
548 }
549 Expr::Group(inner) => {
550 let (result, expr) = self.evaluate_condition(*inner, only_contains_is_defined);
551 (result, WithSpan::new(Expr::Group(Box::new(expr)), span))
552 }
553 Expr::IsDefined(left) => {
554 // Variable is defined so we want to keep the condition.
555 if self.is_var_defined(left) {
556 (
557 EvaluatedResult::AlwaysTrue,
558 WithSpan::new(Expr::BoolLit(true), ""),
559 )
560 } else {
561 (
562 EvaluatedResult::AlwaysFalse,
563 WithSpan::new(Expr::BoolLit(false), ""),
564 )
565 }
566 }
567 Expr::IsNotDefined(left) => {
568 // Variable is defined so we don't want to keep the condition.
569 if self.is_var_defined(left) {
570 (
571 EvaluatedResult::AlwaysFalse,
572 WithSpan::new(Expr::BoolLit(false), ""),
573 )
574 } else {
575 (
576 EvaluatedResult::AlwaysTrue,
577 WithSpan::new(Expr::BoolLit(true), ""),
578 )
579 }
580 }
581 }
582 }
583
584 fn write_if(
585 &mut self,
586 ctx: &Context<'a>,
587 buf: &mut Buffer,
588 if_: &'a If<'_>,
589 ) -> Result<usize, CompileError> {
590 let mut flushed = 0;
591 let mut arm_sizes = Vec::new();
592 let mut has_else = false;
593
594 let conds = Conds::compute_branches(self, if_);
595
596 if let Some(ws_before) = conds.ws_before {
597 self.handle_ws(ws_before);
598 }
599
600 let mut iter = conds.conds.iter().enumerate().peekable();
601 while let Some((pos, cond_info)) = iter.next() {
602 let cond = cond_info.cond;
603
604 if pos == 0 {
605 self.handle_ws(cond.ws);
606 flushed += self.write_buf_writable(ctx, buf)?;
607 }
608
609 self.push_locals(|this| {
610 let mut arm_size = 0;
611
612 if let Some(CondTest { target, expr, .. }) = &cond.cond {
613 let expr = cond_info.cond_expr.as_ref().unwrap_or(expr);
614
615 if pos == 0 {
616 if cond_info.generate_condition {
617 buf.write("if ");
618 }
619 // Otherwise it means it will be the only condition generated,
620 // so nothing to be added here.
621 } else if cond_info.generate_condition {
622 buf.write("} else if ");
623 } else {
624 buf.write("} else {");
625 has_else = true;
626 }
627
628 if let Some(target) = target {
629 let mut expr_buf = Buffer::new();
630 buf.write("let ");
631 // If this is a chain condition, then we need to declare the variable after the
632 // left expression has been handled but before the right expression is handled
633 // but this one should have access to the let-bound variable.
634 match &**expr {
635 Expr::BinOp(op, ref left, ref right) if *op == "||" || *op == "&&" => {
636 this.visit_expr(ctx, &mut expr_buf, left)?;
637 this.visit_target(buf, true, true, target);
638 expr_buf.write(format_args!(" {op} "));
639 this.visit_condition(ctx, &mut expr_buf, right)?;
640 }
641 _ => {
642 this.visit_expr(ctx, &mut expr_buf, expr)?;
643 this.visit_target(buf, true, true, target);
644 }
645 }
646 buf.write(format_args!("= &{} {{", expr_buf.buf));
647 } else if cond_info.generate_condition {
648 this.visit_condition(ctx, buf, expr)?;
649 buf.write('{');
650 }
651 } else if pos != 0 {
652 buf.write("} else {");
653 has_else = true;
654 }
655
656 if cond_info.generate_content {
657 arm_size += this.handle(ctx, &cond.nodes, buf, AstLevel::Nested)?;
658 }
659 arm_sizes.push(arm_size);
660
661 if let Some((_, cond_info)) = iter.peek() {
662 let cond = cond_info.cond;
663
664 this.handle_ws(cond.ws);
665 flushed += this.write_buf_writable(ctx, buf)?;
666 } else {
667 if let Some(ws_after) = conds.ws_after {
668 this.handle_ws(ws_after);
669 }
670 this.handle_ws(if_.ws);
671 flushed += this.write_buf_writable(ctx, buf)?;
672 }
673 Ok(0)
674 })?;
675 }
676
677 if conds.nb_conds > 0 {
678 buf.write('}');
679 }
680
681 if !has_else && !conds.conds.is_empty() {
682 arm_sizes.push(0);
683 }
684 Ok(flushed + median(&mut arm_sizes))
685 }
686
687 #[allow(clippy::too_many_arguments)]
688 fn write_match(
689 &mut self,
690 ctx: &Context<'a>,
691 buf: &mut Buffer,
692 m: &'a Match<'a>,
693 ) -> Result<usize, CompileError> {
694 let Match {
695 ws1,
696 ref expr,
697 ref arms,
698 ws2,
699 } = *m;
700
701 self.flush_ws(ws1);
702 let flushed = self.write_buf_writable(ctx, buf)?;
703 let mut arm_sizes = Vec::new();
704
705 let expr_code = self.visit_expr_root(ctx, expr)?;
706 buf.write(format_args!("match &{expr_code} {{"));
707
708 let mut arm_size = 0;
709 let mut iter = arms.iter().enumerate().peekable();
710 while let Some((i, arm)) = iter.next() {
711 if i == 0 {
712 self.handle_ws(arm.ws);
713 }
714
715 self.push_locals(|this| {
716 for (index, target) in arm.target.iter().enumerate() {
717 if index != 0 {
718 buf.write('|');
719 }
720 this.visit_target(buf, true, true, target);
721 }
722 buf.write(" => {");
723
724 arm_size = this.handle(ctx, &arm.nodes, buf, AstLevel::Nested)?;
725
726 if let Some((_, arm)) = iter.peek() {
727 this.handle_ws(arm.ws);
728 arm_sizes.push(arm_size + this.write_buf_writable(ctx, buf)?);
729
730 buf.write('}');
731 } else {
732 this.handle_ws(ws2);
733 arm_sizes.push(arm_size + this.write_buf_writable(ctx, buf)?);
734 buf.write('}');
735 }
736 Ok(0)
737 })?;
738 }
739
740 buf.write('}');
741
742 Ok(flushed + median(&mut arm_sizes))
743 }
744
745 #[allow(clippy::too_many_arguments)]
746 fn write_loop(
747 &mut self,
748 ctx: &Context<'a>,
749 buf: &mut Buffer,
750 loop_block: &'a WithSpan<'_, Loop<'_>>,
751 ) -> Result<usize, CompileError> {
752 self.handle_ws(loop_block.ws1);
753 self.push_locals(|this| {
754 let expr_code = this.visit_expr_root(ctx, &loop_block.iter)?;
755
756 let has_else_nodes = !loop_block.else_nodes.is_empty();
757
758 let flushed = this.write_buf_writable(ctx, buf)?;
759 buf.write('{');
760 if has_else_nodes {
761 buf.write("let mut _did_loop = false;");
762 }
763 match &*loop_block.iter {
764 Expr::Range(_, _, _) => buf.write(format_args!("let _iter = {expr_code};")),
765 Expr::Array(..) => buf.write(format_args!("let _iter = {expr_code}.iter();")),
766 // If `iter` is a call then we assume it's something that returns
767 // an iterator. If not then the user can explicitly add the needed
768 // call without issues.
769 Expr::Call(..) | Expr::Index(..) => {
770 buf.write(format_args!("let _iter = ({expr_code}).into_iter();"));
771 }
772 // If accessing `self` then it most likely needs to be
773 // borrowed, to prevent an attempt of moving.
774 _ if expr_code.starts_with("self.") => {
775 buf.write(format_args!("let _iter = (&{expr_code}).into_iter();"));
776 }
777 // If accessing a field then it most likely needs to be
778 // borrowed, to prevent an attempt of moving.
779 Expr::Attr(..) => {
780 buf.write(format_args!("let _iter = (&{expr_code}).into_iter();"));
781 }
782 // Otherwise, we borrow `iter` assuming that it implements `IntoIterator`.
783 _ => buf.write(format_args!("let _iter = ({expr_code}).into_iter();")),
784 }
785 if let Some(cond) = &loop_block.cond {
786 this.push_locals(|this| {
787 buf.write("let _iter = _iter.filter(|");
788 this.visit_target(buf, true, true, &loop_block.var);
789 buf.write("| -> bool {");
790 this.visit_expr(ctx, buf, cond)?;
791 buf.write("});");
792 Ok(0)
793 })?;
794 }
795
796 let size_hint1 = this.push_locals(|this| {
797 buf.write("for (");
798 this.visit_target(buf, true, true, &loop_block.var);
799 buf.write(", _loop_item) in rinja::helpers::TemplateLoop::new(_iter) {");
800
801 if has_else_nodes {
802 buf.write("_did_loop = true;");
803 }
804 let mut size_hint1 = this.handle(ctx, &loop_block.body, buf, AstLevel::Nested)?;
805 this.handle_ws(loop_block.ws2);
806 size_hint1 += this.write_buf_writable(ctx, buf)?;
807 Ok(size_hint1)
808 })?;
809 buf.write('}');
810
811 let size_hint2;
812 if has_else_nodes {
813 buf.write("if !_did_loop {");
814 size_hint2 = this.push_locals(|this| {
815 let mut size_hint =
816 this.handle(ctx, &loop_block.else_nodes, buf, AstLevel::Nested)?;
817 this.handle_ws(loop_block.ws3);
818 size_hint += this.write_buf_writable(ctx, buf)?;
819 Ok(size_hint)
820 })?;
821 buf.write('}');
822 } else {
823 this.handle_ws(loop_block.ws3);
824 size_hint2 = this.write_buf_writable(ctx, buf)?;
825 }
826
827 buf.write('}');
828 Ok(flushed + ((size_hint1 * 3) + size_hint2) / 2)
829 })
830 }
831
832 fn write_call(
833 &mut self,
834 ctx: &Context<'a>,
835 buf: &mut Buffer,
836 call: &'a WithSpan<'_, Call<'_>>,
837 ) -> Result<usize, CompileError> {
838 let Call {
839 ws,
840 scope,
841 name,
842 ref args,
843 } = **call;
844 if name == "super" {
845 return self.write_block(ctx, buf, None, ws, call);
846 }
847
848 let (def, own_ctx) = if let Some(s) = scope {
849 let path = ctx.imports.get(s).ok_or_else(|| {
850 ctx.generate_error(&format!("no import found for scope {s:?}"), call)
851 })?;
852 let mctx = self.contexts.get(path).ok_or_else(|| {
853 ctx.generate_error(&format!("context for {path:?} not found"), call)
854 })?;
855 let def = mctx.macros.get(name).ok_or_else(|| {
856 ctx.generate_error(&format!("macro {name:?} not found in scope {s:?}"), call)
857 })?;
858 (def, mctx)
859 } else {
860 let def = ctx
861 .macros
862 .get(name)
863 .ok_or_else(|| ctx.generate_error(&format!("macro {name:?} not found"), call))?;
864 (def, ctx)
865 };
866
867 self.flush_ws(ws); // Cannot handle_ws() here: whitespace from macro definition comes first
868 let size_hint = self.push_locals(|this| {
869 this.write_buf_writable(ctx, buf)?;
870 buf.write('{');
871 this.prepare_ws(def.ws1);
872
873 let mut names = Buffer::new();
874 let mut values = Buffer::new();
875 let mut is_first_variable = true;
876 if args.len() != def.args.len() {
877 return Err(ctx.generate_error(
878 &format!(
879 "macro {name:?} expected {} argument{}, found {}",
880 def.args.len(),
881 if def.args.len() != 1 { "s" } else { "" },
882 args.len()
883 ),
884 call,
885 ));
886 }
887 let mut named_arguments = HashMap::new();
888 // Since named arguments can only be passed last, we only need to check if the last argument
889 // is a named one.
890 if let Some(Expr::NamedArgument(_, _)) = args.last().map(|expr| &**expr) {
891 // First we check that all named arguments actually exist in the called item.
892 for arg in args.iter().rev() {
893 let Expr::NamedArgument(arg_name, _) = &**arg else {
894 break;
895 };
896 if !def.args.iter().any(|arg| arg == arg_name) {
897 return Err(ctx.generate_error(
898 &format!("no argument named `{arg_name}` in macro {name:?}"),
899 call,
900 ));
901 }
902 named_arguments.insert(Cow::Borrowed(arg_name), arg);
903 }
904 }
905
906 // Handling both named and unnamed arguments requires to be careful of the named arguments
907 // order. To do so, we iterate through the macro defined arguments and then check if we have
908 // a named argument with this name:
909 //
910 // * If there is one, we add it and move to the next argument.
911 // * If there isn't one, then we pick the next argument (we can do it without checking
912 // anything since named arguments are always last).
913 let mut allow_positional = true;
914 for (index, arg) in def.args.iter().enumerate() {
915 let expr = if let Some(expr) = named_arguments.get(&Cow::Borrowed(arg)) {
916 allow_positional = false;
917 expr
918 } else {
919 if !allow_positional {
920 // If there is already at least one named argument, then it's not allowed
921 // to use unnamed ones at this point anymore.
922 return Err(ctx.generate_error(
923 &format!(
924 "cannot have unnamed argument (`{arg}`) after named argument in macro \
925 {name:?}"
926 ),
927 call,
928 ));
929 }
930 &args[index]
931 };
932 match &**expr {
933 // If `expr` is already a form of variable then
934 // don't reintroduce a new variable. This is
935 // to avoid moving non-copyable values.
936 Expr::Var(name) if *name != "self" => {
937 let var = this.locals.resolve_or_self(name);
938 this.locals
939 .insert(Cow::Borrowed(arg), LocalMeta::with_ref(var));
940 }
941 Expr::Attr(obj, attr) => {
942 let mut attr_buf = Buffer::new();
943 this.visit_attr(ctx, &mut attr_buf, obj, attr)?;
944
945 let var = this.locals.resolve(&attr_buf.buf).unwrap_or(attr_buf.buf);
946 this.locals
947 .insert(Cow::Borrowed(arg), LocalMeta::with_ref(var));
948 }
949 // Everything else still needs to become variables,
950 // to avoid having the same logic be executed
951 // multiple times, e.g. in the case of macro
952 // parameters being used multiple times.
953 _ => {
954 if is_first_variable {
955 is_first_variable = false;
956 } else {
957 names.write(',');
958 values.write(',');
959 }
960 names.write(arg);
961
962 values.write('(');
963 if !is_copyable(expr) {
964 values.write('&');
965 }
966 values.write(this.visit_expr_root(ctx, expr)?);
967 values.write(')');
968 this.locals.insert_with_default(Cow::Borrowed(arg));
969 }
970 }
971 }
972
973 debug_assert_eq!(names.buf.is_empty(), values.buf.is_empty());
974 if !names.buf.is_empty() {
975 buf.write(format_args!("let ({}) = ({});", names.buf, values.buf));
976 }
977
978 let mut size_hint = this.handle(own_ctx, &def.nodes, buf, AstLevel::Nested)?;
979
980 this.flush_ws(def.ws2);
981 size_hint += this.write_buf_writable(ctx, buf)?;
982 buf.write('}');
983 Ok(size_hint)
984 })?;
985 self.prepare_ws(ws);
986 Ok(size_hint)
987 }
988
989 fn write_filter_block(
990 &mut self,
991 ctx: &Context<'a>,
992 buf: &mut Buffer,
993 filter: &'a WithSpan<'_, FilterBlock<'_>>,
994 ) -> Result<usize, CompileError> {
995 self.write_buf_writable(ctx, buf)?;
996 self.flush_ws(filter.ws1);
997 self.is_in_filter_block += 1;
998 self.write_buf_writable(ctx, buf)?;
999 buf.write('{');
1000
1001 // build `FmtCell` that contains the inner block
1002 buf.write(format_args!(
1003 "let {FILTER_SOURCE} = rinja::helpers::FmtCell::new(\
1004 |writer: &mut rinja::helpers::core::fmt::Formatter<'_>| -> rinja::Result<()> {{"
1005 ));
1006 let size_hint = self.push_locals(|this| {
1007 this.prepare_ws(filter.ws1);
1008 let size_hint = this.handle(ctx, &filter.nodes, buf, AstLevel::Nested)?;
1009 this.flush_ws(filter.ws2);
1010 this.write_buf_writable(ctx, buf)?;
1011 Ok(size_hint)
1012 })?;
1013 buf.write(
1014 "\
1015 rinja::Result::Ok(())\
1016 });",
1017 );
1018
1019 // display the `FmtCell`
1020 let mut filter_buf = Buffer::new();
1021 let display_wrap = self.visit_filter(
1022 ctx,
1023 &mut filter_buf,
1024 filter.filters.name,
1025 &filter.filters.arguments,
1026 filter,
1027 )?;
1028 let filter_buf = match display_wrap {
1029 DisplayWrap::Wrapped => filter_buf.buf,
1030 DisplayWrap::Unwrapped => format!(
1031 "(&&rinja::filters::AutoEscaper::new(&({}), {})).rinja_auto_escape()?",
1032 filter_buf.buf, self.input.escaper,
1033 ),
1034 };
1035 buf.write(format_args!(
1036 "if rinja::helpers::core::write!(writer, \"{{}}\", {filter_buf}).is_err() {{\
1037 return {FILTER_SOURCE}.take_err();\
1038 }}"
1039 ));
1040
1041 buf.write('}');
1042 self.is_in_filter_block -= 1;
1043 self.prepare_ws(filter.ws2);
1044 Ok(size_hint)
1045 }
1046
1047 fn handle_include(
1048 &mut self,
1049 ctx: &Context<'a>,
1050 buf: &mut Buffer,
1051 i: &'a WithSpan<'_, Include<'_>>,
1052 ) -> Result<usize, CompileError> {
1053 self.flush_ws(i.ws);
1054 self.write_buf_writable(ctx, buf)?;
1055 let file_info = ctx.path.map(|path| FileInfo::of(i, path, ctx.parsed));
1056 let path = self
1057 .input
1058 .config
1059 .find_template(i.path, Some(&self.input.path), file_info)?;
1060
1061 // We clone the context of the child in order to preserve their macros and imports.
1062 // But also add all the imports and macros from this template that don't override the
1063 // child's ones to preserve this template's context.
1064 let child_ctx = &mut self.contexts[&path].clone();
1065 for (name, mac) in &ctx.macros {
1066 child_ctx.macros.entry(name).or_insert(mac);
1067 }
1068 for (name, import) in &ctx.imports {
1069 child_ctx
1070 .imports
1071 .entry(name)
1072 .or_insert_with(|| import.clone());
1073 }
1074
1075 // Create a new generator for the child, and call it like in `impl_template` as if it were
1076 // a full template, while preserving the context.
1077 let heritage = if !child_ctx.blocks.is_empty() || child_ctx.extends.is_some() {
1078 Some(Heritage::new(child_ctx, self.contexts))
1079 } else {
1080 None
1081 };
1082
1083 let handle_ctx = match &heritage {
1084 Some(heritage) => heritage.root,
1085 None => child_ctx,
1086 };
1087 let locals = MapChain::with_parent(&self.locals);
1088 let mut child = Self::new(
1089 self.input,
1090 self.contexts,
1091 heritage.as_ref(),
1092 locals,
1093 self.buf_writable.discard,
1094 self.is_in_filter_block,
1095 );
1096 let mut size_hint = child.handle(handle_ctx, handle_ctx.nodes, buf, AstLevel::Top)?;
1097 size_hint += child.write_buf_writable(handle_ctx, buf)?;
1098 self.prepare_ws(i.ws);
1099
1100 Ok(size_hint)
1101 }
1102
1103 fn is_shadowing_variable<T>(
1104 &self,
1105 ctx: &Context<'_>,
1106 var: &Target<'a>,
1107 l: &WithSpan<'_, T>,
1108 ) -> Result<bool, CompileError> {
1109 match var {
1110 Target::Name(name) => {
1111 let name = normalize_identifier(name);
1112 match self.locals.get(&Cow::Borrowed(name)) {
1113 // declares a new variable
1114 None => Ok(false),
1115 // an initialized variable gets shadowed
1116 Some(meta) if meta.initialized => Ok(true),
1117 // initializes a variable that was introduced in a LetDecl before
1118 _ => Ok(false),
1119 }
1120 }
1121 Target::Tuple(_, targets) => {
1122 for target in targets {
1123 match self.is_shadowing_variable(ctx, target, l) {
1124 Ok(false) => continue,
1125 outcome => return outcome,
1126 }
1127 }
1128 Ok(false)
1129 }
1130 Target::Struct(_, named_targets) => {
1131 for (_, target) in named_targets {
1132 match self.is_shadowing_variable(ctx, target, l) {
1133 Ok(false) => continue,
1134 outcome => return outcome,
1135 }
1136 }
1137 Ok(false)
1138 }
1139 _ => Err(ctx.generate_error(
1140 "literals are not allowed on the left-hand side of an assignment",
1141 l,
1142 )),
1143 }
1144 }
1145
1146 fn write_let(
1147 &mut self,
1148 ctx: &Context<'_>,
1149 buf: &mut Buffer,
1150 l: &'a WithSpan<'_, Let<'_>>,
1151 ) -> Result<(), CompileError> {
1152 self.handle_ws(l.ws);
1153
1154 let Some(val) = &l.val else {
1155 self.write_buf_writable(ctx, buf)?;
1156 buf.write("let ");
1157 self.visit_target(buf, false, true, &l.var);
1158 buf.write(';');
1159 return Ok(());
1160 };
1161
1162 let mut expr_buf = Buffer::new();
1163 self.visit_expr(ctx, &mut expr_buf, val)?;
1164
1165 let shadowed = self.is_shadowing_variable(ctx, &l.var, l)?;
1166 if shadowed {
1167 // Need to flush the buffer if the variable is being shadowed,
1168 // to ensure the old variable is used.
1169 self.write_buf_writable(ctx, buf)?;
1170 }
1171 if shadowed
1172 || !matches!(l.var, Target::Name(_))
1173 || matches!(&l.var, Target::Name(name) if self.locals.get(&Cow::Borrowed(name)).is_none())
1174 {
1175 buf.write("let ");
1176 }
1177
1178 self.visit_target(buf, true, true, &l.var);
1179 let (before, after) = if !is_copyable(val) {
1180 ("&(", ")")
1181 } else {
1182 ("", "")
1183 };
1184 buf.write(format_args!(" = {before}{}{after};", &expr_buf.buf));
1185 Ok(())
1186 }
1187
1188 // If `name` is `Some`, this is a call to a block definition, and we have to find
1189 // the first block for that name from the ancestry chain. If name is `None`, this
1190 // is from a `super()` call, and we can get the name from `self.super_block`.
1191 fn write_block<T>(
1192 &mut self,
1193 ctx: &Context<'a>,
1194 buf: &mut Buffer,
1195 name: Option<&'a str>,
1196 outer: Ws,
1197 node: &WithSpan<'_, T>,
1198 ) -> Result<usize, CompileError> {
1199 if self.is_in_filter_block > 0 {
1200 return Err(ctx.generate_error("cannot have a block inside a filter block", node));
1201 }
1202 // Flush preceding whitespace according to the outer WS spec
1203 self.flush_ws(outer);
1204
1205 let cur = match (name, self.super_block) {
1206 // The top-level context contains a block definition
1207 (Some(cur_name), None) => (cur_name, 0),
1208 // A block definition contains a block definition of the same name
1209 (Some(cur_name), Some((prev_name, _))) if cur_name == prev_name => {
1210 return Err(ctx.generate_error(
1211 &format!("cannot define recursive blocks ({cur_name})"),
1212 node,
1213 ));
1214 }
1215 // A block definition contains a definition of another block
1216 (Some(cur_name), Some((_, _))) => (cur_name, 0),
1217 // `super()` was called inside a block
1218 (None, Some((prev_name, gen))) => (prev_name, gen + 1),
1219 // `super()` is called from outside a block
1220 (None, None) => {
1221 return Err(ctx.generate_error("cannot call 'super()' outside block", node));
1222 }
1223 };
1224
1225 self.write_buf_writable(ctx, buf)?;
1226
1227 let block_fragment_write = self.input.block == name && self.buf_writable.discard;
1228 // Allow writing to the buffer if we're in the block fragment
1229 if block_fragment_write {
1230 self.buf_writable.discard = false;
1231 }
1232 let prev_buf_discard = buf.is_discard();
1233 buf.set_discard(self.buf_writable.discard);
1234
1235 // Get the block definition from the heritage chain
1236 let heritage = self
1237 .heritage
1238 .ok_or_else(|| ctx.generate_error("no block ancestors available", node))?;
1239 let (child_ctx, def) = *heritage.blocks[cur.0].get(cur.1).ok_or_else(|| {
1240 ctx.generate_error(
1241 &match name {
1242 None => format!("no super() block found for block '{}'", cur.0),
1243 Some(name) => format!("no block found for name '{name}'"),
1244 },
1245 node,
1246 )
1247 })?;
1248
1249 // We clone the context of the child in order to preserve their macros and imports.
1250 // But also add all the imports and macros from this template that don't override the
1251 // child's ones to preserve this template's context.
1252 let mut child_ctx = child_ctx.clone();
1253 for (name, mac) in &ctx.macros {
1254 child_ctx.macros.entry(name).or_insert(mac);
1255 }
1256 for (name, import) in &ctx.imports {
1257 child_ctx
1258 .imports
1259 .entry(name)
1260 .or_insert_with(|| import.clone());
1261 }
1262
1263 let mut child = Self::new(
1264 self.input,
1265 self.contexts,
1266 Some(heritage),
1267 // Variables are NOT inherited from the parent scope.
1268 MapChain::default(),
1269 self.buf_writable.discard,
1270 self.is_in_filter_block,
1271 );
1272 child.buf_writable = mem::take(&mut self.buf_writable);
1273
1274 // Handle inner whitespace suppression spec and process block nodes
1275 child.prepare_ws(def.ws1);
1276
1277 child.super_block = Some(cur);
1278 let size_hint = child.handle(&child_ctx, &def.nodes, buf, AstLevel::Block)?;
1279
1280 if !child.locals.is_current_empty() {
1281 // Need to flush the buffer before popping the variable stack
1282 child.write_buf_writable(ctx, buf)?;
1283 }
1284
1285 child.flush_ws(def.ws2);
1286 self.buf_writable = child.buf_writable;
1287
1288 // Restore original block context and set whitespace suppression for
1289 // succeeding whitespace according to the outer WS spec
1290 self.prepare_ws(outer);
1291
1292 // If we are rendering a specific block and the discard changed, it means that we're done
1293 // with the block we want to render and that from this point, everything will be discarded.
1294 //
1295 // To get this block content rendered as well, we need to write to the buffer before then.
1296 if buf.is_discard() != prev_buf_discard {
1297 self.write_buf_writable(ctx, buf)?;
1298 }
1299 // Restore the original buffer discarding state
1300 if block_fragment_write {
1301 self.buf_writable.discard = true;
1302 }
1303 buf.set_discard(prev_buf_discard);
1304
1305 Ok(size_hint)
1306 }
1307
1308 fn write_expr(&mut self, ws: Ws, s: &'a WithSpan<'a, Expr<'a>>) {
1309 self.handle_ws(ws);
1310 self.buf_writable
1311 .push(compile_time_escape(s, self.input.escaper).unwrap_or(Writable::Expr(s)));
1312 }
1313
1314 // Write expression buffer and empty
1315 fn write_buf_writable(
1316 &mut self,
1317 ctx: &Context<'_>,
1318 buf: &mut Buffer,
1319 ) -> Result<usize, CompileError> {
1320 let mut size_hint = 0;
1321 let items = mem::take(&mut self.buf_writable.buf);
1322 let mut it = items.iter().enumerate().peekable();
1323
1324 while let Some((_, Writable::Lit(s))) = it.peek() {
1325 size_hint += buf.write_writer(s);
1326 it.next();
1327 }
1328 if it.peek().is_none() {
1329 return Ok(size_hint);
1330 }
1331
1332 let mut targets = Buffer::new();
1333 let mut lines = Buffer::new();
1334 let mut expr_cache = HashMap::with_capacity(self.buf_writable.len());
1335 // the `last_line` contains any sequence of trailing simple `writer.write_str()` calls
1336 let mut trailing_simple_lines = Vec::new();
1337
1338 buf.write("match (");
1339 while let Some((idx, s)) = it.next() {
1340 match s {
1341 Writable::Lit(s) => {
1342 let mut items = vec![s];
1343 while let Some((_, Writable::Lit(s))) = it.peek() {
1344 items.push(s);
1345 it.next();
1346 }
1347 if it.peek().is_some() {
1348 for s in items {
1349 size_hint += lines.write_writer(s);
1350 }
1351 } else {
1352 trailing_simple_lines = items;
1353 break;
1354 }
1355 }
1356 Writable::Expr(s) => {
1357 size_hint += 3;
1358
1359 let mut expr_buf = Buffer::new();
1360 let expr = match self.visit_expr(ctx, &mut expr_buf, s)? {
1361 DisplayWrap::Wrapped => expr_buf.buf,
1362 DisplayWrap::Unwrapped => format!(
1363 "(&&rinja::filters::AutoEscaper::new(&({}), {})).\
1364 rinja_auto_escape()?",
1365 expr_buf.buf, self.input.escaper,
1366 ),
1367 };
1368 let idx = if is_cacheable(s) {
1369 match expr_cache.entry(expr) {
1370 Entry::Occupied(e) => *e.get(),
1371 Entry::Vacant(e) => {
1372 buf.write(format_args!("&({}),", e.key()));
1373 targets.write(format_args!("expr{idx},"));
1374 e.insert(idx);
1375 idx
1376 }
1377 }
1378 } else {
1379 buf.write(format_args!("&({expr}),"));
1380 targets.write(format_args!("expr{idx}, "));
1381 idx
1382 };
1383 lines.write(format_args!(
1384 "(&&rinja::filters::Writable(expr{idx})).rinja_write(writer)?;",
1385 ));
1386 }
1387 }
1388 }
1389 buf.write(format_args!(
1390 ") {{\
1391 ({}) => {{\
1392 {}\
1393 }}\
1394 }}",
1395 targets.buf, lines.buf,
1396 ));
1397
1398 for s in trailing_simple_lines {
1399 size_hint += buf.write_writer(s);
1400 }
1401
1402 Ok(size_hint)
1403 }
1404
1405 fn visit_lit(&mut self, lit: &'a Lit<'_>) {
1406 assert!(self.next_ws.is_none());
1407 let Lit { lws, val, rws } = *lit;
1408 if !lws.is_empty() {
1409 match self.skip_ws {
1410 WhitespaceHandling::Suppress => {}
1411 _ if val.is_empty() => {
1412 assert!(rws.is_empty());
1413 self.next_ws = Some(lws);
1414 }
1415 WhitespaceHandling::Preserve => {
1416 self.buf_writable.push(Writable::Lit(Cow::Borrowed(lws)));
1417 }
1418 WhitespaceHandling::Minimize => {
1419 self.buf_writable.push(Writable::Lit(Cow::Borrowed(
1420 match lws.contains('\n') {
1421 true => "\n",
1422 false => " ",
1423 },
1424 )));
1425 }
1426 }
1427 }
1428
1429 if !val.is_empty() {
1430 self.skip_ws = WhitespaceHandling::Preserve;
1431 self.buf_writable.push(Writable::Lit(Cow::Borrowed(val)));
1432 }
1433
1434 if !rws.is_empty() {
1435 self.next_ws = Some(rws);
1436 }
1437 }
1438
1439 fn write_comment(&mut self, comment: &'a WithSpan<'_, Comment<'_>>) {
1440 self.handle_ws(comment.ws);
1441 }
1442
1443 // Visitor methods for expression types
1444
1445 fn visit_expr_root(
1446 &mut self,
1447 ctx: &Context<'_>,
1448 expr: &WithSpan<'_, Expr<'_>>,
1449 ) -> Result<String, CompileError> {
1450 let mut buf = Buffer::new();
1451 self.visit_expr(ctx, &mut buf, expr)?;
1452 Ok(buf.buf)
1453 }
1454
1455 fn visit_expr(
1456 &mut self,
1457 ctx: &Context<'_>,
1458 buf: &mut Buffer,
1459 expr: &WithSpan<'_, Expr<'_>>,
1460 ) -> Result<DisplayWrap, CompileError> {
1461 Ok(match **expr {
1462 Expr::BoolLit(s) => self.visit_bool_lit(buf, s),
1463 Expr::NumLit(s, _) => self.visit_num_lit(buf, s),
1464 Expr::StrLit(ref s) => self.visit_str_lit(buf, s),
1465 Expr::CharLit(ref s) => self.visit_char_lit(buf, s),
1466 Expr::Var(s) => self.visit_var(buf, s),
1467 Expr::Path(ref path) => self.visit_path(buf, path),
1468 Expr::Array(ref elements) => self.visit_array(ctx, buf, elements)?,
1469 Expr::Attr(ref obj, name) => self.visit_attr(ctx, buf, obj, name)?,
1470 Expr::Index(ref obj, ref key) => self.visit_index(ctx, buf, obj, key)?,
1471 Expr::Filter(Filter {
1472 name,
1473 ref arguments,
1474 }) => self.visit_filter(ctx, buf, name, arguments, expr)?,
1475 Expr::Unary(op, ref inner) => self.visit_unary(ctx, buf, op, inner)?,
1476 Expr::BinOp(op, ref left, ref right) => self.visit_binop(ctx, buf, op, left, right)?,
1477 Expr::Range(op, ref left, ref right) => {
1478 self.visit_range(ctx, buf, op, left.as_deref(), right.as_deref())?
1479 }
1480 Expr::Group(ref inner) => self.visit_group(ctx, buf, inner)?,
1481 Expr::Call(ref obj, ref args) => self.visit_call(ctx, buf, obj, args)?,
1482 Expr::RustMacro(ref path, args) => self.visit_rust_macro(buf, path, args),
1483 Expr::Try(ref expr) => self.visit_try(ctx, buf, expr)?,
1484 Expr::Tuple(ref exprs) => self.visit_tuple(ctx, buf, exprs)?,
1485 Expr::NamedArgument(_, ref expr) => self.visit_named_argument(ctx, buf, expr)?,
1486 Expr::FilterSource => self.visit_filter_source(buf),
1487 Expr::IsDefined(var_name) => self.visit_is_defined(buf, true, var_name)?,
1488 Expr::IsNotDefined(var_name) => self.visit_is_defined(buf, false, var_name)?,
1489 Expr::As(ref expr, target) => self.visit_as(ctx, buf, expr, target)?,
1490 })
1491 }
1492
1493 fn visit_condition(
1494 &mut self,
1495 ctx: &Context<'_>,
1496 buf: &mut Buffer,
1497 expr: &WithSpan<'_, Expr<'_>>,
1498 ) -> Result<(), CompileError> {
1499 match &**expr {
1500 Expr::BoolLit(_) | Expr::IsDefined(_) | Expr::IsNotDefined(_) => {
1501 self.visit_expr(ctx, buf, expr)?;
1502 }
1503 Expr::Unary("!", expr) => {
1504 buf.write('!');
1505 self.visit_condition(ctx, buf, expr)?;
1506 }
1507 Expr::BinOp(op @ ("&&" | "||"), left, right) => {
1508 self.visit_condition(ctx, buf, left)?;
1509 buf.write(format_args!(" {op} "));
1510 self.visit_condition(ctx, buf, right)?;
1511 }
1512 Expr::Group(expr) => {
1513 buf.write('(');
1514 self.visit_condition(ctx, buf, expr)?;
1515 buf.write(')');
1516 }
1517 _ => {
1518 buf.write("rinja::helpers::as_bool(&(");
1519 self.visit_expr(ctx, buf, expr)?;
1520 buf.write("))");
1521 }
1522 }
1523 Ok(())
1524 }
1525
1526 fn visit_is_defined(
1527 &mut self,
1528 buf: &mut Buffer,
1529 is_defined: bool,
1530 left: &str,
1531 ) -> Result<DisplayWrap, CompileError> {
1532 match (is_defined, self.is_var_defined(left)) {
1533 (true, true) | (false, false) => buf.write("true"),
1534 _ => buf.write("false"),
1535 }
1536 Ok(DisplayWrap::Unwrapped)
1537 }
1538
1539 fn visit_as(
1540 &mut self,
1541 ctx: &Context<'_>,
1542 buf: &mut Buffer,
1543 expr: &WithSpan<'_, Expr<'_>>,
1544 target: &str,
1545 ) -> Result<DisplayWrap, CompileError> {
1546 buf.write("rinja::helpers::get_primitive_value(&(");
1547 self.visit_expr(ctx, buf, expr)?;
1548 buf.write(format_args!(
1549 ")) as rinja::helpers::core::primitive::{target}"
1550 ));
1551 Ok(DisplayWrap::Unwrapped)
1552 }
1553
1554 fn visit_try(
1555 &mut self,
1556 ctx: &Context<'_>,
1557 buf: &mut Buffer,
1558 expr: &WithSpan<'_, Expr<'_>>,
1559 ) -> Result<DisplayWrap, CompileError> {
1560 buf.write("rinja::helpers::core::result::Result::map_err(");
1561 self.visit_expr(ctx, buf, expr)?;
1562 buf.write(", |err| rinja::shared::Error::Custom(rinja::helpers::core::convert::Into::into(err)))?");
1563 Ok(DisplayWrap::Unwrapped)
1564 }
1565
1566 fn visit_rust_macro(&mut self, buf: &mut Buffer, path: &[&str], args: &str) -> DisplayWrap {
1567 self.visit_path(buf, path);
1568 buf.write("!(");
1569 buf.write(args);
1570 buf.write(')');
1571
1572 DisplayWrap::Unwrapped
1573 }
1574
1575 fn visit_filter<T>(
1576 &mut self,
1577 ctx: &Context<'_>,
1578 buf: &mut Buffer,
1579 name: &str,
1580 args: &[WithSpan<'_, Expr<'_>>],
1581 node: &WithSpan<'_, T>,
1582 ) -> Result<DisplayWrap, CompileError> {
1583 let filter = match name {
1584 "deref" => Self::_visit_deref_filter,
1585 "escape" | "e" => Self::_visit_escape_filter,
1586 "filesizeformat" => Self::_visit_humansize,
1587 "fmt" => Self::_visit_fmt_filter,
1588 "format" => Self::_visit_format_filter,
1589 "join" => Self::_visit_join_filter,
1590 "json" | "tojson" => Self::_visit_json_filter,
1591 "linebreaks" | "linebreaksbr" | "paragraphbreaks" => Self::_visit_linebreaks_filter,
1592 "pluralize" => Self::_visit_pluralize_filter,
1593 "ref" => Self::_visit_ref_filter,
1594 "safe" => Self::_visit_safe_filter,
1595 "uppercase" | "urlencode_strict" => Self::_visit_urlencode,
1596 name if BUILT_IN_FILTERS.contains(&name) => Self::_visit_builtin_filter,
1597 _ => Self::_visit_custom_filter,
1598 };
1599 filter(self, ctx, buf, name, args, node)
1600 }
1601
1602 fn _visit_custom_filter<T>(
1603 &mut self,
1604 ctx: &Context<'_>,
1605 buf: &mut Buffer,
1606 name: &str,
1607 args: &[WithSpan<'_, Expr<'_>>],
1608 _node: &WithSpan<'_, T>,
1609 ) -> Result<DisplayWrap, CompileError> {
1610 buf.write(format_args!("filters::{name}("));
1611 self._visit_args(ctx, buf, args)?;
1612 buf.write(")?");
1613 Ok(DisplayWrap::Unwrapped)
1614 }
1615
1616 fn _visit_builtin_filter<T>(
1617 &mut self,
1618 ctx: &Context<'_>,
1619 buf: &mut Buffer,
1620 name: &str,
1621 args: &[WithSpan<'_, Expr<'_>>],
1622 _node: &WithSpan<'_, T>,
1623 ) -> Result<DisplayWrap, CompileError> {
1624 buf.write(format_args!("rinja::filters::{name}("));
1625 self._visit_args(ctx, buf, args)?;
1626 buf.write(")?");
1627 Ok(DisplayWrap::Unwrapped)
1628 }
1629
1630 fn _visit_urlencode<T>(
1631 &mut self,
1632 ctx: &Context<'_>,
1633 buf: &mut Buffer,
1634 name: &str,
1635 args: &[WithSpan<'_, Expr<'_>>],
1636 node: &WithSpan<'_, T>,
1637 ) -> Result<DisplayWrap, CompileError> {
1638 if cfg!(not(feature = "urlencode")) {
1639 return Err(ctx.generate_error(
1640 &format!("the `{name}` filter requires the `urlencode` feature to be enabled"),
1641 node,
1642 ));
1643 }
1644
1645 // Both filters return HTML-safe strings.
1646 buf.write(format_args!(
1647 "rinja::filters::HtmlSafeOutput(rinja::filters::{name}(",
1648 ));
1649 self._visit_args(ctx, buf, args)?;
1650 buf.write(")?)");
1651 Ok(DisplayWrap::Unwrapped)
1652 }
1653
1654 fn _visit_humansize<T>(
1655 &mut self,
1656 ctx: &Context<'_>,
1657 buf: &mut Buffer,
1658 name: &str,
1659 args: &[WithSpan<'_, Expr<'_>>],
1660 node: &WithSpan<'_, T>,
1661 ) -> Result<DisplayWrap, CompileError> {
1662 if cfg!(not(feature = "humansize")) {
1663 return Err(ctx.generate_error(
1664 &format!("the `{name}` filter requires the `humansize` feature to be enabled"),
1665 node,
1666 ));
1667 }
1668
1669 // All filters return numbers, and any default formatted number is HTML safe.
1670 buf.write(format_args!(
1671 "rinja::filters::HtmlSafeOutput(rinja::filters::{name}(",
1672 ));
1673 self._visit_args(ctx, buf, args)?;
1674 buf.write(")?)");
1675 Ok(DisplayWrap::Unwrapped)
1676 }
1677
1678 fn _visit_pluralize_filter<T>(
1679 &mut self,
1680 ctx: &Context<'_>,
1681 buf: &mut Buffer,
1682 _name: &str,
1683 args: &[WithSpan<'_, Expr<'_>>],
1684 node: &WithSpan<'_, T>,
1685 ) -> Result<DisplayWrap, CompileError> {
1686 const SINGULAR: &WithSpan<'static, Expr<'static>> = &WithSpan::new(
1687 Expr::StrLit(StrLit {
1688 prefix: None,
1689 content: "",
1690 }),
1691 "",
1692 );
1693 const PLURAL: &WithSpan<'static, Expr<'static>> = &WithSpan::new(
1694 Expr::StrLit(StrLit {
1695 prefix: None,
1696 content: "s",
1697 }),
1698 "",
1699 );
1700
1701 let (count, sg, pl) = match args {
1702 [count] => (count, SINGULAR, PLURAL),
1703 [count, sg] => (count, sg, PLURAL),
1704 [count, sg, pl] => (count, sg, pl),
1705 _ => {
1706 return Err(
1707 ctx.generate_error("unexpected argument(s) in `pluralize` filter", node)
1708 );
1709 }
1710 };
1711 if let Some(is_singular) = expr_is_int_lit_plus_minus_one(count) {
1712 let value = if is_singular { sg } else { pl };
1713 self._visit_auto_escaped_arg(ctx, buf, value)?;
1714 } else {
1715 buf.write("rinja::filters::pluralize(");
1716 self._visit_arg(ctx, buf, count)?;
1717 for value in [sg, pl] {
1718 buf.write(',');
1719 self._visit_auto_escaped_arg(ctx, buf, value)?;
1720 }
1721 buf.write(")?");
1722 }
1723 Ok(DisplayWrap::Wrapped)
1724 }
1725
1726 fn _visit_linebreaks_filter<T>(
1727 &mut self,
1728 ctx: &Context<'_>,
1729 buf: &mut Buffer,
1730 name: &str,
1731 args: &[WithSpan<'_, Expr<'_>>],
1732 node: &WithSpan<'_, T>,
1733 ) -> Result<DisplayWrap, CompileError> {
1734 if args.len() != 1 {
1735 return Err(
1736 ctx.generate_error(&format!("unexpected argument(s) in `{name}` filter"), node)
1737 );
1738 }
1739 buf.write(format_args!(
1740 "rinja::filters::{name}(&(&&rinja::filters::AutoEscaper::new(&(",
1741 ));
1742 self._visit_args(ctx, buf, args)?;
1743 // The input is always HTML escaped, regardless of the selected escaper:
1744 buf.write("), rinja::filters::Html)).rinja_auto_escape()?)?");
1745 // The output is marked as HTML safe, not safe in all contexts:
1746 Ok(DisplayWrap::Unwrapped)
1747 }
1748
1749 fn _visit_ref_filter<T>(
1750 &mut self,
1751 ctx: &Context<'_>,
1752 buf: &mut Buffer,
1753 _name: &str,
1754 args: &[WithSpan<'_, Expr<'_>>],
1755 node: &WithSpan<'_, T>,
1756 ) -> Result<DisplayWrap, CompileError> {
1757 let arg = match args {
1758 [arg] => arg,
1759 _ => return Err(ctx.generate_error("unexpected argument(s) in `as_ref` filter", node)),
1760 };
1761 buf.write('&');
1762 self.visit_expr(ctx, buf, arg)?;
1763 Ok(DisplayWrap::Unwrapped)
1764 }
1765
1766 fn _visit_deref_filter<T>(
1767 &mut self,
1768 ctx: &Context<'_>,
1769 buf: &mut Buffer,
1770 _name: &str,
1771 args: &[WithSpan<'_, Expr<'_>>],
1772 node: &WithSpan<'_, T>,
1773 ) -> Result<DisplayWrap, CompileError> {
1774 let arg = match args {
1775 [arg] => arg,
1776 _ => return Err(ctx.generate_error("unexpected argument(s) in `deref` filter", node)),
1777 };
1778 buf.write('*');
1779 self.visit_expr(ctx, buf, arg)?;
1780 Ok(DisplayWrap::Unwrapped)
1781 }
1782
1783 fn _visit_json_filter<T>(
1784 &mut self,
1785 ctx: &Context<'_>,
1786 buf: &mut Buffer,
1787 _name: &str,
1788 args: &[WithSpan<'_, Expr<'_>>],
1789 node: &WithSpan<'_, T>,
1790 ) -> Result<DisplayWrap, CompileError> {
1791 if cfg!(not(feature = "serde_json")) {
1792 return Err(ctx.generate_error(
1793 "the `json` filter requires the `serde_json` feature to be enabled",
1794 node,
1795 ));
1796 }
1797
1798 let filter = match args.len() {
1799 1 => "json",
1800 2 => "json_pretty",
1801 _ => return Err(ctx.generate_error("unexpected argument(s) in `json` filter", node)),
1802 };
1803
1804 buf.write(format_args!("rinja::filters::{filter}("));
1805 self._visit_args(ctx, buf, args)?;
1806 buf.write(")?");
1807 Ok(DisplayWrap::Unwrapped)
1808 }
1809
1810 fn _visit_safe_filter<T>(
1811 &mut self,
1812 ctx: &Context<'_>,
1813 buf: &mut Buffer,
1814 _name: &str,
1815 args: &[WithSpan<'_, Expr<'_>>],
1816 node: &WithSpan<'_, T>,
1817 ) -> Result<DisplayWrap, CompileError> {
1818 if args.len() != 1 {
1819 return Err(ctx.generate_error("unexpected argument(s) in `safe` filter", node));
1820 }
1821 buf.write("rinja::filters::safe(");
1822 self._visit_args(ctx, buf, args)?;
1823 buf.write(format_args!(", {})?", self.input.escaper));
1824 Ok(DisplayWrap::Wrapped)
1825 }
1826
1827 fn _visit_escape_filter<T>(
1828 &mut self,
1829 ctx: &Context<'_>,
1830 buf: &mut Buffer,
1831 _name: &str,
1832 args: &[WithSpan<'_, Expr<'_>>],
1833 node: &WithSpan<'_, T>,
1834 ) -> Result<DisplayWrap, CompileError> {
1835 if args.len() > 2 {
1836 return Err(ctx.generate_error("only two arguments allowed to escape filter", node));
1837 }
1838 let opt_escaper = match args.get(1).map(|expr| &**expr) {
1839 Some(Expr::StrLit(StrLit { prefix, content })) => {
1840 if let Some(prefix) = prefix {
1841 let kind = if *prefix == StrPrefix::Binary {
1842 "slice"
1843 } else {
1844 "CStr"
1845 };
1846 return Err(ctx.generate_error(
1847 &format!(
1848 "invalid escaper `b{content:?}`. Expected a string, found a {kind}"
1849 ),
1850 &args[1],
1851 ));
1852 }
1853 Some(content)
1854 }
1855 Some(_) => {
1856 return Err(ctx.generate_error("invalid escaper type for escape filter", node));
1857 }
1858 None => None,
1859 };
1860 let escaper = match opt_escaper {
1861 Some(name) => self
1862 .input
1863 .config
1864 .escapers
1865 .iter()
1866 .find_map(|(extensions, path)| {
1867 extensions
1868 .contains(&Cow::Borrowed(name))
1869 .then_some(path.as_ref())
1870 })
1871 .ok_or_else(|| {
1872 ctx.generate_error(
1873 &format!(
1874 "invalid escaper '{name}' for `escape` filter. {}",
1875 MsgValidEscapers(&self.input.config.escapers),
1876 ),
1877 node,
1878 )
1879 })?,
1880 None => self.input.escaper,
1881 };
1882 buf.write("rinja::filters::escape(");
1883 self._visit_args(ctx, buf, &args[..1])?;
1884 buf.write(format_args!(", {escaper})?"));
1885 Ok(DisplayWrap::Wrapped)
1886 }
1887
1888 fn _visit_format_filter<T>(
1889 &mut self,
1890 ctx: &Context<'_>,
1891 buf: &mut Buffer,
1892 _name: &str,
1893 args: &[WithSpan<'_, Expr<'_>>],
1894 node: &WithSpan<'_, T>,
1895 ) -> Result<DisplayWrap, CompileError> {
1896 if !args.is_empty() {
1897 if let Expr::StrLit(ref fmt) = *args[0] {
1898 buf.write("rinja::helpers::std::format!(");
1899 self.visit_str_lit(buf, fmt);
1900 if args.len() > 1 {
1901 buf.write(',');
1902 self._visit_args(ctx, buf, &args[1..])?;
1903 }
1904 buf.write(')');
1905 return Ok(DisplayWrap::Unwrapped);
1906 }
1907 }
1908 Err(ctx.generate_error(r#"use filter format like `"a={} b={}"|format(a, b)`"#, node))
1909 }
1910
1911 fn _visit_fmt_filter<T>(
1912 &mut self,
1913 ctx: &Context<'_>,
1914 buf: &mut Buffer,
1915 _name: &str,
1916 args: &[WithSpan<'_, Expr<'_>>],
1917 node: &WithSpan<'_, T>,
1918 ) -> Result<DisplayWrap, CompileError> {
1919 if let [_, arg2] = args {
1920 if let Expr::StrLit(ref fmt) = **arg2 {
1921 buf.write("rinja::helpers::std::format!(");
1922 self.visit_str_lit(buf, fmt);
1923 buf.write(',');
1924 self._visit_args(ctx, buf, &args[..1])?;
1925 buf.write(')');
1926 return Ok(DisplayWrap::Unwrapped);
1927 }
1928 }
1929 Err(ctx.generate_error(r#"use filter fmt like `value|fmt("{:?}")`"#, node))
1930 }
1931
1932 // Force type coercion on first argument to `join` filter (see #39).
1933 fn _visit_join_filter<T>(
1934 &mut self,
1935 ctx: &Context<'_>,
1936 buf: &mut Buffer,
1937 _name: &str,
1938 args: &[WithSpan<'_, Expr<'_>>],
1939 _node: &WithSpan<'_, T>,
1940 ) -> Result<DisplayWrap, CompileError> {
1941 buf.write("rinja::filters::join((&");
1942 for (i, arg) in args.iter().enumerate() {
1943 if i > 0 {
1944 buf.write(", &");
1945 }
1946 self.visit_expr(ctx, buf, arg)?;
1947 if i == 0 {
1948 buf.write(").into_iter()");
1949 }
1950 }
1951 buf.write(")?");
1952 Ok(DisplayWrap::Unwrapped)
1953 }
1954
1955 fn _visit_args(
1956 &mut self,
1957 ctx: &Context<'_>,
1958 buf: &mut Buffer,
1959 args: &[WithSpan<'_, Expr<'_>>],
1960 ) -> Result<(), CompileError> {
1961 for (i, arg) in args.iter().enumerate() {
1962 if i > 0 {
1963 buf.write(',');
1964 }
1965 self._visit_arg(ctx, buf, arg)?;
1966 }
1967 Ok(())
1968 }
1969
1970 fn _visit_arg(
1971 &mut self,
1972 ctx: &Context<'_>,
1973 buf: &mut Buffer,
1974 arg: &WithSpan<'_, Expr<'_>>,
1975 ) -> Result<(), CompileError> {
1976 self._visit_arg_inner(ctx, buf, arg, false)
1977 }
1978
1979 fn _visit_arg_inner(
1980 &mut self,
1981 ctx: &Context<'_>,
1982 buf: &mut Buffer,
1983 arg: &WithSpan<'_, Expr<'_>>,
1984 // This parameter is needed because even though Expr::Unary is not copyable, we might still
1985 // be able to skip a few levels.
1986 need_borrow: bool,
1987 ) -> Result<(), CompileError> {
1988 if let Expr::Unary(expr @ ("*" | "&"), ref arg) = **arg {
1989 buf.write(expr);
1990 return self._visit_arg_inner(ctx, buf, arg, true);
1991 }
1992 let borrow = need_borrow || !is_copyable(arg);
1993 if borrow {
1994 buf.write("&(");
1995 }
1996 match **arg {
1997 Expr::Call(ref left, _) if !matches!(***left, Expr::Path(_)) => {
1998 buf.write('{');
1999 self.visit_expr(ctx, buf, arg)?;
2000 buf.write('}');
2001 }
2002 _ => {
2003 self.visit_expr(ctx, buf, arg)?;
2004 }
2005 }
2006 if borrow {
2007 buf.write(')');
2008 }
2009 Ok(())
2010 }
2011
2012 fn _visit_auto_escaped_arg(
2013 &mut self,
2014 ctx: &Context<'_>,
2015 buf: &mut Buffer,
2016 arg: &WithSpan<'_, Expr<'_>>,
2017 ) -> Result<(), CompileError> {
2018 if let Some(Writable::Lit(arg)) = compile_time_escape(arg, self.input.escaper) {
2019 if !arg.is_empty() {
2020 buf.write("rinja::filters::Safe(");
2021 buf.write_escaped_str(&arg);
2022 buf.write(')');
2023 } else {
2024 buf.write("rinja::helpers::Empty");
2025 }
2026 } else {
2027 buf.write("(&&rinja::filters::AutoEscaper::new(");
2028 self._visit_arg(ctx, buf, arg)?;
2029 buf.write(format_args!(
2030 ", {})).rinja_auto_escape()?",
2031 self.input.escaper
2032 ));
2033 }
2034 Ok(())
2035 }
2036
2037 fn visit_attr(
2038 &mut self,
2039 ctx: &Context<'_>,
2040 buf: &mut Buffer,
2041 obj: &WithSpan<'_, Expr<'_>>,
2042 attr: &str,
2043 ) -> Result<DisplayWrap, CompileError> {
2044 if let Expr::Var(name) = **obj {
2045 if name == "loop" {
2046 if attr == "index" {
2047 buf.write("(_loop_item.index + 1)");
2048 return Ok(DisplayWrap::Unwrapped);
2049 } else if attr == "index0" {
2050 buf.write("_loop_item.index");
2051 return Ok(DisplayWrap::Unwrapped);
2052 } else if attr == "first" {
2053 buf.write("_loop_item.first");
2054 return Ok(DisplayWrap::Unwrapped);
2055 } else if attr == "last" {
2056 buf.write("_loop_item.last");
2057 return Ok(DisplayWrap::Unwrapped);
2058 } else {
2059 return Err(ctx.generate_error("unknown loop variable", obj));
2060 }
2061 }
2062 }
2063 self.visit_expr(ctx, buf, obj)?;
2064 buf.write(format_args!(".{}", normalize_identifier(attr)));
2065 Ok(DisplayWrap::Unwrapped)
2066 }
2067
2068 fn visit_index(
2069 &mut self,
2070 ctx: &Context<'_>,
2071 buf: &mut Buffer,
2072 obj: &WithSpan<'_, Expr<'_>>,
2073 key: &WithSpan<'_, Expr<'_>>,
2074 ) -> Result<DisplayWrap, CompileError> {
2075 buf.write('&');
2076 self.visit_expr(ctx, buf, obj)?;
2077 buf.write('[');
2078 self.visit_expr(ctx, buf, key)?;
2079 buf.write(']');
2080 Ok(DisplayWrap::Unwrapped)
2081 }
2082
2083 fn visit_call(
2084 &mut self,
2085 ctx: &Context<'_>,
2086 buf: &mut Buffer,
2087 left: &WithSpan<'_, Expr<'_>>,
2088 args: &[WithSpan<'_, Expr<'_>>],
2089 ) -> Result<DisplayWrap, CompileError> {
2090 match &**left {
2091 Expr::Attr(sub_left, method) if ***sub_left == Expr::Var("loop") => match *method {
2092 "cycle" => match args {
2093 [arg] => {
2094 if matches!(**arg, Expr::Array(ref arr) if arr.is_empty()) {
2095 return Err(
2096 ctx.generate_error("loop.cycle(…) cannot use an empty array", arg)
2097 );
2098 }
2099 buf.write(
2100 "\
2101 ({\
2102 let _cycle = &(",
2103 );
2104 self.visit_expr(ctx, buf, arg)?;
2105 buf.write(
2106 "\
2107 );\
2108 let _len = _cycle.len();\
2109 if _len == 0 {\
2110 return rinja::helpers::core::result::Result::Err(rinja::Error::Fmt);\
2111 }\
2112 _cycle[_loop_item.index % _len]\
2113 })",
2114 );
2115 }
2116 _ => {
2117 return Err(
2118 ctx.generate_error("loop.cycle(…) cannot use an empty array", left)
2119 );
2120 }
2121 },
2122 s => return Err(ctx.generate_error(&format!("unknown loop method: {s:?}"), left)),
2123 },
2124 sub_left => {
2125 match sub_left {
2126 Expr::Var(name) => match self.locals.resolve(name) {
2127 Some(resolved) => buf.write(resolved),
2128 None => buf.write(format_args!("self.{}", normalize_identifier(name))),
2129 },
2130 _ => {
2131 self.visit_expr(ctx, buf, left)?;
2132 }
2133 }
2134 buf.write('(');
2135 self._visit_args(ctx, buf, args)?;
2136 buf.write(')');
2137 }
2138 }
2139 Ok(DisplayWrap::Unwrapped)
2140 }
2141
2142 fn visit_unary(
2143 &mut self,
2144 ctx: &Context<'_>,
2145 buf: &mut Buffer,
2146 op: &str,
2147 inner: &WithSpan<'_, Expr<'_>>,
2148 ) -> Result<DisplayWrap, CompileError> {
2149 buf.write(op);
2150 self.visit_expr(ctx, buf, inner)?;
2151 Ok(DisplayWrap::Unwrapped)
2152 }
2153
2154 fn visit_range(
2155 &mut self,
2156 ctx: &Context<'_>,
2157 buf: &mut Buffer,
2158 op: &str,
2159 left: Option<&WithSpan<'_, Expr<'_>>>,
2160 right: Option<&WithSpan<'_, Expr<'_>>>,
2161 ) -> Result<DisplayWrap, CompileError> {
2162 if let Some(left) = left {
2163 self.visit_expr(ctx, buf, left)?;
2164 }
2165 buf.write(op);
2166 if let Some(right) = right {
2167 self.visit_expr(ctx, buf, right)?;
2168 }
2169 Ok(DisplayWrap::Unwrapped)
2170 }
2171
2172 fn visit_binop(
2173 &mut self,
2174 ctx: &Context<'_>,
2175 buf: &mut Buffer,
2176 op: &str,
2177 left: &WithSpan<'_, Expr<'_>>,
2178 right: &WithSpan<'_, Expr<'_>>,
2179 ) -> Result<DisplayWrap, CompileError> {
2180 self.visit_expr(ctx, buf, left)?;
2181 buf.write(format_args!(" {op} "));
2182 self.visit_expr(ctx, buf, right)?;
2183 Ok(DisplayWrap::Unwrapped)
2184 }
2185
2186 fn visit_group(
2187 &mut self,
2188 ctx: &Context<'_>,
2189 buf: &mut Buffer,
2190 inner: &WithSpan<'_, Expr<'_>>,
2191 ) -> Result<DisplayWrap, CompileError> {
2192 buf.write('(');
2193 self.visit_expr(ctx, buf, inner)?;
2194 buf.write(')');
2195 Ok(DisplayWrap::Unwrapped)
2196 }
2197
2198 fn visit_tuple(
2199 &mut self,
2200 ctx: &Context<'_>,
2201 buf: &mut Buffer,
2202 exprs: &[WithSpan<'_, Expr<'_>>],
2203 ) -> Result<DisplayWrap, CompileError> {
2204 buf.write('(');
2205 for (index, expr) in exprs.iter().enumerate() {
2206 if index > 0 {
2207 buf.write(' ');
2208 }
2209 self.visit_expr(ctx, buf, expr)?;
2210 buf.write(',');
2211 }
2212 buf.write(')');
2213 Ok(DisplayWrap::Unwrapped)
2214 }
2215
2216 fn visit_named_argument(
2217 &mut self,
2218 ctx: &Context<'_>,
2219 buf: &mut Buffer,
2220 expr: &WithSpan<'_, Expr<'_>>,
2221 ) -> Result<DisplayWrap, CompileError> {
2222 self.visit_expr(ctx, buf, expr)?;
2223 Ok(DisplayWrap::Unwrapped)
2224 }
2225
2226 fn visit_array(
2227 &mut self,
2228 ctx: &Context<'_>,
2229 buf: &mut Buffer,
2230 elements: &[WithSpan<'_, Expr<'_>>],
2231 ) -> Result<DisplayWrap, CompileError> {
2232 buf.write('[');
2233 for (i, el) in elements.iter().enumerate() {
2234 if i > 0 {
2235 buf.write(',');
2236 }
2237 self.visit_expr(ctx, buf, el)?;
2238 }
2239 buf.write(']');
2240 Ok(DisplayWrap::Unwrapped)
2241 }
2242
2243 fn visit_path(&mut self, buf: &mut Buffer, path: &[&str]) -> DisplayWrap {
2244 for (i, part) in path.iter().enumerate() {
2245 if i > 0 {
2246 buf.write("::");
2247 }
2248 buf.write(part);
2249 }
2250 DisplayWrap::Unwrapped
2251 }
2252
2253 fn visit_var(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap {
2254 if s == "self" {
2255 buf.write(s);
2256 return DisplayWrap::Unwrapped;
2257 }
2258
2259 buf.write(normalize_identifier(&self.locals.resolve_or_self(s)));
2260 DisplayWrap::Unwrapped
2261 }
2262
2263 fn visit_filter_source(&mut self, buf: &mut Buffer) -> DisplayWrap {
2264 // We can assume that the body of the `{% filter %}` was already escaped.
2265 // And if it's not, then this was done intentionally.
2266 buf.write(format_args!("rinja::filters::Safe(&{FILTER_SOURCE})"));
2267 DisplayWrap::Wrapped
2268 }
2269
2270 fn visit_bool_lit(&mut self, buf: &mut Buffer, s: bool) -> DisplayWrap {
2271 if s {
2272 buf.write("true");
2273 } else {
2274 buf.write("false");
2275 }
2276 DisplayWrap::Unwrapped
2277 }
2278
2279 fn visit_str_lit(&mut self, buf: &mut Buffer, s: &StrLit<'_>) -> DisplayWrap {
2280 if let Some(prefix) = s.prefix {
2281 buf.write(prefix.to_char());
2282 }
2283 buf.write(format_args!("\"{}\"", s.content));
2284 DisplayWrap::Unwrapped
2285 }
2286
2287 fn visit_char_lit(&mut self, buf: &mut Buffer, c: &CharLit<'_>) -> DisplayWrap {
2288 if c.prefix == Some(CharPrefix::Binary) {
2289 buf.write('b');
2290 }
2291 buf.write(format_args!("'{}'", c.content));
2292 DisplayWrap::Unwrapped
2293 }
2294
2295 fn visit_num_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap {
2296 buf.write(s);
2297 DisplayWrap::Unwrapped
2298 }
2299
2300 fn visit_target(
2301 &mut self,
2302 buf: &mut Buffer,
2303 initialized: bool,
2304 first_level: bool,
2305 target: &Target<'a>,
2306 ) {
2307 match target {
2308 Target::Placeholder(s) => buf.write(s),
2309 Target::Rest(s) => {
2310 if let Some(var_name) = &**s {
2311 self.locals
2312 .insert(Cow::Borrowed(var_name), LocalMeta::initialized());
2313 buf.write(var_name);
2314 buf.write(" @ ");
2315 }
2316 buf.write("..");
2317 }
2318 Target::Name(name) => {
2319 let name = normalize_identifier(name);
2320 match initialized {
2321 true => self
2322 .locals
2323 .insert(Cow::Borrowed(name), LocalMeta::initialized()),
2324 false => self.locals.insert_with_default(Cow::Borrowed(name)),
2325 }
2326 buf.write(name);
2327 }
2328 Target::OrChain(targets) => match targets.first() {
2329 None => buf.write('_'),
2330 Some(first_target) => {
2331 self.visit_target(buf, initialized, first_level, first_target);
2332 for target in &targets[1..] {
2333 buf.write('|');
2334 self.visit_target(buf, initialized, first_level, target);
2335 }
2336 }
2337 },
2338 Target::Tuple(path, targets) => {
2339 buf.write(SeparatedPath(path));
2340 buf.write('(');
2341 for target in targets {
2342 self.visit_target(buf, initialized, false, target);
2343 buf.write(',');
2344 }
2345 buf.write(')');
2346 }
2347 Target::Array(path, targets) => {
2348 buf.write(SeparatedPath(path));
2349 buf.write('[');
2350 for target in targets {
2351 self.visit_target(buf, initialized, false, target);
2352 buf.write(',');
2353 }
2354 buf.write(']');
2355 }
2356 Target::Struct(path, targets) => {
2357 buf.write(SeparatedPath(path));
2358 buf.write('{');
2359 for (name, target) in targets {
2360 if let Target::Rest(_) = target {
2361 buf.write("..");
2362 continue;
2363 }
2364
2365 buf.write(normalize_identifier(name));
2366 buf.write(": ");
2367 self.visit_target(buf, initialized, false, target);
2368 buf.write(',');
2369 }
2370 buf.write('}');
2371 }
2372 Target::Path(path) => {
2373 self.visit_path(buf, path);
2374 }
2375 Target::StrLit(s) => {
2376 if first_level {
2377 buf.write('&');
2378 }
2379 self.visit_str_lit(buf, s);
2380 }
2381 Target::NumLit(s, _) => {
2382 if first_level {
2383 buf.write('&');
2384 }
2385 self.visit_num_lit(buf, s);
2386 }
2387 Target::CharLit(s) => {
2388 if first_level {
2389 buf.write('&');
2390 }
2391 self.visit_char_lit(buf, s);
2392 }
2393 Target::BoolLit(s) => {
2394 if first_level {
2395 buf.write('&');
2396 }
2397 buf.write(s);
2398 }
2399 }
2400 }
2401
2402 // Helper methods for dealing with whitespace nodes
2403
2404 // Combines `flush_ws()` and `prepare_ws()` to handle both trailing whitespace from the
2405 // preceding literal and leading whitespace from the succeeding literal.
2406 fn handle_ws(&mut self, ws: Ws) {
2407 self.flush_ws(ws);
2408 self.prepare_ws(ws);
2409 }
2410
2411 fn should_trim_ws(&self, ws: Option<Whitespace>) -> WhitespaceHandling {
2412 match ws {
2413 Some(Whitespace::Suppress) => WhitespaceHandling::Suppress,
2414 Some(Whitespace::Preserve) => WhitespaceHandling::Preserve,
2415 Some(Whitespace::Minimize) => WhitespaceHandling::Minimize,
2416 None => self.input.config.whitespace,
2417 }
2418 }
2419
2420 // If the previous literal left some trailing whitespace in `next_ws` and the
2421 // prefix whitespace suppressor from the given argument, flush that whitespace.
2422 // In either case, `next_ws` is reset to `None` (no trailing whitespace).
2423 fn flush_ws(&mut self, ws: Ws) {
2424 if self.next_ws.is_none() {
2425 return;
2426 }
2427
2428 // If `whitespace` is set to `suppress`, we keep the whitespace characters only if there is
2429 // a `+` character.
2430 match self.should_trim_ws(ws.0) {
2431 WhitespaceHandling::Preserve => {
2432 let val = self.next_ws.unwrap();
2433 if !val.is_empty() {
2434 self.buf_writable.push(Writable::Lit(Cow::Borrowed(val)));
2435 }
2436 }
2437 WhitespaceHandling::Minimize => {
2438 let val = self.next_ws.unwrap();
2439 if !val.is_empty() {
2440 self.buf_writable.push(Writable::Lit(Cow::Borrowed(
2441 match val.contains('\n') {
2442 true => "\n",
2443 false => " ",
2444 },
2445 )));
2446 }
2447 }
2448 WhitespaceHandling::Suppress => {}
2449 }
2450 self.next_ws = None;
2451 }
2452
2453 // Sets `skip_ws` to match the suffix whitespace suppressor from the given
2454 // argument, to determine whether to suppress leading whitespace from the
2455 // next literal.
2456 fn prepare_ws(&mut self, ws: Ws) {
2457 self.skip_ws = self.should_trim_ws(ws.1);
2458 }
2459}
2460
2461#[cfg(target_pointer_width = "16")]
2462type TargetIsize = i16;
2463#[cfg(target_pointer_width = "32")]
2464type TargetIsize = i32;
2465#[cfg(target_pointer_width = "64")]
2466type TargetIsize = i64;
2467
2468#[cfg(target_pointer_width = "16")]
2469type TargetUsize = u16;
2470#[cfg(target_pointer_width = "32")]
2471type TargetUsize = u32;
2472#[cfg(target_pointer_width = "64")]
2473type TargetUsize = u64;
2474
2475#[cfg(not(any(
2476 target_pointer_width = "16",
2477 target_pointer_width = "32",
2478 target_pointer_width = "64"
2479)))]
2480const _: () = {
2481 panic!("unknown cfg!(target_pointer_width)");
2482};
2483
2484fn expr_is_int_lit_plus_minus_one(expr: &WithSpan<'_, Expr<'_>>) -> Option<bool> {
2485 fn is_signed_singular<T: Eq + Default, E>(
2486 from_str_radix: impl Fn(&str, u32) -> Result<T, E>,
2487 value: &str,
2488 plus_one: T,
2489 minus_one: T,
2490 ) -> Option<bool> {
2491 Some([plus_one, minus_one].contains(&from_str_radix(value, 10).ok()?))
2492 }
2493
2494 fn is_unsigned_singular<T: Eq + Default, E>(
2495 from_str_radix: impl Fn(&str, u32) -> Result<T, E>,
2496 value: &str,
2497 plus_one: T,
2498 ) -> Option<bool> {
2499 Some(from_str_radix(value, 10).ok()? == plus_one)
2500 }
2501
2502 macro_rules! impl_match {
2503 (
2504 $kind:ident $value:ident;
2505 $($svar:ident => $sty:ident),*;
2506 $($uvar:ident => $uty:ident),*;
2507 ) => {
2508 match $kind {
2509 $(
2510 Some(IntKind::$svar) => is_signed_singular($sty::from_str_radix, $value, 1, -1),
2511 )*
2512 $(
2513 Some(IntKind::$uvar) => is_unsigned_singular($sty::from_str_radix, $value, 1),
2514 )*
2515 None => match $value.starts_with('-') {
2516 true => is_signed_singular(i128::from_str_radix, $value, 1, -1),
2517 false => is_unsigned_singular(u128::from_str_radix, $value, 1),
2518 },
2519 }
2520 };
2521 }
2522
2523 let Expr::NumLit(_, Num::Int(value, kind)) = **expr else {
2524 return None;
2525 };
2526 impl_match! {
2527 kind value;
2528 I8 => i8,
2529 I16 => i16,
2530 I32 => i32,
2531 I64 => i64,
2532 I128 => i128,
2533 Isize => TargetIsize;
2534 U8 => u8,
2535 U16 => u16,
2536 U32 => u32,
2537 U64 => u64,
2538 U128 => u128,
2539 Usize => TargetUsize;
2540 }
2541}
2542
2543/// In here, we inspect in the expression if it is a literal, and if it is, whether it
2544/// can be escaped at compile time.
2545fn compile_time_escape<'a>(expr: &Expr<'a>, escaper: &str) -> Option<Writable<'a>> {
2546 // we only optimize for known escapers
2547 enum OutputKind {
2548 Html,
2549 Text,
2550 }
2551
2552 // we only optimize for known escapers
2553 let output = match escaper.strip_prefix("rinja::filters::")? {
2554 "Html" => OutputKind::Html,
2555 "Text" => OutputKind::Text,
2556 _ => return None,
2557 };
2558
2559 // for now, we only escape strings, chars, numbers, and bools at compile time
2560 let value = match *expr {
2561 Expr::StrLit(StrLit {
2562 prefix: None,
2563 content,
2564 }) => {
2565 if content.find('\\').is_none() {
2566 // if the literal does not contain any backslashes, then it does not need unescaping
2567 Cow::Borrowed(content)
2568 } else {
2569 // the input could be string escaped if it contains any backslashes
2570 let input = format!(r#""{content}""#);
2571 let input = input.parse().ok()?;
2572 let input = syn::parse2::<syn::LitStr>(input).ok()?;
2573 Cow::Owned(input.value())
2574 }
2575 }
2576 Expr::CharLit(CharLit {
2577 prefix: None,
2578 content,
2579 }) => {
2580 if content.find('\\').is_none() {
2581 // if the literal does not contain any backslashes, then it does not need unescaping
2582 Cow::Borrowed(content)
2583 } else {
2584 // the input could be string escaped if it contains any backslashes
2585 let input = format!(r#"'{content}'"#);
2586 let input = input.parse().ok()?;
2587 let input = syn::parse2::<syn::LitChar>(input).ok()?;
2588 Cow::Owned(input.value().to_string())
2589 }
2590 }
2591 Expr::NumLit(_, value) => {
2592 enum NumKind {
2593 Int(Option<IntKind>),
2594 Float(Option<FloatKind>),
2595 }
2596
2597 let (orig_value, kind) = match value {
2598 Num::Int(value, kind) => (value, NumKind::Int(kind)),
2599 Num::Float(value, kind) => (value, NumKind::Float(kind)),
2600 };
2601 let value = match orig_value.chars().any(|c| c == '_') {
2602 true => Cow::Owned(orig_value.chars().filter(|&c| c != '_').collect()),
2603 false => Cow::Borrowed(orig_value),
2604 };
2605
2606 fn int<T: ToString, E>(
2607 from_str_radix: impl Fn(&str, u32) -> Result<T, E>,
2608 value: &str,
2609 ) -> Option<String> {
2610 Some(from_str_radix(value, 10).ok()?.to_string())
2611 }
2612
2613 let value = match kind {
2614 NumKind::Int(Some(IntKind::I8)) => int(i8::from_str_radix, &value)?,
2615 NumKind::Int(Some(IntKind::I16)) => int(i16::from_str_radix, &value)?,
2616 NumKind::Int(Some(IntKind::I32)) => int(i32::from_str_radix, &value)?,
2617 NumKind::Int(Some(IntKind::I64)) => int(i64::from_str_radix, &value)?,
2618 NumKind::Int(Some(IntKind::I128)) => int(i128::from_str_radix, &value)?,
2619 NumKind::Int(Some(IntKind::Isize)) => int(TargetIsize::from_str_radix, &value)?,
2620 NumKind::Int(Some(IntKind::U8)) => int(u8::from_str_radix, &value)?,
2621 NumKind::Int(Some(IntKind::U16)) => int(u16::from_str_radix, &value)?,
2622 NumKind::Int(Some(IntKind::U32)) => int(u32::from_str_radix, &value)?,
2623 NumKind::Int(Some(IntKind::U64)) => int(u64::from_str_radix, &value)?,
2624 NumKind::Int(Some(IntKind::U128)) => int(u128::from_str_radix, &value)?,
2625 NumKind::Int(Some(IntKind::Usize)) => int(TargetUsize::from_str_radix, &value)?,
2626 NumKind::Int(None) => match value.starts_with('-') {
2627 true => int(i128::from_str_radix, &value)?,
2628 false => int(u128::from_str_radix, &value)?,
2629 },
2630 NumKind::Float(Some(FloatKind::F32)) => value.parse::<f32>().ok()?.to_string(),
2631 NumKind::Float(Some(FloatKind::F64) | None) => {
2632 value.parse::<f64>().ok()?.to_string()
2633 }
2634 // FIXME: implement once `f16` and `f128` are available
2635 NumKind::Float(Some(FloatKind::F16 | FloatKind::F128)) => return None,
2636 };
2637 match value == orig_value {
2638 true => Cow::Borrowed(orig_value),
2639 false => Cow::Owned(value),
2640 }
2641 }
2642 Expr::BoolLit(true) => Cow::Borrowed("true"),
2643 Expr::BoolLit(false) => Cow::Borrowed("false"),
2644 _ => return None,
2645 };
2646
2647 // escape the un-string-escaped input using the selected escaper
2648 Some(Writable::Lit(match output {
2649 OutputKind::Text => value,
2650 OutputKind::Html => {
2651 let mut escaped = String::with_capacity(value.len() + 20);
2652 write_escaped_str(&mut escaped, &value).ok()?;
2653 match escaped == value {
2654 true => value,
2655 false => Cow::Owned(escaped),
2656 }
2657 }
2658 }))
2659}
2660
2661#[derive(Debug)]
2662struct Buffer {
2663 // The buffer to generate the code into
2664 buf: String,
2665 discard: bool,
2666 last_was_write_str: bool,
2667}
2668
2669impl Buffer {
2670 fn new() -> Self {
2671 Self {
2672 buf: String::new(),
2673 discard: false,
2674 last_was_write_str: false,
2675 }
2676 }
2677
2678 fn is_discard(&self) -> bool {
2679 self.discard
2680 }
2681
2682 fn set_discard(&mut self, discard: bool) {
2683 self.discard = discard;
2684 self.last_was_write_str = false;
2685 }
2686
2687 fn write(&mut self, src: impl BufferFmt) {
2688 if !self.discard {
2689 src.append_to(&mut self.buf);
2690 self.last_was_write_str = false;
2691 }
2692 }
2693
2694 fn write_escaped_str(&mut self, s: &str) {
2695 if !self.discard {
2696 self.buf.push('"');
2697 string_escape(&mut self.buf, s);
2698 self.buf.push('"');
2699 }
2700 }
2701
2702 fn write_writer(&mut self, s: &str) -> usize {
2703 const OPEN: &str = r#"writer.write_str(""#;
2704 const CLOSE: &str = r#"")?;"#;
2705
2706 if !s.is_empty() && !self.discard {
2707 if !self.last_was_write_str {
2708 self.last_was_write_str = true;
2709 self.buf.push_str(OPEN);
2710 } else {
2711 // strip trailing `")?;`, leaving an unterminated string
2712 self.buf.truncate(self.buf.len() - CLOSE.len());
2713 }
2714 string_escape(&mut self.buf, s);
2715 self.buf.push_str(CLOSE);
2716 }
2717 s.len()
2718 }
2719}
2720
2721trait BufferFmt {
2722 fn append_to(&self, buf: &mut String);
2723}
2724
2725impl<T: BufferFmt + ?Sized> BufferFmt for &T {
2726 fn append_to(&self, buf: &mut String) {
2727 T::append_to(self, buf);
2728 }
2729}
2730
2731impl BufferFmt for char {
2732 fn append_to(&self, buf: &mut String) {
2733 buf.push(*self);
2734 }
2735}
2736
2737impl BufferFmt for str {
2738 fn append_to(&self, buf: &mut String) {
2739 buf.push_str(self);
2740 }
2741}
2742
2743impl BufferFmt for String {
2744 fn append_to(&self, buf: &mut String) {
2745 buf.push_str(self);
2746 }
2747}
2748
2749impl BufferFmt for Arguments<'_> {
2750 fn append_to(&self, buf: &mut String) {
2751 buf.write_fmt(*self).unwrap();
2752 }
2753}
2754
2755struct CondInfo<'a> {
2756 cond: &'a WithSpan<'a, Cond<'a>>,
2757 cond_expr: Option<WithSpan<'a, Expr<'a>>>,
2758 generate_condition: bool,
2759 generate_content: bool,
2760}
2761
2762struct Conds<'a> {
2763 conds: Vec<CondInfo<'a>>,
2764 ws_before: Option<Ws>,
2765 ws_after: Option<Ws>,
2766 nb_conds: usize,
2767}
2768
2769impl<'a> Conds<'a> {
2770 fn compute_branches(generator: &Generator<'a>, i: &'a If<'a>) -> Self {
2771 let mut conds = Vec::with_capacity(i.branches.len());
2772 let mut ws_before = None;
2773 let mut ws_after = None;
2774 let mut nb_conds = 0;
2775 let mut stop_loop = false;
2776
2777 for cond in &i.branches {
2778 if stop_loop {
2779 ws_after = Some(cond.ws);
2780 break;
2781 }
2782 if let Some(CondTest {
2783 expr,
2784 contains_bool_lit_or_is_defined,
2785 ..
2786 }) = &cond.cond
2787 {
2788 let mut only_contains_is_defined = true;
2789
2790 let (evaluated_result, cond_expr) = if *contains_bool_lit_or_is_defined {
2791 let (evaluated_result, expr) =
2792 generator.evaluate_condition(expr.clone(), &mut only_contains_is_defined);
2793 (evaluated_result, Some(expr))
2794 } else {
2795 (EvaluatedResult::Unknown, None)
2796 };
2797
2798 match evaluated_result {
2799 // We generate the condition in case some calls are changing a variable, but
2800 // no need to generate the condition body since it will never be called.
2801 //
2802 // However, if the condition only contains "is (not) defined" checks, then we
2803 // can completely skip it.
2804 EvaluatedResult::AlwaysFalse => {
2805 if only_contains_is_defined {
2806 if conds.is_empty() && ws_before.is_none() {
2807 // If this is the first `if` and it's skipped, we definitely don't
2808 // want its whitespace control to be lost.
2809 ws_before = Some(cond.ws);
2810 }
2811 continue;
2812 }
2813 nb_conds += 1;
2814 conds.push(CondInfo {
2815 cond,
2816 cond_expr,
2817 generate_condition: true,
2818 generate_content: false,
2819 });
2820 }
2821 // This case is more interesting: it means that we will always enter this
2822 // condition, meaning that any following should not be generated. Another
2823 // thing to take into account: if there are no if branches before this one,
2824 // no need to generate an `else`.
2825 EvaluatedResult::AlwaysTrue => {
2826 let generate_condition = !only_contains_is_defined;
2827 if generate_condition {
2828 nb_conds += 1;
2829 }
2830 conds.push(CondInfo {
2831 cond,
2832 cond_expr,
2833 generate_condition,
2834 generate_content: true,
2835 });
2836 // Since it's always true, we can stop here.
2837 stop_loop = true;
2838 }
2839 EvaluatedResult::Unknown => {
2840 nb_conds += 1;
2841 conds.push(CondInfo {
2842 cond,
2843 cond_expr,
2844 generate_condition: true,
2845 generate_content: true,
2846 });
2847 }
2848 }
2849 } else {
2850 let generate_condition = !conds.is_empty();
2851 if generate_condition {
2852 nb_conds += 1;
2853 }
2854 conds.push(CondInfo {
2855 cond,
2856 cond_expr: None,
2857 generate_condition,
2858 generate_content: true,
2859 });
2860 }
2861 }
2862 Self {
2863 conds,
2864 ws_before,
2865 ws_after,
2866 nb_conds,
2867 }
2868 }
2869}
2870
2871struct SeparatedPath<I>(I);
2872
2873impl<I: IntoIterator<Item = E> + Copy, E: BufferFmt> BufferFmt for SeparatedPath<I> {
2874 fn append_to(&self, buf: &mut String) {
2875 for (idx: usize, item: E) in self.0.into_iter().enumerate() {
2876 if idx > 0 {
2877 buf.push_str(string:"::");
2878 }
2879 item.append_to(buf);
2880 }
2881 }
2882}
2883
2884#[derive(Clone, Default)]
2885pub(crate) struct LocalMeta {
2886 refs: Option<String>,
2887 initialized: bool,
2888}
2889
2890impl LocalMeta {
2891 fn initialized() -> Self {
2892 Self {
2893 refs: None,
2894 initialized: true,
2895 }
2896 }
2897
2898 fn with_ref(refs: String) -> Self {
2899 Self {
2900 refs: Some(refs),
2901 initialized: true,
2902 }
2903 }
2904}
2905
2906#[derive(Debug, Clone)]
2907pub(crate) struct MapChain<'a, K, V>
2908where
2909 K: cmp::Eq + hash::Hash,
2910{
2911 parent: Option<&'a MapChain<'a, K, V>>,
2912 scopes: Vec<HashMap<K, V, FxBuildHasher>>,
2913}
2914
2915impl<'a, K: 'a, V: 'a> MapChain<'a, K, V>
2916where
2917 K: cmp::Eq + hash::Hash,
2918{
2919 fn with_parent<'p>(parent: &'p MapChain<'_, K, V>) -> MapChain<'p, K, V> {
2920 MapChain {
2921 parent: Some(parent),
2922 scopes: vec![HashMap::default()],
2923 }
2924 }
2925
2926 /// Iterates the scopes in reverse and returns `Some(LocalMeta)`
2927 /// from the first scope where `key` exists.
2928 fn get(&self, key: &K) -> Option<&V> {
2929 let mut scopes = self.scopes.iter().rev();
2930 scopes
2931 .find_map(|set| set.get(key))
2932 .or_else(|| self.parent.and_then(|set| set.get(key)))
2933 }
2934
2935 fn is_current_empty(&self) -> bool {
2936 self.scopes.last().unwrap().is_empty()
2937 }
2938
2939 fn insert(&mut self, key: K, val: V) {
2940 self.scopes.last_mut().unwrap().insert(key, val);
2941
2942 // Note that if `insert` returns `Some` then it implies
2943 // an identifier is reused. For e.g. `{% macro f(a, a) %}`
2944 // and `{% let (a, a) = ... %}` then this results in a
2945 // generated template, which when compiled fails with the
2946 // compile error "identifier `a` used more than once".
2947 }
2948
2949 fn insert_with_default(&mut self, key: K)
2950 where
2951 V: Default,
2952 {
2953 self.insert(key, V::default());
2954 }
2955}
2956
2957impl MapChain<'_, Cow<'_, str>, LocalMeta> {
2958 fn resolve(&self, name: &str) -> Option<String> {
2959 let name: &str = normalize_identifier(ident:name);
2960 self.get(&Cow::Borrowed(name)).map(|meta: &LocalMeta| match &meta.refs {
2961 Some(expr: &String) => expr.clone(),
2962 None => name.to_string(),
2963 })
2964 }
2965
2966 fn resolve_or_self(&self, name: &str) -> String {
2967 let name: &str = normalize_identifier(ident:name);
2968 self.resolve(name).unwrap_or_else(|| format!("self.{name}"))
2969 }
2970}
2971
2972impl<'a, K: Eq + hash::Hash, V> Default for MapChain<'a, K, V> {
2973 fn default() -> Self {
2974 Self {
2975 parent: None,
2976 scopes: vec![HashMap::default()],
2977 }
2978 }
2979}
2980
2981/// Returns `true` if enough assumptions can be made,
2982/// to determine that `self` is copyable.
2983fn is_copyable(expr: &Expr<'_>) -> bool {
2984 is_copyable_within_op(expr, within_op:false)
2985}
2986
2987fn is_copyable_within_op(expr: &Expr<'_>, within_op: bool) -> bool {
2988 match expr {
2989 Expr::BoolLit(_) | Expr::NumLit(_, _) | Expr::StrLit(_) | Expr::CharLit(_) => true,
2990 Expr::Unary(.., expr: &Box>>) => is_copyable_within_op(expr, within_op:true),
2991 Expr::BinOp(_, lhs: &Box>>, rhs: &Box>>) => {
2992 is_copyable_within_op(expr:lhs, within_op:true) && is_copyable_within_op(expr:rhs, within_op:true)
2993 }
2994 Expr::Range(..) => true,
2995 // The result of a call likely doesn't need to be borrowed,
2996 // as in that case the call is more likely to return a
2997 // reference in the first place then.
2998 Expr::Call(..) | Expr::Path(..) | Expr::Filter(..) | Expr::RustMacro(..) => true,
2999 // If the `expr` is within a `Unary` or `BinOp` then
3000 // an assumption can be made that the operand is copy.
3001 // If not, then the value is moved and adding `.clone()`
3002 // will solve that issue. However, if the operand is
3003 // implicitly borrowed, then it's likely not even possible
3004 // to get the template to compile.
3005 _ => within_op && is_attr_self(expr),
3006 }
3007}
3008
3009/// Returns `true` if this is an `Attr` where the `obj` is `"self"`.
3010pub(crate) fn is_attr_self(mut expr: &Expr<'_>) -> bool {
3011 loop {
3012 match expr {
3013 Expr::Attr(obj: &Box>>, _) if matches!(***obj, Expr::Var("self")) => return true,
3014 Expr::Attr(obj: &Box>>, _) if matches!(***obj, Expr::Attr(..)) => expr = obj,
3015 _ => return false,
3016 }
3017 }
3018}
3019
3020/// Returns `true` if the outcome of this expression may be used multiple times in the same
3021/// `write!()` call, without evaluating the expression again, i.e. the expression should be
3022/// side-effect free.
3023pub(crate) fn is_cacheable(expr: &WithSpan<'_, Expr<'_>>) -> bool {
3024 match &**expr {
3025 // Literals are the definition of pure:
3026 Expr::BoolLit(_) => true,
3027 Expr::NumLit(_, _) => true,
3028 Expr::StrLit(_) => true,
3029 Expr::CharLit(_) => true,
3030 // fmt::Display should have no effects:
3031 Expr::Var(_) => true,
3032 Expr::Path(_) => true,
3033 // Check recursively:
3034 Expr::Array(args) => args.iter().all(is_cacheable),
3035 Expr::Attr(lhs, _) => is_cacheable(lhs),
3036 Expr::Index(lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs),
3037 Expr::Filter(Filter { arguments, .. }) => arguments.iter().all(is_cacheable),
3038 Expr::Unary(_, arg) => is_cacheable(arg),
3039 Expr::BinOp(_, lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs),
3040 Expr::IsDefined(_) | Expr::IsNotDefined(_) => true,
3041 Expr::Range(_, lhs, rhs) => {
3042 lhs.as_ref().map_or(true, |v| is_cacheable(v))
3043 && rhs.as_ref().map_or(true, |v| is_cacheable(v))
3044 }
3045 Expr::Group(arg) => is_cacheable(arg),
3046 Expr::Tuple(args) => args.iter().all(is_cacheable),
3047 Expr::NamedArgument(_, expr) => is_cacheable(expr),
3048 Expr::As(expr, _) => is_cacheable(expr),
3049 Expr::Try(expr) => is_cacheable(expr),
3050 // We have too little information to tell if the expression is pure:
3051 Expr::Call(_, _) => false,
3052 Expr::RustMacro(_, _) => false,
3053 // Should never be encountered:
3054 Expr::FilterSource => unreachable!("FilterSource in expression?"),
3055 }
3056}
3057
3058const FILTER_SOURCE: &str = "__rinja_filter_block";
3059
3060fn median(sizes: &mut [usize]) -> usize {
3061 if sizes.is_empty() {
3062 return 0;
3063 }
3064 sizes.sort_unstable();
3065 if sizes.len() % 2 == 1 {
3066 sizes[sizes.len() / 2]
3067 } else {
3068 (sizes[sizes.len() / 2 - 1] + sizes[sizes.len() / 2]) / 2
3069 }
3070}
3071
3072#[derive(Clone, Copy, PartialEq)]
3073enum AstLevel {
3074 Top,
3075 Block,
3076 Nested,
3077}
3078
3079#[derive(Clone, Copy, Debug)]
3080enum DisplayWrap {
3081 Wrapped,
3082 Unwrapped,
3083}
3084
3085#[derive(Default, Debug)]
3086struct WritableBuffer<'a> {
3087 buf: Vec<Writable<'a>>,
3088 discard: bool,
3089}
3090
3091impl<'a> WritableBuffer<'a> {
3092 fn push(&mut self, writable: Writable<'a>) {
3093 if !self.discard {
3094 self.buf.push(writable);
3095 }
3096 }
3097}
3098
3099impl<'a> Deref for WritableBuffer<'a> {
3100 type Target = [Writable<'a>];
3101
3102 fn deref(&self) -> &Self::Target {
3103 &self.buf[..]
3104 }
3105}
3106
3107#[derive(Debug)]
3108enum Writable<'a> {
3109 Lit(Cow<'a, str>),
3110 Expr(&'a WithSpan<'a, Expr<'a>>),
3111}
3112
3113/// Identifiers to be replaced with raw identifiers, so as to avoid
3114/// collisions between template syntax and Rust's syntax. In particular
3115/// [Rust keywords](https://doc.rust-lang.org/reference/keywords.html)
3116/// should be replaced, since they're not reserved words in Rinja
3117/// syntax but have a high probability of causing problems in the
3118/// generated code.
3119///
3120/// This list excludes the Rust keywords *self*, *Self*, and *super*
3121/// because they are not allowed to be raw identifiers, and *loop*
3122/// because it's used something like a keyword in the template
3123/// language.
3124fn normalize_identifier(ident: &str) -> &str {
3125 // This table works for as long as the replacement string is the original string
3126 // prepended with "r#". The strings get right-padded to the same length with b'_'.
3127 // While the code does not need it, please keep the list sorted when adding new
3128 // keywords.
3129
3130 if ident.len() > parser::node::MAX_KW_LEN {
3131 return ident;
3132 }
3133 let kws = parser::node::KWS[ident.len()];
3134
3135 let mut padded_ident = [b'_'; parser::node::MAX_KW_LEN];
3136 padded_ident[..ident.len()].copy_from_slice(ident.as_bytes());
3137
3138 // Since the individual buckets are quite short, a linear search is faster than a binary search.
3139 let replacement = match kws.iter().find(|probe| {
3140 padded_ident == <[u8; parser::node::MAX_KW_LEN]>::try_from(&probe[2..]).unwrap()
3141 }) {
3142 Some(replacement) => replacement,
3143 None => return ident,
3144 };
3145
3146 // SAFETY: We know that the input byte slice is pure-ASCII.
3147 unsafe { std::str::from_utf8_unchecked(&replacement[..ident.len() + 2]) }
3148}
3149
3150/// Similar to `write!(dest, "{src:?}")`, but only escapes the strictly needed characters,
3151/// and without the surrounding `"…"` quotation marks.
3152pub(crate) fn string_escape(dest: &mut String, src: &str) {
3153 // SAFETY: we will only push valid str slices
3154 let dest: &mut Vec = unsafe { dest.as_mut_vec() };
3155 let src: &[u8] = src.as_bytes();
3156 let mut last: usize = 0;
3157
3158 // According to <https://doc.rust-lang.org/reference/tokens.html#string-literals>, every
3159 // character is valid except `" \ IsolatedCR`. We don't test if the `\r` is isolated or not,
3160 // but always escape it.
3161 for x: usize in memchr::memchr3_iter(needle1:b'\\', needle2:b'"', needle3:b'\r', haystack:src) {
3162 dest.extend(&src[last..x]);
3163 dest.extend(iter:match src[x] {
3164 b'\\' => br"\\",
3165 b'\"' => br#"\""#,
3166 _ => br"\r",
3167 });
3168 last = x + 1;
3169 }
3170 dest.extend(&src[last..]);
3171}
3172