use std::borrow::Cow; use std::collections::hash_map::{Entry, HashMap}; use std::fmt::{Arguments, Display, Write}; use std::ops::Deref; use std::path::Path; use std::rc::Rc; use std::{cmp, hash, mem, str}; use crate::config::WhitespaceHandling; use crate::heritage::{Context, Heritage}; use crate::input::{Source, TemplateInput}; use crate::{CompileError, CRATE}; use parser::node::{ Call, Comment, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Match, Whitespace, Ws, }; use parser::{Expr, Filter, Node, Target, WithSpan}; use quote::quote; pub(crate) struct Generator<'a> { // The template input state: original struct AST and attributes input: &'a TemplateInput<'a>, // All contexts, keyed by the package-relative template path contexts: &'a HashMap<&'a Rc, Context<'a>>, // The heritage contains references to blocks and their ancestry heritage: Option<&'a Heritage<'a>>, // Variables accessible directly from the current scope (not redirected to context) locals: MapChain<'a, Cow<'a, str>, LocalMeta>, // Suffix whitespace from the previous literal. Will be flushed to the // output buffer unless suppressed by whitespace suppression on the next // non-literal. next_ws: Option<&'a str>, // Whitespace suppression from the previous non-literal. Will be used to // determine whether to flush prefix whitespace from the next literal. skip_ws: WhitespaceHandling, // If currently in a block, this will contain the name of a potential parent block super_block: Option<(&'a str, usize)>, // Buffer for writable buf_writable: WritableBuffer<'a>, // Counter for write! hash named arguments named: usize, } impl<'a> Generator<'a> { pub(crate) fn new<'n>( input: &'n TemplateInput<'_>, contexts: &'n HashMap<&'n Rc, Context<'n>>, heritage: Option<&'n Heritage<'_>>, locals: MapChain<'n, Cow<'n, str>, LocalMeta>, buf_writable_discard: bool, ) -> Generator<'n> { Generator { input, contexts, heritage, locals, next_ws: None, skip_ws: WhitespaceHandling::Preserve, super_block: None, buf_writable: WritableBuffer { discard: buf_writable_discard, ..Default::default() }, named: 0, } } // Takes a Context and generates the relevant implementations. pub(crate) fn build(mut self, ctx: &Context<'a>) -> Result { let mut buf = Buffer::new(); self.impl_template(ctx, &mut buf)?; self.impl_display(&mut buf); #[cfg(feature = "with-actix-web")] self.impl_actix_web_responder(&mut buf); #[cfg(feature = "with-axum")] self.impl_axum_into_response(&mut buf); #[cfg(feature = "with-rocket")] self.impl_rocket_responder(&mut buf); #[cfg(feature = "with-warp")] self.impl_warp_reply(&mut buf); Ok(buf.buf) } // Implement `Template` for the given context struct. fn impl_template(&mut self, ctx: &Context<'a>, buf: &mut Buffer) -> Result<(), CompileError> { self.write_header(buf, format_args!("{CRATE}::Template"), None); buf.write("fn render_into(&self, writer: &mut (impl ::std::fmt::Write + ?Sized)) -> "); buf.write(CRATE); buf.writeln("::Result<()> {"); buf.discard = self.buf_writable.discard; // Make sure the compiler understands that the generated code depends on the template files. for path in self.contexts.keys() { // Skip the fake path of templates defined in rust source. let path_is_valid = match self.input.source { Source::Path(_) => true, Source::Source(_) => **path != self.input.path, }; if path_is_valid { let path = path.to_str().unwrap(); buf.writeln( quote! { const _: &[::core::primitive::u8] = ::core::include_bytes!(#path); } .to_string(), ); } } let size_hint = if let Some(heritage) = self.heritage { self.handle(heritage.root, heritage.root.nodes, buf, AstLevel::Top) } else { self.handle(ctx, ctx.nodes, buf, AstLevel::Top) }?; buf.discard = false; self.flush_ws(Ws(None, None)); buf.write(CRATE); buf.writeln("::Result::Ok(())"); buf.writeln("}"); buf.writeln(format_args!( "const EXTENSION: ::std::option::Option<&'static ::std::primitive::str> = {:?};", self.input.extension(), )); buf.writeln(format_args!( "const SIZE_HINT: ::std::primitive::usize = {size_hint};", )); buf.writeln(format_args!( "const MIME_TYPE: &'static ::std::primitive::str = {:?};", self.input.mime_type, )); buf.writeln("}"); Ok(()) } // Implement `Display` for the given context struct. fn impl_display(&mut self, buf: &mut Buffer) { self.write_header(buf, "::std::fmt::Display", None); buf.writeln("#[inline]"); buf.writeln("fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {"); buf.write(CRATE); buf.writeln("::Template::render_into(self, f).map_err(|_| ::std::fmt::Error {})"); buf.writeln("}"); buf.writeln("}"); } // Implement Actix-web's `Responder`. #[cfg(feature = "with-actix-web")] fn impl_actix_web_responder(&mut self, buf: &mut Buffer) { self.write_header(buf, "::rinja_actix::actix_web::Responder", None); buf.writeln("type Body = ::rinja_actix::actix_web::body::BoxBody;"); buf.writeln("#[inline]"); buf.writeln( "fn respond_to(self, _req: &::rinja_actix::actix_web::HttpRequest) \ -> ::rinja_actix::actix_web::HttpResponse {", ); buf.writeln("::rinja_actix::into_response(&self)"); buf.writeln("}"); buf.writeln("}"); } // Implement Axum's `IntoResponse`. #[cfg(feature = "with-axum")] fn impl_axum_into_response(&mut self, buf: &mut Buffer) { self.write_header(buf, "::rinja_axum::axum_core::response::IntoResponse", None); buf.writeln("#[inline]"); buf.writeln( "fn into_response(self)\ -> ::rinja_axum::axum_core::response::Response {", ); buf.writeln("::rinja_axum::into_response(&self)"); buf.writeln("}"); buf.writeln("}"); } // Implement Rocket's `Responder`. #[cfg(feature = "with-rocket")] fn impl_rocket_responder(&mut self, buf: &mut Buffer) { let lifetime1 = syn::Lifetime::new("'rinja1", proc_macro2::Span::call_site()); let param1 = syn::GenericParam::Lifetime(syn::LifetimeParam::new(lifetime1)); self.write_header( buf, "::rinja_rocket::rocket::response::Responder<'rinja1, 'static>", Some(vec![param1]), ); buf.writeln("#[inline]"); buf.writeln( "fn respond_to(self, _: &'rinja1 ::rinja_rocket::rocket::request::Request<'_>) \ -> ::rinja_rocket::rocket::response::Result<'static> {", ); buf.writeln("::rinja_rocket::respond(&self)"); buf.writeln("}"); buf.writeln("}"); } #[cfg(feature = "with-warp")] fn impl_warp_reply(&mut self, buf: &mut Buffer) { self.write_header(buf, "::rinja_warp::warp::reply::Reply", None); buf.writeln("#[inline]"); buf.writeln("fn into_response(self) -> ::rinja_warp::warp::reply::Response {"); buf.writeln("::rinja_warp::into_response(&self)"); buf.writeln("}"); buf.writeln("}"); } // Writes header for the `impl` for `TraitFromPathName` or `Template` // for the given context struct. fn write_header( &mut self, buf: &mut Buffer, target: impl Display, params: Option>, ) { let mut generics; let (impl_generics, orig_ty_generics, where_clause) = if let Some(params) = params { generics = self.input.ast.generics.clone(); for param in params { generics.params.push(param); } let (_, orig_ty_generics, _) = self.input.ast.generics.split_for_impl(); let (impl_generics, _, where_clause) = generics.split_for_impl(); (impl_generics, orig_ty_generics, where_clause) } else { self.input.ast.generics.split_for_impl() }; buf.writeln(format_args!( "{} {} for {}{} {{", quote!(impl #impl_generics), target, self.input.ast.ident, quote!(#orig_ty_generics #where_clause), )); } /* Helper methods for handling node types */ fn handle( &mut self, ctx: &Context<'a>, nodes: &'a [Node<'_>], buf: &mut Buffer, level: AstLevel, ) -> Result { let mut size_hint = 0; for n in nodes { match *n { Node::Lit(ref lit) => { self.visit_lit(lit); } Node::Comment(ref comment) => { self.write_comment(comment); } Node::Expr(ws, ref val) => { self.write_expr(ws, val); } Node::Let(ref l) => { self.write_let(ctx, buf, l)?; } Node::If(ref i) => { size_hint += self.write_if(ctx, buf, i)?; } Node::Match(ref m) => { size_hint += self.write_match(ctx, buf, m)?; } Node::Loop(ref loop_block) => { size_hint += self.write_loop(ctx, buf, loop_block)?; } Node::BlockDef(ref b) => { size_hint += self.write_block(ctx, buf, Some(b.name), Ws(b.ws1.0, b.ws2.1), b)?; } Node::Include(ref i) => { size_hint += self.handle_include(ctx, buf, i)?; } Node::Call(ref call) => { size_hint += self.write_call(ctx, buf, call)?; } Node::FilterBlock(ref filter) => { size_hint += self.write_filter_block(ctx, buf, filter)?; } Node::Macro(ref m) => { if level != AstLevel::Top { return Err( ctx.generate_error("macro blocks only allowed at the top level", m) ); } self.flush_ws(m.ws1); self.prepare_ws(m.ws2); } Node::Raw(ref raw) => { self.handle_ws(raw.ws1); self.visit_lit(&raw.lit); self.handle_ws(raw.ws2); } Node::Import(ref i) => { if level != AstLevel::Top { return Err( ctx.generate_error("import blocks only allowed at the top level", i) ); } self.handle_ws(i.ws); } Node::Extends(ref e) => { if level != AstLevel::Top { return Err( ctx.generate_error("extend blocks only allowed at the top level", e) ); } // No whitespace handling: child template top-level is not used, // except for the blocks defined in it. } Node::Break(ref ws) => { self.handle_ws(**ws); self.write_buf_writable(ctx, buf)?; buf.writeln("break;"); } Node::Continue(ref ws) => { self.handle_ws(**ws); self.write_buf_writable(ctx, buf)?; buf.writeln("continue;"); } } } if AstLevel::Top == level { // Handle any pending whitespace. if self.next_ws.is_some() { self.flush_ws(Ws(Some(self.skip_ws.into()), None)); } size_hint += self.write_buf_writable(ctx, buf)?; } Ok(size_hint) } fn write_if( &mut self, ctx: &Context<'a>, buf: &mut Buffer, i: &'a If<'_>, ) -> Result { let mut flushed = 0; let mut arm_sizes = Vec::new(); let mut has_else = false; for (i, cond) in i.branches.iter().enumerate() { self.handle_ws(cond.ws); flushed += self.write_buf_writable(ctx, buf)?; if i > 0 { self.locals.pop(); } self.locals.push(); let mut arm_size = 0; if let Some(CondTest { target, expr }) = &cond.cond { if i == 0 { buf.write("if "); } else { buf.write("} else if "); } if let Some(target) = target { let mut expr_buf = Buffer::new(); buf.write("let "); // If this is a chain condition, then we need to declare the variable after the // left expression has been handled but before the right expression is handled // but this one should have access to the let-bound variable. match &**expr { Expr::BinOp(op, ref left, ref right) if *op == "||" || *op == "&&" => { self.visit_expr(ctx, &mut expr_buf, left)?; self.visit_target(buf, true, true, target); expr_buf.write(format_args!(" {op} ")); self.visit_expr(ctx, &mut expr_buf, right)?; } _ => { self.visit_expr(ctx, &mut expr_buf, expr)?; self.visit_target(buf, true, true, target); } } buf.write(" = &"); buf.write(expr_buf.buf); } else { // The following syntax `*(&(...) as &bool)` is used to // trigger Rust's automatic dereferencing, to coerce // e.g. `&&&&&bool` to `bool`. First `&(...) as &bool` // coerces e.g. `&&&bool` to `&bool`. Then `*(&bool)` // finally dereferences it to `bool`. buf.write("*(&("); buf.write(self.visit_expr_root(ctx, expr)?); buf.write(") as &bool)"); } } else { buf.write("} else"); has_else = true; } buf.writeln(" {"); arm_size += self.handle(ctx, &cond.nodes, buf, AstLevel::Nested)?; arm_sizes.push(arm_size); } self.handle_ws(i.ws); flushed += self.write_buf_writable(ctx, buf)?; buf.writeln("}"); self.locals.pop(); if !has_else { arm_sizes.push(0); } Ok(flushed + median(&mut arm_sizes)) } #[allow(clippy::too_many_arguments)] fn write_match( &mut self, ctx: &Context<'a>, buf: &mut Buffer, m: &'a Match<'a>, ) -> Result { let Match { ws1, ref expr, ref arms, ws2, } = *m; self.flush_ws(ws1); let flushed = self.write_buf_writable(ctx, buf)?; let mut arm_sizes = Vec::new(); let expr_code = self.visit_expr_root(ctx, expr)?; buf.writeln(format_args!("match &{expr_code} {{")); let mut arm_size = 0; for (i, arm) in arms.iter().enumerate() { self.handle_ws(arm.ws); if i > 0 { arm_sizes.push(arm_size + self.write_buf_writable(ctx, buf)?); buf.writeln("}"); self.locals.pop(); } self.locals.push(); self.visit_target(buf, true, true, &arm.target); buf.writeln(" => {"); arm_size = self.handle(ctx, &arm.nodes, buf, AstLevel::Nested)?; } self.handle_ws(ws2); arm_sizes.push(arm_size + self.write_buf_writable(ctx, buf)?); buf.writeln("}"); self.locals.pop(); buf.writeln("}"); Ok(flushed + median(&mut arm_sizes)) } #[allow(clippy::too_many_arguments)] fn write_loop( &mut self, ctx: &Context<'a>, buf: &mut Buffer, loop_block: &'a WithSpan<'_, Loop<'_>>, ) -> Result { self.handle_ws(loop_block.ws1); self.locals.push(); let expr_code = self.visit_expr_root(ctx, &loop_block.iter)?; let has_else_nodes = !loop_block.else_nodes.is_empty(); let flushed = self.write_buf_writable(ctx, buf)?; buf.writeln("{"); if has_else_nodes { buf.writeln("let mut _did_loop = false;"); } match &*loop_block.iter { Expr::Range(_, _, _) => buf.writeln(format_args!("let _iter = {expr_code};")), Expr::Array(..) => buf.writeln(format_args!("let _iter = {expr_code}.iter();")), // If `iter` is a call then we assume it's something that returns // an iterator. If not then the user can explicitly add the needed // call without issues. Expr::Call(..) | Expr::Index(..) => { buf.writeln(format_args!("let _iter = ({expr_code}).into_iter();")) } // If accessing `self` then it most likely needs to be // borrowed, to prevent an attempt of moving. _ if expr_code.starts_with("self.") => { buf.writeln(format_args!("let _iter = (&{expr_code}).into_iter();")) } // If accessing a field then it most likely needs to be // borrowed, to prevent an attempt of moving. Expr::Attr(..) => buf.writeln(format_args!("let _iter = (&{expr_code}).into_iter();")), // Otherwise, we borrow `iter` assuming that it implements `IntoIterator`. _ => buf.writeln(format_args!("let _iter = ({expr_code}).into_iter();")), } if let Some(cond) = &loop_block.cond { self.locals.push(); buf.write("let _iter = _iter.filter(|"); self.visit_target(buf, true, true, &loop_block.var); buf.write("| -> bool {"); self.visit_expr(ctx, buf, cond)?; buf.writeln("});"); self.locals.pop(); } self.locals.push(); buf.write("for ("); self.visit_target(buf, true, true, &loop_block.var); buf.write(", _loop_item) in "); buf.write(CRATE); buf.writeln("::helpers::TemplateLoop::new(_iter) {"); if has_else_nodes { buf.writeln("_did_loop = true;"); } let mut size_hint1 = self.handle(ctx, &loop_block.body, buf, AstLevel::Nested)?; self.handle_ws(loop_block.ws2); size_hint1 += self.write_buf_writable(ctx, buf)?; self.locals.pop(); buf.writeln("}"); let mut size_hint2; if has_else_nodes { buf.writeln("if !_did_loop {"); self.locals.push(); size_hint2 = self.handle(ctx, &loop_block.else_nodes, buf, AstLevel::Nested)?; self.handle_ws(loop_block.ws3); size_hint2 += self.write_buf_writable(ctx, buf)?; self.locals.pop(); buf.writeln("}"); } else { self.handle_ws(loop_block.ws3); size_hint2 = self.write_buf_writable(ctx, buf)?; } buf.writeln("}"); Ok(flushed + ((size_hint1 * 3) + size_hint2) / 2) } fn write_call( &mut self, ctx: &Context<'a>, buf: &mut Buffer, call: &'a WithSpan<'_, Call<'_>>, ) -> Result { let Call { ws, scope, name, ref args, } = **call; if name == "super" { return self.write_block(ctx, buf, None, ws, call); } let (def, own_ctx) = match scope { Some(s) => { let path = ctx.imports.get(s).ok_or_else(|| { ctx.generate_error(&format!("no import found for scope {s:?}"), call) })?; let mctx = self.contexts.get(path).ok_or_else(|| { ctx.generate_error(&format!("context for {path:?} not found"), call) })?; let def = mctx.macros.get(name).ok_or_else(|| { ctx.generate_error(&format!("macro {name:?} not found in scope {s:?}"), call) })?; (def, mctx) } None => { let def = ctx.macros.get(name).ok_or_else(|| { ctx.generate_error(&format!("macro {name:?} not found"), call) })?; (def, ctx) } }; self.flush_ws(ws); // Cannot handle_ws() here: whitespace from macro definition comes first self.locals.push(); self.write_buf_writable(ctx, buf)?; buf.writeln("{"); self.prepare_ws(def.ws1); let mut names = Buffer::new(); let mut values = Buffer::new(); let mut is_first_variable = true; if args.len() != def.args.len() { return Err(ctx.generate_error( &format!( "macro {name:?} expected {} argument{}, found {}", def.args.len(), if def.args.len() != 1 { "s" } else { "" }, args.len() ), call, )); } let mut named_arguments = HashMap::new(); // Since named arguments can only be passed last, we only need to check if the last argument // is a named one. if let Some(Expr::NamedArgument(_, _)) = args.last().map(|expr| &**expr) { // First we check that all named arguments actually exist in the called item. for arg in args.iter().rev() { let Expr::NamedArgument(arg_name, _) = &**arg else { break; }; if !def.args.iter().any(|arg| arg == arg_name) { return Err(ctx.generate_error( &format!("no argument named `{arg_name}` in macro {name:?}"), call, )); } named_arguments.insert(Cow::Borrowed(arg_name), arg); } } // Handling both named and unnamed arguments requires to be careful of the named arguments // order. To do so, we iterate through the macro defined arguments and then check if we have // a named argument with this name: // // * If there is one, we add it and move to the next argument. // * If there isn't one, then we pick the next argument (we can do it without checking // anything since named arguments are always last). let mut allow_positional = true; for (index, arg) in def.args.iter().enumerate() { let expr = match named_arguments.get(&Cow::Borrowed(arg)) { Some(expr) => { allow_positional = false; expr } None => { if !allow_positional { // If there is already at least one named argument, then it's not allowed // to use unnamed ones at this point anymore. return Err(ctx.generate_error( &format!( "cannot have unnamed argument (`{arg}`) after named argument in macro \ {name:?}" ), call, )); } &args[index] } }; match &**expr { // If `expr` is already a form of variable then // don't reintroduce a new variable. This is // to avoid moving non-copyable values. Expr::Var(name) if *name != "self" => { let var = self.locals.resolve_or_self(name); self.locals .insert(Cow::Borrowed(arg), LocalMeta::with_ref(var)); } Expr::Attr(obj, attr) => { let mut attr_buf = Buffer::new(); self.visit_attr(ctx, &mut attr_buf, obj, attr)?; let var = self.locals.resolve(&attr_buf.buf).unwrap_or(attr_buf.buf); self.locals .insert(Cow::Borrowed(arg), LocalMeta::with_ref(var)); } // Everything else still needs to become variables, // to avoid having the same logic be executed // multiple times, e.g. in the case of macro // parameters being used multiple times. _ => { if is_first_variable { is_first_variable = false } else { names.write(", "); values.write(", "); } names.write(arg); values.write("("); if !is_copyable(expr) { values.write("&"); } values.write(self.visit_expr_root(ctx, expr)?); values.write(")"); self.locals.insert_with_default(Cow::Borrowed(arg)); } } } debug_assert_eq!(names.buf.is_empty(), values.buf.is_empty()); if !names.buf.is_empty() { buf.writeln(format_args!("let ({}) = ({});", names.buf, values.buf)); } let mut size_hint = self.handle(own_ctx, &def.nodes, buf, AstLevel::Nested)?; self.flush_ws(def.ws2); size_hint += self.write_buf_writable(ctx, buf)?; buf.writeln("}"); self.locals.pop(); self.prepare_ws(ws); Ok(size_hint) } fn write_filter_block( &mut self, ctx: &Context<'a>, buf: &mut Buffer, filter: &'a WithSpan<'_, FilterBlock<'_>>, ) -> Result { self.flush_ws(filter.ws1); let mut var_name = String::new(); for id in 0.. { var_name = format!("__filter_block{id}"); if self.locals.get(&Cow::Borrowed(&var_name)).is_none() { // No variable with this name exists, we're in the clear! break; } } let current_buf = mem::take(&mut self.buf_writable.buf); self.prepare_ws(filter.ws1); let mut size_hint = self.handle(ctx, &filter.nodes, buf, AstLevel::Nested)?; self.flush_ws(filter.ws2); let WriteParts { size_hint: write_size_hint, buffers, } = self.prepare_format(ctx)?; size_hint += match buffers { None => return Ok(0), Some(WritePartsBuffers { format, expr: None }) => { buf.writeln(format_args!("let {var_name} = {:#?};", &format.buf)); write_size_hint } Some(WritePartsBuffers { format, expr: Some(expr), }) => { buf.writeln(format_args!( "let {var_name} = format!({:#?}, {});", &format.buf, expr.buf.trim(), )); write_size_hint } }; self.buf_writable.buf = current_buf; let mut filter_buf = Buffer::new(); let Filter { name: filter_name, arguments, } = &filter.filters; let mut arguments = arguments.clone(); insert_first_filter_argument(&mut arguments, var_name.clone()); let wrap = self.visit_filter(ctx, &mut filter_buf, filter_name, &arguments, filter)?; self.buf_writable .push(Writable::Generated(filter_buf.buf, wrap)); self.prepare_ws(filter.ws2); // We don't forget to add the created variable into the list of variables in the scope. self.locals .insert(Cow::Owned(var_name), LocalMeta::initialized()); Ok(size_hint) } fn handle_include( &mut self, ctx: &Context<'a>, buf: &mut Buffer, i: &'a Include<'_>, ) -> Result { self.flush_ws(i.ws); self.write_buf_writable(ctx, buf)?; let path = self .input .config .find_template(i.path, Some(&self.input.path))?; // Make sure the compiler understands that the generated code depends on the template file. { let path = path.to_str().unwrap(); buf.writeln( quote! { const _: &[::core::primitive::u8] = ::core::include_bytes!(#path); } .to_string(), ); } // We clone the context of the child in order to preserve their macros and imports. // But also add all the imports and macros from this template that don't override the // child's ones to preserve this template's context. let child_ctx = &mut self.contexts[&path].clone(); for (name, mac) in &ctx.macros { child_ctx.macros.entry(name).or_insert(mac); } for (name, import) in &ctx.imports { child_ctx .imports .entry(name) .or_insert_with(|| import.clone()); } // Create a new generator for the child, and call it like in `impl_template` as if it were // a full template, while preserving the context. let heritage = if !child_ctx.blocks.is_empty() || child_ctx.extends.is_some() { Some(Heritage::new(child_ctx, self.contexts)) } else { None }; let handle_ctx = match &heritage { Some(heritage) => heritage.root, None => child_ctx, }; let locals = MapChain::with_parent(&self.locals); let mut child = Self::new( self.input, self.contexts, heritage.as_ref(), locals, self.buf_writable.discard, ); let mut size_hint = child.handle(handle_ctx, handle_ctx.nodes, buf, AstLevel::Top)?; size_hint += child.write_buf_writable(handle_ctx, buf)?; self.prepare_ws(i.ws); Ok(size_hint) } fn is_shadowing_variable( &self, ctx: &Context<'_>, var: &Target<'a>, l: &WithSpan<'_, T>, ) -> Result { match var { Target::Name(name) => { let name = normalize_identifier(name); match self.locals.get(&Cow::Borrowed(name)) { // declares a new variable None => Ok(false), // an initialized variable gets shadowed Some(meta) if meta.initialized => Ok(true), // initializes a variable that was introduced in a LetDecl before _ => Ok(false), } } Target::Tuple(_, targets) => { for target in targets { match self.is_shadowing_variable(ctx, target, l) { Ok(false) => continue, outcome => return outcome, } } Ok(false) } Target::Struct(_, named_targets) => { for (_, target) in named_targets { match self.is_shadowing_variable(ctx, target, l) { Ok(false) => continue, outcome => return outcome, } } Ok(false) } _ => Err(ctx.generate_error( "literals are not allowed on the left-hand side of an assignment", l, )), } } fn write_let( &mut self, ctx: &Context<'_>, buf: &mut Buffer, l: &'a WithSpan<'_, Let<'_>>, ) -> Result<(), CompileError> { self.handle_ws(l.ws); let Some(val) = &l.val else { self.write_buf_writable(ctx, buf)?; buf.write("let "); self.visit_target(buf, false, true, &l.var); buf.writeln(";"); return Ok(()); }; let mut expr_buf = Buffer::new(); self.visit_expr(ctx, &mut expr_buf, val)?; let shadowed = self.is_shadowing_variable(ctx, &l.var, l)?; if shadowed { // Need to flush the buffer if the variable is being shadowed, // to ensure the old variable is used. self.write_buf_writable(ctx, buf)?; } if shadowed || !matches!(l.var, Target::Name(_)) || matches!(&l.var, Target::Name(name) if self.locals.get(&Cow::Borrowed(name)).is_none()) { buf.write("let "); } self.visit_target(buf, true, true, &l.var); let (before, after) = if !is_copyable(val) { ("&(", ")") } else { ("", "") }; buf.writeln(format_args!(" = {before}{}{after};", &expr_buf.buf)); Ok(()) } // If `name` is `Some`, this is a call to a block definition, and we have to find // the first block for that name from the ancestry chain. If name is `None`, this // is from a `super()` call, and we can get the name from `self.super_block`. fn write_block( &mut self, ctx: &Context<'a>, buf: &mut Buffer, name: Option<&'a str>, outer: Ws, node: &WithSpan<'_, T>, ) -> Result { // Flush preceding whitespace according to the outer WS spec self.flush_ws(outer); let cur = match (name, self.super_block) { // The top-level context contains a block definition (Some(cur_name), None) => (cur_name, 0), // A block definition contains a block definition of the same name (Some(cur_name), Some((prev_name, _))) if cur_name == prev_name => { return Err(ctx.generate_error( &format!("cannot define recursive blocks ({cur_name})"), node, )); } // A block definition contains a definition of another block (Some(cur_name), Some((_, _))) => (cur_name, 0), // `super()` was called inside a block (None, Some((prev_name, gen))) => (prev_name, gen + 1), // `super()` is called from outside a block (None, None) => { return Err(ctx.generate_error("cannot call 'super()' outside block", node)) } }; self.write_buf_writable(ctx, buf)?; let block_fragment_write = self.input.block == name && self.buf_writable.discard; // Allow writing to the buffer if we're in the block fragment if block_fragment_write { self.buf_writable.discard = false; } let prev_buf_discard = mem::replace(&mut buf.discard, self.buf_writable.discard); // Get the block definition from the heritage chain let heritage = self .heritage .ok_or_else(|| ctx.generate_error("no block ancestors available", node))?; let (child_ctx, def) = *heritage.blocks[cur.0].get(cur.1).ok_or_else(|| { ctx.generate_error( &match name { None => format!("no super() block found for block '{}'", cur.0), Some(name) => format!("no block found for name '{name}'"), }, node, ) })?; // We clone the context of the child in order to preserve their macros and imports. // But also add all the imports and macros from this template that don't override the // child's ones to preserve this template's context. let mut child_ctx = child_ctx.clone(); for (name, mac) in &ctx.macros { child_ctx.macros.entry(name).or_insert(mac); } for (name, import) in &ctx.imports { child_ctx .imports .entry(name) .or_insert_with(|| import.clone()); } let mut child = Self::new( self.input, self.contexts, Some(heritage), // Variables are NOT inherited from the parent scope. MapChain::default(), self.buf_writable.discard, ); child.buf_writable = mem::take(&mut self.buf_writable); // Handle inner whitespace suppression spec and process block nodes child.prepare_ws(def.ws1); child.super_block = Some(cur); let size_hint = child.handle(&child_ctx, &def.nodes, buf, AstLevel::Block)?; if !child.locals.is_current_empty() { // Need to flush the buffer before popping the variable stack child.write_buf_writable(ctx, buf)?; } child.flush_ws(def.ws2); self.buf_writable = child.buf_writable; // Restore original block context and set whitespace suppression for // succeeding whitespace according to the outer WS spec self.prepare_ws(outer); // If we are rendering a specific block and the discard changed, it means that we're done // with the block we want to render and that from this point, everything will be discarded. // // To get this block content rendered as well, we need to write to the buffer before then. if buf.discard != prev_buf_discard { self.write_buf_writable(ctx, buf)?; } // Restore the original buffer discarding state if block_fragment_write { self.buf_writable.discard = true; } buf.discard = prev_buf_discard; Ok(size_hint) } fn write_expr(&mut self, ws: Ws, s: &'a WithSpan<'a, Expr<'a>>) { self.handle_ws(ws); self.buf_writable.push(Writable::Expr(s)); } // Write expression buffer and empty fn write_buf_writable( &mut self, ctx: &Context<'_>, buf: &mut Buffer, ) -> Result { let WriteParts { size_hint, buffers } = self.prepare_format(ctx)?; match buffers { None => Ok(size_hint), Some(WritePartsBuffers { format, expr: None }) => { buf.writeln(format_args!("writer.write_str({:#?})?;", &format.buf)); Ok(size_hint) } Some(WritePartsBuffers { format, expr: Some(expr), }) => { buf.writeln("::std::write!("); buf.writeln("writer,"); buf.writeln(format_args!("{:#?},", &format.buf)); buf.writeln(expr.buf.trim()); buf.writeln(")?;"); Ok(size_hint) } } } /// This is the common code to generate an expression. It is used for filter blocks and for /// expressions more generally. It stores the size it represents and the buffers. Take a look /// at `WriteParts` for more details. fn prepare_format(&mut self, ctx: &Context<'_>) -> Result { if self.buf_writable.is_empty() { return Ok(WriteParts { size_hint: 0, buffers: None, }); } if self .buf_writable .iter() .all(|w| matches!(w, Writable::Lit(_))) { let mut buf_lit = Buffer::new(); for s in mem::take(&mut self.buf_writable.buf) { if let Writable::Lit(s) = s { buf_lit.write(s); }; } return Ok(WriteParts { size_hint: buf_lit.buf.len(), buffers: Some(WritePartsBuffers { format: buf_lit, expr: None, }), }); } let mut expr_cache = HashMap::with_capacity(self.buf_writable.len()); let mut size_hint = 0; let mut buf_format = Buffer::new(); let mut buf_expr = Buffer::new(); for s in mem::take(&mut self.buf_writable.buf) { match s { Writable::Lit(s) => { buf_format.write(s.replace('{', "{{").replace('}', "}}")); size_hint += s.len(); } Writable::Expr(s) => { let mut expr_buf = Buffer::new(); let wrapped = self.visit_expr(ctx, &mut expr_buf, s)?; let cacheable = is_cacheable(s); size_hint += self.named_expression( &mut buf_expr, &mut buf_format, expr_buf.buf, wrapped, cacheable, &mut expr_cache, ); } Writable::Generated(s, wrapped) => { size_hint += self.named_expression( &mut buf_expr, &mut buf_format, s, wrapped, false, &mut expr_cache, ); } } } Ok(WriteParts { size_hint, buffers: Some(WritePartsBuffers { format: buf_format, expr: Some(buf_expr), }), }) } fn named_expression( &mut self, buf_expr: &mut Buffer, buf_format: &mut Buffer, expr: String, wrapped: DisplayWrap, cacheable: bool, expr_cache: &mut HashMap, ) -> usize { let expression = match wrapped { DisplayWrap::Wrapped => expr, DisplayWrap::Unwrapped => format!( "{CRATE}::MarkupDisplay::new_unsafe(&({}), {})", expr, self.input.escaper ), }; let id = match expr_cache.entry(expression) { Entry::Occupied(e) if cacheable => *e.get(), entry => { let id = self.named; self.named += 1; buf_expr.write(format_args!("expr{id} = &{},", entry.key())); if let Entry::Vacant(e) = entry { e.insert(id); } id } }; buf_format.write(format_args!("{{expr{id}}}")); 3 } fn visit_lit(&mut self, lit: &'a Lit<'_>) { assert!(self.next_ws.is_none()); let Lit { lws, val, rws } = *lit; if !lws.is_empty() { match self.skip_ws { WhitespaceHandling::Suppress => {} _ if val.is_empty() => { assert!(rws.is_empty()); self.next_ws = Some(lws); } WhitespaceHandling::Preserve => self.buf_writable.push(Writable::Lit(lws)), WhitespaceHandling::Minimize => { self.buf_writable .push(Writable::Lit(match lws.contains('\n') { true => "\n", false => " ", })); } } } if !val.is_empty() { self.skip_ws = WhitespaceHandling::Preserve; self.buf_writable.push(Writable::Lit(val)); } if !rws.is_empty() { self.next_ws = Some(rws); } } fn write_comment(&mut self, comment: &'a WithSpan<'_, Comment<'_>>) { self.handle_ws(comment.ws); } /* Visitor methods for expression types */ fn visit_expr_root( &mut self, ctx: &Context<'_>, expr: &WithSpan<'_, Expr<'_>>, ) -> Result { let mut buf = Buffer::new(); self.visit_expr(ctx, &mut buf, expr)?; Ok(buf.buf) } fn visit_expr( &mut self, ctx: &Context<'_>, buf: &mut Buffer, expr: &WithSpan<'_, Expr<'_>>, ) -> Result { Ok(match **expr { Expr::BoolLit(s) => self.visit_bool_lit(buf, s), Expr::NumLit(s) => self.visit_num_lit(buf, s), Expr::StrLit(s) => self.visit_str_lit(buf, s), Expr::CharLit(s) => self.visit_char_lit(buf, s), Expr::Var(s) => self.visit_var(buf, s), Expr::Path(ref path) => self.visit_path(buf, path), Expr::Array(ref elements) => self.visit_array(ctx, buf, elements)?, Expr::Attr(ref obj, name) => self.visit_attr(ctx, buf, obj, name)?, Expr::Index(ref obj, ref key) => self.visit_index(ctx, buf, obj, key)?, Expr::Filter(Filter { name, ref arguments, }) => self.visit_filter(ctx, buf, name, arguments, expr)?, Expr::Unary(op, ref inner) => self.visit_unary(ctx, buf, op, inner)?, Expr::BinOp(op, ref left, ref right) => self.visit_binop(ctx, buf, op, left, right)?, Expr::Range(op, ref left, ref right) => { self.visit_range(ctx, buf, op, left.as_deref(), right.as_deref())? } Expr::Group(ref inner) => self.visit_group(ctx, buf, inner)?, Expr::Call(ref obj, ref args) => self.visit_call(ctx, buf, obj, args)?, Expr::RustMacro(ref path, args) => self.visit_rust_macro(buf, path, args), Expr::Try(ref expr) => self.visit_try(ctx, buf, expr)?, Expr::Tuple(ref exprs) => self.visit_tuple(ctx, buf, exprs)?, Expr::NamedArgument(_, ref expr) => self.visit_named_argument(ctx, buf, expr)?, Expr::Generated(ref s) => self.visit_generated(buf, s), }) } fn visit_try( &mut self, ctx: &Context<'_>, buf: &mut Buffer, expr: &WithSpan<'_, Expr<'_>>, ) -> Result { buf.write("::core::result::Result::map_err("); self.visit_expr(ctx, buf, expr)?; buf.write(", |err| "); buf.write(CRATE); buf.write("::shared::Error::Custom(::core::convert::Into::into(err)))?"); Ok(DisplayWrap::Unwrapped) } fn visit_rust_macro(&mut self, buf: &mut Buffer, path: &[&str], args: &str) -> DisplayWrap { self.visit_path(buf, path); buf.write("!("); buf.write(args); buf.write(")"); DisplayWrap::Unwrapped } fn visit_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, name: &str, args: &[WithSpan<'_, Expr<'_>>], filter: &WithSpan<'_, T>, ) -> Result { match name { "deref" => return self._visit_deref_filter(ctx, buf, args, filter), "escape" | "e" => return self._visit_escape_filter(ctx, buf, args, filter), "fmt" => return self._visit_fmt_filter(ctx, buf, args, filter), "format" => return self._visit_format_filter(ctx, buf, args, filter), "join" => return self._visit_join_filter(ctx, buf, args), "json" | "tojson" => return self._visit_json_filter(ctx, buf, args, filter), "ref" => return self._visit_ref_filter(ctx, buf, args, filter), "safe" => return self._visit_safe_filter(ctx, buf, args, filter), _ => {} } if crate::BUILT_IN_FILTERS.contains(&name) { buf.write(format_args!("{CRATE}::filters::{name}(")); } else { buf.write(format_args!("filters::{name}(")); } self._visit_args(ctx, buf, args)?; buf.write(")?"); Ok(DisplayWrap::Unwrapped) } fn _visit_ref_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], node: &WithSpan<'_, T>, ) -> Result { let arg = match args { [arg] => arg, _ => return Err(ctx.generate_error("unexpected argument(s) in `as_ref` filter", node)), }; buf.write("&"); self.visit_expr(ctx, buf, arg)?; Ok(DisplayWrap::Unwrapped) } fn _visit_deref_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], node: &WithSpan<'_, T>, ) -> Result { let arg = match args { [arg] => arg, _ => return Err(ctx.generate_error("unexpected argument(s) in `deref` filter", node)), }; buf.write("*"); self.visit_expr(ctx, buf, arg)?; Ok(DisplayWrap::Unwrapped) } fn _visit_json_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], node: &WithSpan<'_, T>, ) -> Result { if cfg!(not(feature = "serde_json")) { return Err(ctx.generate_error( "the `json` filter requires the `serde_json` feature to be enabled", node, )); } let filter = match args.len() { 1 => "json", 2 => "json_pretty", _ => return Err(ctx.generate_error("unexpected argument(s) in `json` filter", node)), }; buf.write(format_args!("{CRATE}::filters::{filter}(")); self._visit_args(ctx, buf, args)?; buf.write(")?"); Ok(DisplayWrap::Unwrapped) } fn _visit_safe_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], node: &WithSpan<'_, T>, ) -> Result { if args.len() != 1 { return Err(ctx.generate_error("unexpected argument(s) in `safe` filter", node)); } buf.write(CRATE); buf.write("::filters::safe("); buf.write(self.input.escaper); buf.write(", "); self._visit_args(ctx, buf, args)?; buf.write(")?"); Ok(DisplayWrap::Wrapped) } fn _visit_escape_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], node: &WithSpan<'_, T>, ) -> Result { if args.len() > 2 { return Err(ctx.generate_error("only two arguments allowed to escape filter", node)); } let opt_escaper = match args.get(1).map(|expr| &**expr) { Some(Expr::StrLit(name)) => Some(*name), Some(_) => { return Err(ctx.generate_error("invalid escaper type for escape filter", node)) } None => None, }; let escaper = match opt_escaper { Some(name) => self .input .config .escapers .iter() .find_map(|(escapers, escaper)| escapers.contains(name).then_some(escaper)) .ok_or_else(|| ctx.generate_error("invalid escaper for escape filter", node))?, None => self.input.escaper, }; buf.write(CRATE); buf.write("::filters::escape("); buf.write(escaper); buf.write(", "); self._visit_args(ctx, buf, &args[..1])?; buf.write(")?"); Ok(DisplayWrap::Wrapped) } fn _visit_format_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], node: &WithSpan<'_, T>, ) -> Result { if !args.is_empty() { if let Expr::StrLit(fmt) = *args[0] { buf.write("::std::format!("); self.visit_str_lit(buf, fmt); if args.len() > 1 { buf.write(", "); self._visit_args(ctx, buf, &args[1..])?; } buf.write(")"); return Ok(DisplayWrap::Unwrapped); } } Err(ctx.generate_error(r#"use filter format like `"a={} b={}"|format(a, b)`"#, node)) } fn _visit_fmt_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], node: &WithSpan<'_, T>, ) -> Result { if let [_, arg2] = args { if let Expr::StrLit(fmt) = **arg2 { buf.write("::std::format!("); self.visit_str_lit(buf, fmt); buf.write(", "); self._visit_args(ctx, buf, &args[..1])?; buf.write(")"); return Ok(DisplayWrap::Unwrapped); } } Err(ctx.generate_error(r#"use filter fmt like `value|fmt("{:?}")`"#, node)) } // Force type coercion on first argument to `join` filter (see #39). fn _visit_join_filter( &mut self, ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], ) -> Result { buf.write(CRATE); buf.write("::filters::join((&"); for (i, arg) in args.iter().enumerate() { if i > 0 { buf.write(", &"); } self.visit_expr(ctx, buf, arg)?; if i == 0 { buf.write(").into_iter()"); } } buf.write(")?"); Ok(DisplayWrap::Unwrapped) } fn _visit_args( &mut self, ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], ) -> Result<(), CompileError> { if args.is_empty() { return Ok(()); } for (i, arg) in args.iter().enumerate() { if i > 0 { buf.write(", "); } let borrow = !is_copyable(arg); if borrow { buf.write("&("); } match **arg { Expr::Call(ref left, _) if !matches!(***left, Expr::Path(_)) => { buf.writeln("{"); self.visit_expr(ctx, buf, arg)?; buf.writeln("}"); } _ => { self.visit_expr(ctx, buf, arg)?; } } if borrow { buf.write(")"); } } Ok(()) } fn visit_attr( &mut self, ctx: &Context<'_>, buf: &mut Buffer, obj: &WithSpan<'_, Expr<'_>>, attr: &str, ) -> Result { if let Expr::Var(name) = **obj { if name == "loop" { if attr == "index" { buf.write("(_loop_item.index + 1)"); return Ok(DisplayWrap::Unwrapped); } else if attr == "index0" { buf.write("_loop_item.index"); return Ok(DisplayWrap::Unwrapped); } else if attr == "first" { buf.write("_loop_item.first"); return Ok(DisplayWrap::Unwrapped); } else if attr == "last" { buf.write("_loop_item.last"); return Ok(DisplayWrap::Unwrapped); } else { return Err(ctx.generate_error("unknown loop variable", obj)); } } } self.visit_expr(ctx, buf, obj)?; buf.write(format_args!(".{}", normalize_identifier(attr))); Ok(DisplayWrap::Unwrapped) } fn visit_index( &mut self, ctx: &Context<'_>, buf: &mut Buffer, obj: &WithSpan<'_, Expr<'_>>, key: &WithSpan<'_, Expr<'_>>, ) -> Result { buf.write("&"); self.visit_expr(ctx, buf, obj)?; buf.write("["); self.visit_expr(ctx, buf, key)?; buf.write("]"); Ok(DisplayWrap::Unwrapped) } fn visit_call( &mut self, ctx: &Context<'_>, buf: &mut Buffer, left: &WithSpan<'_, Expr<'_>>, args: &[WithSpan<'_, Expr<'_>>], ) -> Result { match &**left { Expr::Attr(sub_left, method) if ***sub_left == Expr::Var("loop") => match *method { "cycle" => match args { [arg] => { if matches!(**arg, Expr::Array(ref arr) if arr.is_empty()) { return Err( ctx.generate_error("loop.cycle(…) cannot use an empty array", arg) ); } buf.write("({"); buf.write("let _cycle = &("); self.visit_expr(ctx, buf, arg)?; buf.writeln(");"); buf.writeln("let _len = _cycle.len();"); buf.writeln("if _len == 0 {"); buf.writeln(format_args!( "return ::core::result::Result::Err({CRATE}::Error::Fmt);" )); buf.writeln("}"); buf.writeln("_cycle[_loop_item.index % _len]"); buf.writeln("})"); } _ => { return Err( ctx.generate_error("loop.cycle(…) cannot use an empty array", left) ) } }, s => return Err(ctx.generate_error(&format!("unknown loop method: {s:?}"), left)), }, sub_left => { match sub_left { Expr::Var(name) => match self.locals.resolve(name) { Some(resolved) => buf.write(resolved), None => buf.write(format_args!("self.{}", normalize_identifier(name))), }, _ => { self.visit_expr(ctx, buf, left)?; } } buf.write("("); self._visit_args(ctx, buf, args)?; buf.write(")"); } } Ok(DisplayWrap::Unwrapped) } fn visit_unary( &mut self, ctx: &Context<'_>, buf: &mut Buffer, op: &str, inner: &WithSpan<'_, Expr<'_>>, ) -> Result { buf.write(op); self.visit_expr(ctx, buf, inner)?; Ok(DisplayWrap::Unwrapped) } fn visit_range( &mut self, ctx: &Context<'_>, buf: &mut Buffer, op: &str, left: Option<&WithSpan<'_, Expr<'_>>>, right: Option<&WithSpan<'_, Expr<'_>>>, ) -> Result { if let Some(left) = left { self.visit_expr(ctx, buf, left)?; } buf.write(op); if let Some(right) = right { self.visit_expr(ctx, buf, right)?; } Ok(DisplayWrap::Unwrapped) } fn visit_binop( &mut self, ctx: &Context<'_>, buf: &mut Buffer, op: &str, left: &WithSpan<'_, Expr<'_>>, right: &WithSpan<'_, Expr<'_>>, ) -> Result { self.visit_expr(ctx, buf, left)?; buf.write(format_args!(" {op} ")); self.visit_expr(ctx, buf, right)?; Ok(DisplayWrap::Unwrapped) } fn visit_group( &mut self, ctx: &Context<'_>, buf: &mut Buffer, inner: &WithSpan<'_, Expr<'_>>, ) -> Result { buf.write("("); self.visit_expr(ctx, buf, inner)?; buf.write(")"); Ok(DisplayWrap::Unwrapped) } fn visit_tuple( &mut self, ctx: &Context<'_>, buf: &mut Buffer, exprs: &[WithSpan<'_, Expr<'_>>], ) -> Result { buf.write("("); for (index, expr) in exprs.iter().enumerate() { if index > 0 { buf.write(" "); } self.visit_expr(ctx, buf, expr)?; buf.write(","); } buf.write(")"); Ok(DisplayWrap::Unwrapped) } fn visit_named_argument( &mut self, ctx: &Context<'_>, buf: &mut Buffer, expr: &WithSpan<'_, Expr<'_>>, ) -> Result { self.visit_expr(ctx, buf, expr)?; Ok(DisplayWrap::Unwrapped) } fn visit_array( &mut self, ctx: &Context<'_>, buf: &mut Buffer, elements: &[WithSpan<'_, Expr<'_>>], ) -> Result { buf.write("["); for (i, el) in elements.iter().enumerate() { if i > 0 { buf.write(", "); } self.visit_expr(ctx, buf, el)?; } buf.write("]"); Ok(DisplayWrap::Unwrapped) } fn visit_path(&mut self, buf: &mut Buffer, path: &[&str]) -> DisplayWrap { for (i, part) in path.iter().enumerate() { if i > 0 { buf.write("::"); } buf.write(part); } DisplayWrap::Unwrapped } fn visit_var(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap { if s == "self" { buf.write(s); return DisplayWrap::Unwrapped; } buf.write(normalize_identifier(&self.locals.resolve_or_self(s))); DisplayWrap::Unwrapped } fn visit_generated(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap { buf.write(s); DisplayWrap::Unwrapped } fn visit_bool_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap { buf.write(s); DisplayWrap::Unwrapped } fn visit_str_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap { buf.write(format_args!("\"{s}\"")); DisplayWrap::Unwrapped } fn visit_char_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap { buf.write(format_args!("'{s}'")); DisplayWrap::Unwrapped } fn visit_num_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap { buf.write(s); DisplayWrap::Unwrapped } fn visit_target( &mut self, buf: &mut Buffer, initialized: bool, first_level: bool, target: &Target<'a>, ) { match target { Target::Placeholder(s) | Target::Rest(s) => { buf.write(s); } Target::Name(name) => { let name = normalize_identifier(name); match initialized { true => self .locals .insert(Cow::Borrowed(name), LocalMeta::initialized()), false => self.locals.insert_with_default(Cow::Borrowed(name)), } buf.write(name); } Target::OrChain(targets) => match targets.first() { None => buf.write("_"), Some(first_target) => { self.visit_target(buf, initialized, first_level, first_target); for target in &targets[1..] { buf.write(" | "); self.visit_target(buf, initialized, first_level, target); } } }, Target::Tuple(path, targets) => { buf.write(SeparatedPath(path)); buf.write("("); for target in targets { self.visit_target(buf, initialized, false, target); buf.write(","); } buf.write(")"); } Target::Struct(path, targets) => { buf.write(SeparatedPath(path)); buf.write(" { "); for (name, target) in targets { if let Target::Rest(s) = target { buf.write(s); continue; } buf.write(normalize_identifier(name)); buf.write(": "); self.visit_target(buf, initialized, false, target); buf.write(","); } buf.write(" }"); } Target::Path(path) => { self.visit_path(buf, path); } Target::StrLit(s) => { if first_level { buf.write("&"); } self.visit_str_lit(buf, s); } Target::NumLit(s) => { if first_level { buf.write("&"); } self.visit_num_lit(buf, s); } Target::CharLit(s) => { if first_level { buf.write("&"); } self.visit_char_lit(buf, s); } Target::BoolLit(s) => { if first_level { buf.write("&"); } buf.write(s); } } } /* Helper methods for dealing with whitespace nodes */ // Combines `flush_ws()` and `prepare_ws()` to handle both trailing whitespace from the // preceding literal and leading whitespace from the succeeding literal. fn handle_ws(&mut self, ws: Ws) { self.flush_ws(ws); self.prepare_ws(ws); } fn should_trim_ws(&self, ws: Option) -> WhitespaceHandling { match ws { Some(Whitespace::Suppress) => WhitespaceHandling::Suppress, Some(Whitespace::Preserve) => WhitespaceHandling::Preserve, Some(Whitespace::Minimize) => WhitespaceHandling::Minimize, None => self.input.config.whitespace, } } // If the previous literal left some trailing whitespace in `next_ws` and the // prefix whitespace suppressor from the given argument, flush that whitespace. // In either case, `next_ws` is reset to `None` (no trailing whitespace). fn flush_ws(&mut self, ws: Ws) { if self.next_ws.is_none() { return; } // If `whitespace` is set to `suppress`, we keep the whitespace characters only if there is // a `+` character. match self.should_trim_ws(ws.0) { WhitespaceHandling::Preserve => { let val = self.next_ws.unwrap(); if !val.is_empty() { self.buf_writable.push(Writable::Lit(val)); } } WhitespaceHandling::Minimize => { let val = self.next_ws.unwrap(); if !val.is_empty() { self.buf_writable .push(Writable::Lit(match val.contains('\n') { true => "\n", false => " ", })); } } WhitespaceHandling::Suppress => {} } self.next_ws = None; } // Sets `skip_ws` to match the suffix whitespace suppressor from the given // argument, to determine whether to suppress leading whitespace from the // next literal. fn prepare_ws(&mut self, ws: Ws) { self.skip_ws = self.should_trim_ws(ws.1); } } #[derive(Debug)] struct Buffer { // The buffer to generate the code into buf: String, discard: bool, } impl Buffer { fn new() -> Self { Self { buf: String::new(), discard: false, } } fn writeln(&mut self, src: impl BufferFmt) { if !self.discard { src.append_to(&mut self.buf); self.buf.push('\n'); } } fn write(&mut self, src: impl BufferFmt) { if !self.discard { src.append_to(&mut self.buf); } } } trait BufferFmt { fn append_to(&self, buf: &mut String); } impl BufferFmt for &T { fn append_to(&self, buf: &mut String) { T::append_to(self, buf) } } impl BufferFmt for str { fn append_to(&self, buf: &mut String) { buf.push_str(self); } } impl BufferFmt for String { fn append_to(&self, buf: &mut String) { buf.push_str(self); } } impl BufferFmt for Arguments<'_> { fn append_to(&self, buf: &mut String) { buf.write_fmt(*self).unwrap(); } } struct SeparatedPath(I); impl + Copy, E: BufferFmt> BufferFmt for SeparatedPath { fn append_to(&self, buf: &mut String) { for (idx, item) in self.0.into_iter().enumerate() { if idx > 0 { buf.push_str("::"); } item.append_to(buf); } } } #[derive(Clone, Default)] pub(crate) struct LocalMeta { refs: Option, initialized: bool, } impl LocalMeta { fn initialized() -> Self { Self { refs: None, initialized: true, } } fn with_ref(refs: String) -> Self { Self { refs: Some(refs), initialized: true, } } } // type SetChain<'a, T> = MapChain<'a, T, ()>; #[derive(Debug, Clone)] pub(crate) struct MapChain<'a, K, V> where K: cmp::Eq + hash::Hash, { parent: Option<&'a MapChain<'a, K, V>>, scopes: Vec>, } impl<'a, K: 'a, V: 'a> MapChain<'a, K, V> where K: cmp::Eq + hash::Hash, { fn with_parent<'p>(parent: &'p MapChain<'_, K, V>) -> MapChain<'p, K, V> { MapChain { parent: Some(parent), scopes: vec![HashMap::new()], } } /// Iterates the scopes in reverse and returns `Some(LocalMeta)` /// from the first scope where `key` exists. fn get(&self, key: &K) -> Option<&V> { let mut scopes = self.scopes.iter().rev(); scopes .find_map(|set| set.get(key)) .or_else(|| self.parent.and_then(|set| set.get(key))) } fn is_current_empty(&self) -> bool { self.scopes.last().unwrap().is_empty() } fn insert(&mut self, key: K, val: V) { self.scopes.last_mut().unwrap().insert(key, val); // Note that if `insert` returns `Some` then it implies // an identifier is reused. For e.g. `{% macro f(a, a) %}` // and `{% let (a, a) = ... %}` then this results in a // generated template, which when compiled fails with the // compile error "identifier `a` used more than once". } fn insert_with_default(&mut self, key: K) where V: Default, { self.insert(key, V::default()); } fn push(&mut self) { self.scopes.push(HashMap::new()); } fn pop(&mut self) { self.scopes.pop().unwrap(); assert!(!self.scopes.is_empty()); } } impl MapChain<'_, Cow<'_, str>, LocalMeta> { fn resolve(&self, name: &str) -> Option { let name = normalize_identifier(name); self.get(&Cow::Borrowed(name)).map(|meta| match &meta.refs { Some(expr) => expr.clone(), None => name.to_string(), }) } fn resolve_or_self(&self, name: &str) -> String { let name = normalize_identifier(name); self.resolve(name).unwrap_or_else(|| format!("self.{name}")) } } impl<'a, K: Eq + hash::Hash, V> Default for MapChain<'a, K, V> { fn default() -> Self { Self { parent: None, scopes: vec![HashMap::new()], } } } /// Returns `true` if enough assumptions can be made, /// to determine that `self` is copyable. fn is_copyable(expr: &Expr<'_>) -> bool { is_copyable_within_op(expr, false) } fn is_copyable_within_op(expr: &Expr<'_>, within_op: bool) -> bool { match expr { Expr::BoolLit(_) | Expr::NumLit(_) | Expr::StrLit(_) | Expr::CharLit(_) => true, Expr::Unary(.., expr) => is_copyable_within_op(expr, true), Expr::BinOp(_, lhs, rhs) => { is_copyable_within_op(lhs, true) && is_copyable_within_op(rhs, true) } Expr::Range(..) => true, // The result of a call likely doesn't need to be borrowed, // as in that case the call is more likely to return a // reference in the first place then. Expr::Call(..) | Expr::Path(..) | Expr::Filter(..) => true, // If the `expr` is within a `Unary` or `BinOp` then // an assumption can be made that the operand is copy. // If not, then the value is moved and adding `.clone()` // will solve that issue. However, if the operand is // implicitly borrowed, then it's likely not even possible // to get the template to compile. _ => within_op && is_attr_self(expr), } } /// Returns `true` if this is an `Attr` where the `obj` is `"self"`. pub(crate) fn is_attr_self(expr: &Expr<'_>) -> bool { match expr { Expr::Attr(obj, _) if matches!(***obj, Expr::Var("self")) => true, Expr::Attr(obj, _) if matches!(***obj, Expr::Attr(..)) => is_attr_self(obj), _ => false, } } /// Returns `true` if the outcome of this expression may be used multiple times in the same /// `write!()` call, without evaluating the expression again, i.e. the expression should be /// side-effect free. pub(crate) fn is_cacheable(expr: &WithSpan<'_, Expr<'_>>) -> bool { match &**expr { // Literals are the definition of pure: Expr::BoolLit(_) => true, Expr::NumLit(_) => true, Expr::StrLit(_) => true, Expr::CharLit(_) => true, // fmt::Display should have no effects: Expr::Var(_) => true, Expr::Path(_) => true, // Check recursively: Expr::Array(args) => args.iter().all(is_cacheable), Expr::Attr(lhs, _) => is_cacheable(lhs), Expr::Index(lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs), Expr::Filter(Filter { arguments, .. }) => arguments.iter().all(is_cacheable), Expr::Unary(_, arg) => is_cacheable(arg), Expr::BinOp(_, lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs), Expr::Range(_, lhs, rhs) => { lhs.as_ref().map_or(true, |v| is_cacheable(v)) && rhs.as_ref().map_or(true, |v| is_cacheable(v)) } Expr::Group(arg) => is_cacheable(arg), Expr::Tuple(args) => args.iter().all(is_cacheable), Expr::NamedArgument(_, expr) => is_cacheable(expr), // We have too little information to tell if the expression is pure: Expr::Call(_, _) => false, Expr::RustMacro(_, _) => false, Expr::Try(_) => false, Expr::Generated(_) => true, } } fn median(sizes: &mut [usize]) -> usize { sizes.sort_unstable(); if sizes.len() % 2 == 1 { sizes[sizes.len() / 2] } else { (sizes[sizes.len() / 2 - 1] + sizes[sizes.len() / 2]) / 2 } } /// In `FilterBlock`, we have a recursive `Expr::Filter` entry, where the more you go "down", /// the sooner you are called in the Rust code. Example: /// /// ```text /// {% filter a|b|c %}bla{% endfilter %} /// ``` /// /// Will be translated as: /// /// ```text /// FilterBlock { /// filters: Filter { /// name: "c", /// arguments: vec![ /// Filter { /// name: "b", /// arguments: vec![ /// Filter { /// name: "a", /// arguments: vec![], /// }. /// ], /// } /// ], /// }, /// // ... /// } /// ``` /// /// So in here, we want to insert the variable containing the content of the filter block inside /// the call to `"a"`. To do so, we recursively go through all `Filter` and finally insert our /// variable as the first argument to the `"a"` call. fn insert_first_filter_argument(args: &mut Vec>>, var_name: String) { if let Some(expr) = args.first_mut() { if let Expr::Filter(Filter { ref mut arguments, .. }) = **expr { insert_first_filter_argument(arguments, var_name); return; } } args.insert(0, WithSpan::new(Expr::Generated(var_name), "")); } #[derive(Clone, Copy, PartialEq)] enum AstLevel { Top, Block, Nested, } #[derive(Clone, Copy, Debug)] enum DisplayWrap { Wrapped, Unwrapped, } #[derive(Default, Debug)] struct WritableBuffer<'a> { buf: Vec>, discard: bool, } impl<'a> WritableBuffer<'a> { fn push(&mut self, writable: Writable<'a>) { if !self.discard { self.buf.push(writable); } } } impl<'a> Deref for WritableBuffer<'a> { type Target = [Writable<'a>]; fn deref(&self) -> &Self::Target { &self.buf[..] } } #[derive(Debug)] enum Writable<'a> { Lit(&'a str), Expr(&'a WithSpan<'a, Expr<'a>>), Generated(String, DisplayWrap), } struct WriteParts { size_hint: usize, buffers: Option, } /// If "expr" is `None`, it means we can generate code like this: /// /// ```ignore /// let var = format; /// ``` /// /// Otherwise we need to format "expr" using "format": /// /// ```ignore /// let var = format!(format, expr); /// ``` #[derive(Debug)] struct WritePartsBuffers { format: Buffer, expr: Option, } /// Identifiers to be replaced with raw identifiers, so as to avoid /// collisions between template syntax and Rust's syntax. In particular /// [Rust keywords](https://doc.rust-lang.org/reference/keywords.html) /// should be replaced, since they're not reserved words in Rinja /// syntax but have a high probability of causing problems in the /// generated code. /// /// This list excludes the Rust keywords *self*, *Self*, and *super* /// because they are not allowed to be raw identifiers, and *loop* /// because it's used something like a keyword in the template /// language. fn normalize_identifier(ident: &str) -> &str { // This table works for as long as the replacement string is the original string // prepended with "r#". The strings get right-padded to the same length with b'_'. // While the code does not need it, please keep the list sorted when adding new // keywords. if ident.len() > parser::node::MAX_KW_LEN { return ident; } let kws = parser::node::KWS[ident.len()]; let mut padded_ident = [b'_'; parser::node::MAX_KW_LEN]; padded_ident[..ident.len()].copy_from_slice(ident.as_bytes()); // Since the individual buckets are quite short, a linear search is faster than a binary search. let replacement = match kws.iter().find(|probe| { padded_ident == <[u8; parser::node::MAX_KW_LEN]>::try_from(&probe[2..]).unwrap() }) { Some(replacement) => replacement, None => return ident, }; // SAFETY: We know that the input byte slice is pure-ASCII. unsafe { std::str::from_utf8_unchecked(&replacement[..ident.len() + 2]) } }