derive: don't use format_args! for code formatting

* No need to manually escape raw identifiers
* Allow a few warnings
This commit is contained in:
René Kijewski 2025-08-06 06:25:59 +02:00 committed by René Kijewski
parent 7dbbe397f9
commit 693f86d1c4
13 changed files with 660 additions and 611 deletions

View File

@ -0,0 +1 @@
../../askama/src/ascii_str.rs

View File

@ -12,19 +12,18 @@ use std::str;
use std::sync::Arc; use std::sync::Arc;
use parser::node::{Call, Macro, Whitespace}; use parser::node::{Call, Macro, Whitespace};
use parser::{ use parser::{CharLit, Expr, FloatKind, IntKind, Num, StrLit, WithSpan};
CharLit, Expr, FloatKind, IntKind, MAX_RUST_KEYWORD_LEN, Num, RUST_KEYWORDS, StrLit, WithSpan,
};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::ToTokens;
use rustc_hash::FxBuildHasher; use rustc_hash::FxBuildHasher;
use syn::Token;
use crate::ascii_str::{AsciiChar, AsciiStr};
use crate::generator::helpers::{clean_path, diff_paths}; use crate::generator::helpers::{clean_path, diff_paths};
use crate::heritage::{Context, Heritage}; use crate::heritage::{Context, Heritage};
use crate::html::write_escaped_str; use crate::html::write_escaped_str;
use crate::input::{Source, TemplateInput}; use crate::input::{Source, TemplateInput};
use crate::integration::{Buffer, impl_everything, write_header}; use crate::integration::{Buffer, impl_everything, write_header};
use crate::{CompileError, FileInfo}; use crate::{CompileError, FileInfo, field_new};
pub(crate) fn template_to_string( pub(crate) fn template_to_string(
buf: &mut Buffer, buf: &mut Buffer,
@ -170,16 +169,12 @@ impl<'a, 'h> Generator<'a, 'h> {
) -> Result<usize, CompileError> { ) -> Result<usize, CompileError> {
let ctx = &self.contexts[&self.input.path]; let ctx = &self.contexts[&self.input.path];
let span = proc_macro2::Span::call_site(); let span = self.input.ast.ident.span();
let target = match tmpl_kind { let target = match tmpl_kind {
TmplKind::Struct => spanned!(span=> askama::Template), TmplKind::Struct => spanned!(span=> askama::Template),
TmplKind::Variant => spanned!(span=> askama::helpers::EnumVariantTemplate), TmplKind::Variant => spanned!(span=> askama::helpers::EnumVariantTemplate),
TmplKind::Block(trait_name) => { TmplKind::Block(trait_name) => field_new(trait_name, span),
let trait_name = proc_macro2::Ident::new(trait_name, span);
quote::quote!(#trait_name)
}
}; };
write_header(self.input.ast, buf, target, span);
let mut full_paths = TokenStream::new(); let mut full_paths = TokenStream::new();
if let Some(full_config_path) = &self.input.config.full_config_path { if let Some(full_config_path) = &self.input.config.full_config_path {
@ -227,15 +222,17 @@ impl<'a, 'h> Generator<'a, 'h> {
); );
} }
buf.write_tokens(spanned!(span=> write_header(self.input.ast, buf, target, span);
{ let var_writer = crate::var_writer();
let var_values = crate::var_values();
quote_into!(buf, span, { {
fn render_into_with_values<AskamaW>( fn render_into_with_values<AskamaW>(
&self, &self,
__askama_writer: &mut AskamaW, #var_writer: &mut AskamaW,
__askama_values: &dyn askama::Values #var_values: &dyn askama::Values,
) -> askama::Result<()> ) -> askama::Result<()>
where where
AskamaW: askama::helpers::core::fmt::Write + ?askama::helpers::core::marker::Sized AskamaW: askama::helpers::core::fmt::Write + ?askama::helpers::core::marker::Sized,
{ {
#[allow(unused_imports)] #[allow(unused_imports)]
use askama::{ use askama::{
@ -249,8 +246,7 @@ impl<'a, 'h> Generator<'a, 'h> {
askama::Result::Ok(()) askama::Result::Ok(())
} }
#size_hint_s #size_hint_s
} } });
));
#[cfg(feature = "blocks")] #[cfg(feature = "blocks")]
for block in self.input.blocks { for block in self.input.blocks {
@ -688,7 +684,6 @@ impl<'a> MapChain<'a> {
} }
fn resolve(&self, name: &str) -> Option<String> { fn resolve(&self, name: &str) -> Option<String> {
let name = normalize_identifier(name);
self.get(&Cow::Borrowed(name)).map(|meta| match &meta.refs { self.get(&Cow::Borrowed(name)).map(|meta| match &meta.refs {
Some(expr) => expr.clone(), Some(expr) => expr.clone(),
None => name.to_string(), None => name.to_string(),
@ -696,7 +691,6 @@ impl<'a> MapChain<'a> {
} }
fn resolve_or_self(&self, name: &str) -> String { fn resolve_or_self(&self, name: &str) -> String {
let name = normalize_identifier(name);
self.resolve(name).unwrap_or_else(|| format!("self.{name}")) self.resolve(name).unwrap_or_else(|| format!("self.{name}"))
} }
@ -759,8 +753,6 @@ fn is_associated_item_self(mut expr: &Expr<'_>) -> bool {
} }
} }
const FILTER_SOURCE: &str = "__askama_filter_block";
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
enum DisplayWrap { enum DisplayWrap {
Wrapped, Wrapped,
@ -796,36 +788,35 @@ enum Writable<'a> {
Expr(&'a WithSpan<'a, Box<Expr<'a>>>), Expr(&'a WithSpan<'a, Box<Expr<'a>>>),
} }
/// Identifiers to be replaced with raw identifiers, so as to avoid macro_rules! make_token_match {
/// collisions between template syntax and Rust's syntax. In particular ($op:ident @ $span:ident => $($tt:tt)+) => {
/// [Rust keywords](https://doc.rust-lang.org/reference/keywords.html) match $op {
/// should be replaced, since they're not reserved words in Askama $(stringify!($tt) => Token![$tt]($span).into_token_stream(),)+
/// syntax but have a high probability of causing problems in the _ => unreachable!(),
/// 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() > MAX_RUST_KEYWORD_LEN {
return ident;
}
let kws = RUST_KEYWORDS[ident.len()];
let mut padded_ident = [0; MAX_RUST_KEYWORD_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.
for probe in kws {
if padded_ident == *AsciiChar::slice_as_bytes(probe[2..].try_into().unwrap()) {
return AsciiStr::from_slice(&probe[..ident.len() + 2]);
} }
} };
ident }
#[inline]
#[track_caller]
fn logic_op(op: &str, span: proc_macro2::Span) -> TokenStream {
make_token_match!(op @ span => && || ^)
}
#[inline]
#[track_caller]
fn unary_op(op: &str, span: proc_macro2::Span) -> TokenStream {
make_token_match!(op @ span => - ! * &)
}
#[inline]
#[track_caller]
fn range_op(op: &str, span: proc_macro2::Span) -> TokenStream {
make_token_match!(op @ span => .. ..=)
}
#[inline]
#[track_caller]
fn binary_op(op: &str, span: proc_macro2::Span) -> TokenStream {
make_token_match!(op @ span => * / % + - << >> & ^ | == != < > <= >= && || .. ..=)
} }

View File

@ -3,19 +3,20 @@ use std::str::FromStr;
use parser::node::CondTest; use parser::node::CondTest;
use parser::{ use parser::{
AssociatedItem, CharLit, CharPrefix, Expr, PathComponent, Span, StrLit, Target, TyGenerics, AssociatedItem, CharLit, CharPrefix, Expr, PathComponent, Span, StrLit, StrPrefix, Target,
WithSpan, TyGenerics, WithSpan,
}; };
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::{quote, quote_spanned};
use syn::Token;
use super::{ use super::{
DisplayWrap, FILTER_SOURCE, Generator, LocalMeta, Writable, compile_time_escape, is_copyable, DisplayWrap, Generator, LocalMeta, Writable, binary_op, compile_time_escape, is_copyable,
normalize_identifier, logic_op, range_op, unary_op,
}; };
use crate::CompileError;
use crate::heritage::Context; use crate::heritage::Context;
use crate::integration::Buffer; use crate::integration::Buffer;
use crate::{CompileError, field_new};
impl<'a> Generator<'a, '_> { impl<'a> Generator<'a, '_> {
pub(crate) fn visit_expr_root( pub(crate) fn visit_expr_root(
@ -35,42 +36,26 @@ impl<'a> Generator<'a, '_> {
iter: &WithSpan<'a, Box<Expr<'a>>>, iter: &WithSpan<'a, Box<Expr<'a>>>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
let expr_code = self.visit_expr_root(ctx, iter)?; let expr_code = self.visit_expr_root(ctx, iter)?;
match &***iter { let span = ctx.span_for_node(iter.span());
Expr::Range(..) => buf.write(expr_code, ctx.span_for_node(iter.span())), buf.write_tokens(match &***iter {
Expr::Array(..) => buf.write( Expr::Range(..) => expr_code,
format_args!("{expr_code}.iter()"), Expr::Array(..) => quote_spanned!(span => #expr_code.iter()),
ctx.span_for_node(iter.span()),
),
// If `iter` is a call then we assume it's something that returns // 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 // an iterator. If not then the user can explicitly add the needed
// call without issues. // call without issues.
Expr::Call { .. } | Expr::Index(..) => { Expr::Call { .. } | Expr::Index(..) => quote_spanned!(span => (#expr_code).into_iter()),
buf.write(
format_args!("({expr_code}).into_iter()"),
ctx.span_for_node(iter.span()),
);
}
// If accessing `self` then it most likely needs to be // If accessing `self` then it most likely needs to be
// borrowed, to prevent an attempt of moving. // borrowed, to prevent an attempt of moving.
// FIXME: Remove this `to_string()` call, it's terrible performance-wise. // FIXME: Remove this `to_string()` call, it's terrible performance-wise.
_ if expr_code.to_string().trim_start().starts_with("self.") => { _ if expr_code.to_string().trim_start().starts_with("self.") => {
buf.write( quote_spanned!(span => (&#expr_code).into_iter())
format_args!("(&{expr_code}).into_iter()"),
ctx.span_for_node(iter.span()),
);
} }
// If accessing a field then it most likely needs to be // If accessing a field then it most likely needs to be
// borrowed, to prevent an attempt of moving. // borrowed, to prevent an attempt of moving.
Expr::AssociatedItem(..) => buf.write( Expr::AssociatedItem(..) => quote_spanned!(span => (&#expr_code).into_iter()),
format_args!("(&{expr_code}).into_iter()"),
ctx.span_for_node(iter.span()),
),
// Otherwise, we borrow `iter` assuming that it implements `IntoIterator`. // Otherwise, we borrow `iter` assuming that it implements `IntoIterator`.
_ => buf.write( _ => quote_spanned!(span => (#expr_code).into_iter()),
format_args!("({expr_code}).into_iter()"), });
ctx.span_for_node(iter.span()),
),
}
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
@ -138,11 +123,11 @@ impl<'a> Generator<'a, '_> {
match ***expr { match ***expr {
Expr::BinOp(ref v) if matches!(v.op, "&&" | "||") => { Expr::BinOp(ref v) if matches!(v.op, "&&" | "||") => {
let ret = self.visit_expr(ctx, buf, &v.lhs)?; let ret = self.visit_expr(ctx, buf, &v.lhs)?;
buf.write(format_args!(" {} ", &v.op), ctx.span_for_node(expr.span())); buf.write_tokens(logic_op(v.op, ctx.span_for_node(expr.span())));
return Ok(ret); return Ok(ret);
} }
Expr::Unary(op, ref inner) => { Expr::Unary(op, ref inner) => {
buf.write(op, ctx.span_for_node(expr.span())); buf.write_tokens(unary_op(op, ctx.span_for_node(expr.span())));
return self.visit_expr_first(ctx, buf, inner); return self.visit_expr_first(ctx, buf, inner);
} }
_ => {} _ => {}
@ -180,12 +165,12 @@ impl<'a> Generator<'a, '_> {
self.visit_expr(ctx, buf, expr)?; self.visit_expr(ctx, buf, expr)?;
} }
Expr::Unary("!", expr) => { Expr::Unary("!", expr) => {
buf.write('!', ctx.span_for_node(expr.span())); buf.write_token(Token![!], ctx.span_for_node(expr.span()));
self.visit_condition(ctx, buf, expr)?; self.visit_condition(ctx, buf, expr)?;
} }
Expr::BinOp(v) if matches!(v.op, "&&" | "||") => { Expr::BinOp(v) if matches!(v.op, "&&" | "||") => {
self.visit_condition(ctx, buf, &v.lhs)?; self.visit_condition(ctx, buf, &v.lhs)?;
buf.write(format_args!(" {} ", v.op), ctx.span_for_node(expr.span())); buf.write_tokens(logic_op(v.op, ctx.span_for_node(expr.span())));
self.visit_condition(ctx, buf, &v.rhs)?; self.visit_condition(ctx, buf, &v.rhs)?;
} }
Expr::Group(expr) => { Expr::Group(expr) => {
@ -237,10 +222,12 @@ impl<'a> Generator<'a, '_> {
self.visit_expr(ctx, &mut tmp, expr)?; self.visit_expr(ctx, &mut tmp, expr)?;
let tmp = tmp.into_token_stream(); let tmp = tmp.into_token_stream();
let span = ctx.span_for_node(expr.span()); let span = ctx.span_for_node(expr.span());
let target = proc_macro2::Ident::new(target, span); let target = field_new(target, span);
buf.write_tokens(spanned!( quote_into!(
span=> askama::helpers::get_primitive_value(&(#tmp)) as askama::helpers::core::primitive::#target buf,
)); span,
{ askama::helpers::get_primitive_value(&(#tmp)) as askama::helpers::core::primitive::#target }
);
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
@ -255,7 +242,6 @@ impl<'a> Generator<'a, '_> {
[expr] => self.visit_expr(ctx, buf, expr), [expr] => self.visit_expr(ctx, buf, expr),
exprs => { exprs => {
let (l, r) = exprs.split_at(exprs.len().div_ceil(2)); let (l, r) = exprs.split_at(exprs.len().div_ceil(2));
// FIXME: Is this valid?
let span = ctx.span_for_node(l[0].span()); let span = ctx.span_for_node(l[0].span());
let mut buf_l = Buffer::new(); let mut buf_l = Buffer::new();
let mut buf_r = Buffer::new(); let mut buf_r = Buffer::new();
@ -281,7 +267,7 @@ impl<'a> Generator<'a, '_> {
let display_wrap = self.visit_expr_first(ctx, &mut expr_buf, &cond.expr)?; let display_wrap = self.visit_expr_first(ctx, &mut expr_buf, &cond.expr)?;
let expr_buf = expr_buf.into_token_stream(); let expr_buf = expr_buf.into_token_stream();
let span = ctx.span_for_node(cond.span()); let span = ctx.span_for_node(cond.span());
buf.write(" let ", span); buf.write_token(Token![let], span);
if let Some(ref target) = cond.target { if let Some(ref target) = cond.target {
self.visit_target(ctx, buf, true, true, target); self.visit_target(ctx, buf, true, true, target);
} }
@ -317,15 +303,12 @@ impl<'a> Generator<'a, '_> {
let [path @ .., name] = path else { let [path @ .., name] = path else {
unreachable!("path cannot be empty"); unreachable!("path cannot be empty");
}; };
let name = match normalize_identifier(name) {
"loop" => quote::format_ident!("r#loop"),
name => quote::format_ident!("{}", name),
};
let span = ctx.span_for_node(node); let span = ctx.span_for_node(node);
let name = field_new(name, span);
if !path.is_empty() { if !path.is_empty() {
self.visit_macro_path(buf, path, span); self.visit_macro_path(buf, path, span);
buf.write("::", span); buf.write_token(Token![::], span);
} }
let args = TokenStream::from_str(args).unwrap(); let args = TokenStream::from_str(args).unwrap();
buf.write_tokens(spanned!(span=> #name !(#args))); buf.write_tokens(spanned!(span=> #name !(#args)));
@ -365,8 +348,9 @@ impl<'a> Generator<'a, '_> {
let args = self.visit_arg(ctx, key, span)?; let args = self.visit_arg(ctx, key, span)?;
let ty_generics = ty_generics.into_token_stream(); let ty_generics = ty_generics.into_token_stream();
let var_values = crate::var_values();
buf.write_tokens(spanned!(span=> askama::helpers::get_value::<#ty_generics>( buf.write_tokens(spanned!(span=> askama::helpers::get_value::<#ty_generics>(
&__askama_values, &(#args) &#var_values, &(#args)
))); )));
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
@ -380,7 +364,7 @@ impl<'a> Generator<'a, '_> {
for (i, arg) in args.iter().enumerate() { for (i, arg) in args.iter().enumerate() {
let span = ctx.span_for_node(arg.span()); let span = ctx.span_for_node(arg.span());
if i > 0 { if i > 0 {
buf.write(',', span); buf.write_token(Token![,], span);
} }
buf.write(self.visit_arg(ctx, arg, span)?, ctx.template_span); buf.write(self.visit_arg(ctx, arg, span)?, ctx.template_span);
} }
@ -444,15 +428,16 @@ impl<'a> Generator<'a, '_> {
let tmp = tmp.into_token_stream(); let tmp = tmp.into_token_stream();
buf.write_tokens(spanned!(span=> askama::filters::Safe(#tmp))); buf.write_tokens(spanned!(span=> askama::filters::Safe(#tmp)));
} else { } else {
buf.write("askama::helpers::Empty", span); quote_into!(buf, span, { askama::helpers::Empty });
} }
} else { } else {
let arg = self.visit_arg(ctx, arg, span)?; let arg = self.visit_arg(ctx, arg, span)?;
let escaper = TokenStream::from_str(self.input.escaper).unwrap(); let escaper = TokenStream::from_str(self.input.escaper).unwrap();
buf.write_tokens(spanned!(span=> ( quote_into!(
&&askama::filters::AutoEscaper::new(#arg, #escaper) buf,
).askama_auto_escape()? span,
)); { (&&askama::filters::AutoEscaper::new(#arg, #escaper)).askama_auto_escape()? }
);
} }
Ok(()) Ok(())
} }
@ -466,28 +451,29 @@ impl<'a> Generator<'a, '_> {
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
let span = ctx.span_for_node(obj.span()); let span = ctx.span_for_node(obj.span());
if let Expr::Var("loop") = ***obj { if let Expr::Var("loop") = ***obj {
buf.write( let var_item = crate::var_item();
match associated_item.name { buf.write_tokens(match associated_item.name {
"index0" => "__askama_item.index0", "index0" => quote_spanned!(span => #var_item.index0),
"index" => "(__askama_item.index0 + 1)", "index" => quote_spanned!(span => (#var_item.index0 + 1)),
"first" => "(__askama_item.index0 == 0)", "first" => quote_spanned!(span => (#var_item.index0 == 0)),
"last" => "__askama_item.last", "last" => quote_spanned!(span => #var_item.last),
name => { name => {
return Err(ctx.generate_error( return Err(ctx.generate_error(
format!("unknown loop variable `{}`", name.escape_debug()), format!("unknown loop variable `{}`", name.escape_debug()),
obj.span(), obj.span(),
)); ));
} }
}, });
span,
);
return Ok(DisplayWrap::Unwrapped); return Ok(DisplayWrap::Unwrapped);
} }
let mut expr = Buffer::new(); let mut expr = Buffer::new();
self.visit_expr(ctx, &mut expr, obj)?; self.visit_expr(ctx, &mut expr, obj)?;
let expr = expr.into_token_stream(); let expr = expr.into_token_stream();
let identifier = TokenStream::from_str(normalize_identifier(associated_item.name)).unwrap(); let identifier = field_new(
associated_item.name,
ctx.span_for_node(Span::from(associated_item.name)),
);
let mut call_generics = Buffer::new(); let mut call_generics = Buffer::new();
self.visit_call_generics(ctx, &mut call_generics, &associated_item.generics); self.visit_call_generics(ctx, &mut call_generics, &associated_item.generics);
let call_generics = call_generics.into_token_stream(); let call_generics = call_generics.into_token_stream();
@ -503,7 +489,7 @@ impl<'a> Generator<'a, '_> {
generics: &[WithSpan<'a, TyGenerics<'a>>], generics: &[WithSpan<'a, TyGenerics<'a>>],
) { ) {
if let Some(first) = generics.first() { if let Some(first) = generics.first() {
buf.write("::", ctx.span_for_node(first.span())); buf.write_token(Token![::], ctx.span_for_node(first.span()));
self.visit_ty_generics(ctx, buf, generics); self.visit_ty_generics(ctx, buf, generics);
} }
} }
@ -521,7 +507,7 @@ impl<'a> Generator<'a, '_> {
for generic in generics { for generic in generics {
let span = ctx.span_for_node(generic.span()); let span = ctx.span_for_node(generic.span());
self.visit_ty_generic(ctx, &mut tmp, generic, span); self.visit_ty_generic(ctx, &mut tmp, generic, span);
tmp.write(',', span); tmp.write_token(Token![,], span);
} }
let tmp = tmp.into_token_stream(); let tmp = tmp.into_token_stream();
// FIXME: use a better span // FIXME: use a better span
@ -535,12 +521,14 @@ impl<'a> Generator<'a, '_> {
generic: &WithSpan<'a, TyGenerics<'a>>, generic: &WithSpan<'a, TyGenerics<'a>>,
span: proc_macro2::Span, span: proc_macro2::Span,
) { ) {
let TyGenerics { refs, path, args } = &**generic; let TyGenerics {
let mut refs_s = String::new(); refs,
for _ in 0..*refs { ref path,
refs_s.push('&'); ref args,
} = **generic;
for _ in 0..refs {
buf.write_token(Token![&], span);
} }
buf.write(refs_s, span);
self.visit_macro_path(buf, path, span); self.visit_macro_path(buf, path, span);
self.visit_ty_generics(ctx, buf, args); self.visit_ty_generics(ctx, buf, args);
} }
@ -552,7 +540,7 @@ impl<'a> Generator<'a, '_> {
obj: &WithSpan<'a, Box<Expr<'a>>>, obj: &WithSpan<'a, Box<Expr<'a>>>,
key: &WithSpan<'a, Box<Expr<'a>>>, key: &WithSpan<'a, Box<Expr<'a>>>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
buf.write('&', ctx.span_for_node(obj.span())); buf.write_token(Token![&], ctx.span_for_node(obj.span()));
self.visit_expr(ctx, buf, obj)?; self.visit_expr(ctx, buf, obj)?;
let key_span = ctx.span_for_node(key.span()); let key_span = ctx.span_for_node(key.span());
@ -605,14 +593,17 @@ impl<'a> Generator<'a, '_> {
let expr_buf = expr_buf.into_token_stream(); let expr_buf = expr_buf.into_token_stream();
let arg_span = ctx.span_for_node(arg.span()); let arg_span = ctx.span_for_node(arg.span());
let var_cycle = crate::var_cycle();
let var_item = crate::var_item();
let var_len = crate::var_len();
buf.write_tokens( buf.write_tokens(
spanned!(arg_span=> ({ spanned!(arg_span=> ({
let _cycle = &(#expr_buf); let #var_cycle = &(#expr_buf);
let __askama_len = _cycle.len(); let #var_len = #var_cycle.len();
if __askama_len == 0 { if #var_len == 0 {
return askama::helpers::core::result::Result::Err(askama::Error::Fmt); return askama::helpers::core::result::Result::Err(askama::Error::Fmt);
} }
_cycle[__askama_item.index0 % __askama_len] #var_cycle[#var_item.index0 % #var_len]
})), })),
); );
} }
@ -632,26 +623,31 @@ impl<'a> Generator<'a, '_> {
} }
} }
} }
// We special-case "askama::get_value".
Expr::Path(path) if matches!(path.as_slice(), [part1, part2] if part1.generics.is_empty() && part1.name == "askama" && part2.name == "get_value") =>
{
self.visit_value(
ctx,
buf,
args,
// Generics of the `get_value` call.
&path[1].generics,
left.span(),
"`get_value` function",
)?;
}
sub_left => { sub_left => {
// We special-case "askama::get_value".
if let Expr::Path(path) = sub_left
&& let [part1, part2] = path.as_slice()
&& part1.generics.is_empty()
&& part1.name == "askama"
&& part2.name == "get_value"
{
return self.visit_value(
ctx,
buf,
args,
&part2.generics,
left.span(),
"`get_value` function",
);
}
let span = ctx.span_for_node(left.span()); let span = ctx.span_for_node(left.span());
match *sub_left { match *sub_left {
Expr::Var(name) => match self.locals.resolve(name) { Expr::Var(name) => match self.locals.resolve(name) {
Some(resolved) => buf.write(resolved, span), Some(resolved) => write_resolved(buf, &resolved, span),
None => { None => {
buf.write(format_args!("self.{}", normalize_identifier(name)), span) let id = field_new(name, span);
quote_into!(buf, span, { self.#id });
} }
}, },
_ => { _ => {
@ -675,7 +671,7 @@ impl<'a> Generator<'a, '_> {
inner: &WithSpan<'a, Box<Expr<'a>>>, inner: &WithSpan<'a, Box<Expr<'a>>>,
span: Span<'_>, span: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
buf.write(op, ctx.span_for_node(span)); buf.write_tokens(unary_op(op, ctx.span_for_node(span)));
self.visit_expr(ctx, buf, inner)?; self.visit_expr(ctx, buf, inner)?;
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
@ -692,7 +688,7 @@ impl<'a> Generator<'a, '_> {
if let Some(left) = left { if let Some(left) = left {
self.visit_expr(ctx, buf, left)?; self.visit_expr(ctx, buf, left)?;
} }
buf.write(op, ctx.span_for_node(span)); buf.write_tokens(range_op(op, ctx.span_for_node(span)));
if let Some(right) = right { if let Some(right) = right {
self.visit_expr(ctx, buf, right)?; self.visit_expr(ctx, buf, right)?;
} }
@ -709,7 +705,7 @@ impl<'a> Generator<'a, '_> {
span: Span<'_>, span: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
self.visit_expr(ctx, buf, left)?; self.visit_expr(ctx, buf, left)?;
buf.write(format_args!(" {op} "), ctx.span_for_node(span)); buf.write_tokens(binary_op(op, ctx.span_for_node(span)));
self.visit_expr(ctx, buf, right)?; self.visit_expr(ctx, buf, right)?;
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
@ -742,7 +738,7 @@ impl<'a> Generator<'a, '_> {
let mut tmp = Buffer::new(); let mut tmp = Buffer::new();
for expr in exprs { for expr in exprs {
self.visit_expr(ctx, &mut tmp, expr)?; self.visit_expr(ctx, &mut tmp, expr)?;
tmp.write(',', span); tmp.write_token(Token![,], span);
} }
let tmp = tmp.into_token_stream(); let tmp = tmp.into_token_stream();
buf.write_tokens(spanned!(span=> (#tmp))); buf.write_tokens(spanned!(span=> (#tmp)));
@ -771,7 +767,7 @@ impl<'a> Generator<'a, '_> {
let mut tmp = Buffer::new(); let mut tmp = Buffer::new();
for el in elements { for el in elements {
self.visit_expr(ctx, &mut tmp, el)?; self.visit_expr(ctx, &mut tmp, el)?;
tmp.write(',', span); tmp.write_token(Token![,], span);
} }
let tmp = tmp.into_token_stream(); let tmp = tmp.into_token_stream();
buf.write_tokens(spanned!(span=> [#tmp])); buf.write_tokens(spanned!(span=> [#tmp]));
@ -786,7 +782,7 @@ impl<'a> Generator<'a, '_> {
) { ) {
for (i, part) in path.iter().copied().enumerate() { for (i, part) in path.iter().copied().enumerate() {
if i > 0 { if i > 0 {
buf.write("::", span); buf.write_token(Token![::], span);
} else if let Some(enum_ast) = self.input.enum_ast } else if let Some(enum_ast) = self.input.enum_ast
&& part == "Self" && part == "Self"
{ {
@ -796,7 +792,7 @@ impl<'a> Generator<'a, '_> {
buf.write_tokens(spanned!(span=> #this #generics)); buf.write_tokens(spanned!(span=> #this #generics));
continue; continue;
} }
buf.write(part, span); buf.write_field(part, span);
} }
} }
@ -809,7 +805,7 @@ impl<'a> Generator<'a, '_> {
for (i, part) in path.iter().enumerate() { for (i, part) in path.iter().enumerate() {
let span = ctx.span_for_node(part.span()); let span = ctx.span_for_node(part.span());
if i > 0 { if i > 0 {
buf.write("::", span); buf.write_token(Token![::], span);
} else if let Some(enum_ast) = self.input.enum_ast } else if let Some(enum_ast) = self.input.enum_ast
&& part.name == "Self" && part.name == "Self"
{ {
@ -819,9 +815,11 @@ impl<'a> Generator<'a, '_> {
buf.write_tokens(spanned!(span=> #this #generics)); buf.write_tokens(spanned!(span=> #this #generics));
continue; continue;
} }
buf.write(part.name, span); if !part.name.is_empty() {
buf.write_field(part.name, span);
}
if !part.generics.is_empty() { if !part.generics.is_empty() {
buf.write("::", span); buf.write_token(Token![::], span);
self.visit_ty_generics(ctx, buf, &part.generics); self.visit_ty_generics(ctx, buf, &part.generics);
} }
} }
@ -837,11 +835,10 @@ impl<'a> Generator<'a, '_> {
) -> DisplayWrap { ) -> DisplayWrap {
let span = ctx.span_for_node(node); let span = ctx.span_for_node(node);
if s == "self" { if s == "self" {
buf.write(s, span); quote_into!(buf, span, { self });
return DisplayWrap::Unwrapped; } else {
write_resolved(buf, &self.locals.resolve_or_self(s), span);
} }
buf.write(normalize_identifier(&self.locals.resolve_or_self(s)), span);
DisplayWrap::Unwrapped DisplayWrap::Unwrapped
} }
@ -853,10 +850,9 @@ impl<'a> Generator<'a, '_> {
) -> DisplayWrap { ) -> DisplayWrap {
// We can assume that the body of the `{% filter %}` was already escaped. // We can assume that the body of the `{% filter %}` was already escaped.
// And if it's not, then this was done intentionally. // And if it's not, then this was done intentionally.
buf.write( let span = ctx.span_for_node(node);
format_args!("askama::filters::Safe(&{FILTER_SOURCE})"), let id = crate::var_filter_source();
ctx.span_for_node(node), quote_into!(buf, span, { askama::filters::Safe(&#id) });
);
DisplayWrap::Wrapped DisplayWrap::Wrapped
} }
@ -869,9 +865,9 @@ impl<'a> Generator<'a, '_> {
) -> DisplayWrap { ) -> DisplayWrap {
let span = ctx.span_for_node(node); let span = ctx.span_for_node(node);
if s { if s {
buf.write("true", span); quote_into!(buf, span, { true });
} else { } else {
buf.write("false", span); quote_into!(buf, span, { false });
} }
DisplayWrap::Unwrapped DisplayWrap::Unwrapped
} }
@ -879,35 +875,36 @@ impl<'a> Generator<'a, '_> {
pub(super) fn visit_str_lit( pub(super) fn visit_str_lit(
&mut self, &mut self,
buf: &mut Buffer, buf: &mut Buffer,
StrLit { &StrLit {
content, prefix, .. content, prefix, ..
}: &StrLit<'_>, }: &StrLit<'_>,
span: proc_macro2::Span, span: proc_macro2::Span,
) -> DisplayWrap { ) -> DisplayWrap {
if let Some(prefix) = prefix { let repr = match prefix {
buf.write(format!("{}\"{content}\"", prefix.to_char()), span); Some(StrPrefix::Binary) => format!(r#"b"{content}""#),
} else { Some(StrPrefix::CLike) => format!(r#"c"{content}""#),
buf.write(format!("\"{content}\""), span); None => format!(r#""{content}""#),
} };
buf.write_literal(&repr, span);
DisplayWrap::Unwrapped DisplayWrap::Unwrapped
} }
fn visit_char_lit( fn visit_char_lit(
&mut self, &mut self,
buf: &mut Buffer, buf: &mut Buffer,
c: &CharLit<'_>, &CharLit { prefix, content }: &CharLit<'_>,
span: proc_macro2::Span, span: proc_macro2::Span,
) -> DisplayWrap { ) -> DisplayWrap {
if c.prefix == Some(CharPrefix::Binary) { let repr = match prefix {
buf.write(format_args!("b'{}'", c.content), span); Some(CharPrefix::Binary) => format!(r#"b'{content}'"#),
} else { None => format!(r#"'{content}'"#),
buf.write(format_args!("'{}'", c.content), span); };
} buf.write_literal(&repr, span);
DisplayWrap::Unwrapped DisplayWrap::Unwrapped
} }
fn visit_num_lit(&mut self, buf: &mut Buffer, s: &str, span: proc_macro2::Span) -> DisplayWrap { fn visit_num_lit(&mut self, buf: &mut Buffer, s: &str, span: proc_macro2::Span) -> DisplayWrap {
buf.write(s, span); buf.write_literal(s, span);
DisplayWrap::Unwrapped DisplayWrap::Unwrapped
} }
@ -921,32 +918,32 @@ impl<'a> Generator<'a, '_> {
target: &Target<'a>, target: &Target<'a>,
) { ) {
match target { match target {
Target::Placeholder(_) => buf.write('_', ctx.template_span), Target::Placeholder(s) => quote_into!(buf, ctx.span_for_node(s.span()), { _ }),
Target::Rest(s) => { Target::Rest(s) => {
let span = ctx.span_for_node(s.span());
if let Some(var_name) = &**s { if let Some(var_name) = &**s {
let id = field_new(var_name, span);
self.locals self.locals
.insert(Cow::Borrowed(var_name), LocalMeta::var_def()); .insert(Cow::Borrowed(var_name), LocalMeta::var_def());
buf.write(*var_name, ctx.template_span); quote_into!(buf, span, { #id @ });
buf.write(" @ ", ctx.template_span);
} }
buf.write("..", ctx.template_span); buf.write_token(Token![..], span);
} }
Target::Name(name) => { Target::Name(name) => {
let name = normalize_identifier(name);
match initialized { match initialized {
true => self true => self
.locals .locals
.insert(Cow::Borrowed(name), LocalMeta::var_def()), .insert(Cow::Borrowed(name), LocalMeta::var_def()),
false => self.locals.insert_with_default(Cow::Borrowed(name)), false => self.locals.insert_with_default(Cow::Borrowed(name)),
} }
buf.write(name, ctx.template_span); buf.write_field(name, ctx.template_span);
} }
Target::OrChain(targets) => match targets.first() { Target::OrChain(targets) => match targets.first() {
None => buf.write('_', ctx.template_span), None => quote_into!(buf, ctx.template_span, { _ }),
Some(first_target) => { Some(first_target) => {
self.visit_target(ctx, buf, initialized, first_level, first_target); self.visit_target(ctx, buf, initialized, first_level, first_target);
for target in &targets[1..] { for target in &targets[1..] {
buf.write('|', ctx.template_span); buf.write_token(Token![|], ctx.template_span);
self.visit_target(ctx, buf, initialized, first_level, target); self.visit_target(ctx, buf, initialized, first_level, target);
} }
} }
@ -956,7 +953,7 @@ impl<'a> Generator<'a, '_> {
let mut targets_buf = Buffer::new(); let mut targets_buf = Buffer::new();
for target in targets { for target in targets {
self.visit_target(ctx, &mut targets_buf, initialized, false, target); self.visit_target(ctx, &mut targets_buf, initialized, false, target);
targets_buf.write(',', ctx.template_span); targets_buf.write_token(Token![,], ctx.template_span);
} }
let targets_buf = targets_buf.into_token_stream(); let targets_buf = targets_buf.into_token_stream();
buf.write( buf.write(
@ -971,7 +968,7 @@ impl<'a> Generator<'a, '_> {
let mut targets_buf = Buffer::new(); let mut targets_buf = Buffer::new();
for target in targets { for target in targets {
self.visit_target(ctx, &mut targets_buf, initialized, false, target); self.visit_target(ctx, &mut targets_buf, initialized, false, target);
targets_buf.write(',', ctx.template_span); targets_buf.write_token(Token![,], ctx.template_span);
} }
let targets_buf = targets_buf.into_token_stream(); let targets_buf = targets_buf.into_token_stream();
buf.write( buf.write(
@ -986,14 +983,14 @@ impl<'a> Generator<'a, '_> {
let mut targets_buf = Buffer::new(); let mut targets_buf = Buffer::new();
for (name, target) in targets { for (name, target) in targets {
if let Target::Rest(_) = target { if let Target::Rest(_) = target {
targets_buf.write("..", ctx.template_span); targets_buf.write_token(Token![..], ctx.template_span);
continue; continue;
} }
targets_buf.write(normalize_identifier(name), ctx.template_span); targets_buf.write_field(name, ctx.template_span);
targets_buf.write(": ", ctx.template_span); targets_buf.write_token(Token![:], ctx.template_span);
self.visit_target(ctx, &mut targets_buf, initialized, false, target); self.visit_target(ctx, &mut targets_buf, initialized, false, target);
targets_buf.write(',', ctx.template_span); targets_buf.write_token(Token![,], ctx.template_span);
} }
let targets_buf = targets_buf.into_token_stream(); let targets_buf = targets_buf.into_token_stream();
buf.write( buf.write(
@ -1007,35 +1004,49 @@ impl<'a> Generator<'a, '_> {
} }
Target::Path(path) => { Target::Path(path) => {
self.visit_path(ctx, buf, path); self.visit_path(ctx, buf, path);
buf.write("{}", ctx.template_span); quote_into!(buf, ctx.template_span, { {} });
} }
Target::StrLit(s) => { Target::StrLit(s) => {
let span = ctx.span_for_node(Span::from(s.content));
if first_level { if first_level {
buf.write('&', ctx.template_span); buf.write_token(Token![&], span);
} }
// FIXME: `Span` should not be `ctx.template_span`. self.visit_str_lit(buf, s, span);
self.visit_str_lit(buf, s, ctx.template_span);
} }
Target::NumLit(s, _) => { &Target::NumLit(repr, _) => {
let span = ctx.span_for_node(Span::from(repr));
if first_level { if first_level {
buf.write('&', ctx.template_span); buf.write_token(Token![&], span);
} }
// FIXME: `Span` should not be `ctx.template_span`. self.visit_num_lit(buf, repr, span);
self.visit_num_lit(buf, s, ctx.template_span);
} }
Target::CharLit(s) => { Target::CharLit(s) => {
let span = ctx.span_for_node(Span::from(s.content));
if first_level { if first_level {
buf.write('&', ctx.template_span); buf.write_token(Token![&], span);
} }
// FIXME: `Span` should not be `ctx.template_span`. self.visit_char_lit(buf, s, span);
self.visit_char_lit(buf, s, ctx.template_span);
} }
Target::BoolLit(s) => { &Target::BoolLit(s) => {
let span = ctx.span_for_node(Span::from(s));
if first_level { if first_level {
buf.write('&', ctx.template_span); buf.write_token(Token![&], span);
}
match s {
"true" => quote_into!(buf, span, { true }),
"false" => quote_into!(buf, span, { false }),
_ => unreachable!(),
} }
buf.write(*s, ctx.template_span);
} }
} }
} }
} }
fn write_resolved(buf: &mut Buffer, resolved: &str, span: proc_macro2::Span) {
for (idx, name) in resolved.split('.').enumerate() {
if idx > 0 {
buf.write_token(Token![.], span);
}
buf.write_field(name, span);
}
}

View File

@ -9,11 +9,12 @@ use parser::{
WithSpan, WithSpan,
}; };
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use syn::Token;
use super::{DisplayWrap, Generator, TargetIsize, TargetUsize}; use super::{DisplayWrap, Generator, TargetIsize, TargetUsize};
use crate::heritage::Context; use crate::heritage::Context;
use crate::integration::Buffer; use crate::integration::Buffer;
use crate::{CompileError, MsgValidEscapers, fmt_left, fmt_right}; use crate::{CompileError, MsgValidEscapers, field_new, fmt_left, fmt_right};
impl<'a> Generator<'a, '_> { impl<'a> Generator<'a, '_> {
pub(super) fn visit_filter( pub(super) fn visit_filter(
@ -85,9 +86,10 @@ impl<'a> Generator<'a, '_> {
let mut tmp = Buffer::new(); let mut tmp = Buffer::new();
tmp.write_tokens(self.visit_arg(ctx, &args[0], ctx.span_for_node(args[0].span()))?); tmp.write_tokens(self.visit_arg(ctx, &args[0], ctx.span_for_node(args[0].span()))?);
tmp.write(",__askama_values", span); let var_values = crate::var_values();
quote_into!(&mut tmp, span, { ,#var_values });
if args.len() > 1 { if args.len() > 1 {
tmp.write(',', span); tmp.write_token(Token![,], span);
self.visit_args(ctx, &mut tmp, &args[1..])?; self.visit_args(ctx, &mut tmp, &args[1..])?;
} }
let tmp = tmp.into_token_stream(); let tmp = tmp.into_token_stream();
@ -155,7 +157,8 @@ impl<'a> Generator<'a, '_> {
let span = ctx.span_for_node(node); let span = ctx.span_for_node(node);
let arg = no_arguments(ctx, name, args)?; let arg = no_arguments(ctx, name, args)?;
buf.write(format_args!("askama::filters::{name}"), span); let name = field_new(name, span);
quote_into!(buf, span, { askama::filters::#name });
self.visit_call_generics(ctx, buf, generics); self.visit_call_generics(ctx, buf, generics);
let arg = self.visit_arg(ctx, arg, span)?; let arg = self.visit_arg(ctx, arg, span)?;
buf.write_tokens(spanned!(span=> (#arg)?)); buf.write_tokens(spanned!(span=> (#arg)?));
@ -222,10 +225,11 @@ impl<'a> Generator<'a, '_> {
let span = ctx.span_for_node(node); let span = ctx.span_for_node(node);
let arg = self.visit_arg(ctx, arg, span)?; let arg = self.visit_arg(ctx, arg, span)?;
let var_values = crate::var_values();
buf.write_tokens(spanned!(span=> match askama::filters::wordcount(&(#arg)) { buf.write_tokens(spanned!(span=> match askama::filters::wordcount(&(#arg)) {
expr0 => { expr0 => {
(&&&askama::filters::Writable(&expr0)). (&&&askama::filters::Writable(&expr0)).
askama_write(&mut askama::helpers::Empty, __askama_values)?; askama_write(&mut askama::helpers::Empty, #var_values)?;
expr0.into_count() expr0.into_count()
} }
})); }));
@ -346,7 +350,7 @@ impl<'a> Generator<'a, '_> {
self.visit_auto_escaped_arg(ctx, &mut pl_buf, pl)?; self.visit_auto_escaped_arg(ctx, &mut pl_buf, pl)?;
let sg = sg_buf.into_token_stream(); let sg = sg_buf.into_token_stream();
let pl = pl_buf.into_token_stream(); let pl = pl_buf.into_token_stream();
buf.write_tokens(spanned!(span=> askama::filters::pluralize(#arg,#sg,#pl)?)); quote_into!(buf, span, { askama::filters::pluralize(#arg, #sg, #pl)? });
} }
Ok(DisplayWrap::Wrapped) Ok(DisplayWrap::Wrapped)
} }
@ -412,7 +416,7 @@ impl<'a> Generator<'a, '_> {
node: Span<'_>, node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
let arg = no_arguments(ctx, "ref", args)?; let arg = no_arguments(ctx, "ref", args)?;
buf.write('&', ctx.span_for_node(node)); buf.write_token(Token![&], ctx.span_for_node(node));
self.visit_expr(ctx, buf, arg)?; self.visit_expr(ctx, buf, arg)?;
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
@ -425,7 +429,7 @@ impl<'a> Generator<'a, '_> {
node: Span<'_>, node: Span<'_>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
let arg = no_arguments(ctx, "deref", args)?; let arg = no_arguments(ctx, "deref", args)?;
buf.write('*', ctx.span_for_node(node)); buf.write_token(Token![*], ctx.span_for_node(node));
self.visit_expr(ctx, buf, arg)?; self.visit_expr(ctx, buf, arg)?;
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
@ -611,7 +615,7 @@ impl<'a> Generator<'a, '_> {
let mut filter = Buffer::new(); let mut filter = Buffer::new();
self.visit_str_lit(&mut filter, fmt, span); self.visit_str_lit(&mut filter, fmt, span);
if !tail.is_empty() { if !tail.is_empty() {
filter.write(',', span); filter.write_token(Token![,], ctx.span_for_node(node));
self.visit_args(ctx, &mut filter, tail)?; self.visit_args(ctx, &mut filter, tail)?;
} }
let filter = filter.into_token_stream(); let filter = filter.into_token_stream();

View File

@ -6,13 +6,14 @@ use std::mem;
use parser::node::{Call, Macro, Ws}; use parser::node::{Call, Macro, Ws};
use parser::{Expr, Span, WithSpan}; use parser::{Expr, Span, WithSpan};
use quote::quote_spanned;
use rustc_hash::FxBuildHasher; use rustc_hash::FxBuildHasher;
use crate::CompileError;
use crate::generator::node::AstLevel; use crate::generator::node::AstLevel;
use crate::generator::{Generator, LocalMeta, is_copyable, normalize_identifier}; use crate::generator::{Generator, LocalMeta, is_copyable};
use crate::heritage::Context; use crate::heritage::Context;
use crate::integration::Buffer; use crate::integration::Buffer;
use crate::{CompileError, field_new};
/// Helper to generate the code for macro invocations /// Helper to generate the code for macro invocations
pub(crate) struct MacroInvocation<'a, 'b> { pub(crate) struct MacroInvocation<'a, 'b> {
@ -83,14 +84,7 @@ impl<'a, 'b> MacroInvocation<'a, 'b> {
this.flush_ws(self.macro_def.ws2); this.flush_ws(self.macro_def.ws2);
size_hint += this.write_buf_writable(self.callsite_ctx, &mut content)?; size_hint += this.write_buf_writable(self.callsite_ctx, &mut content)?;
let content = content.into_token_stream(); let content = content.into_token_stream();
buf.write( quote_into!(buf, self.callsite_ctx.template_span, {{ #content }});
quote::quote!(
{
#content
}
),
self.callsite_ctx.template_span,
);
this.prepare_ws(self.callsite_ws); this.prepare_ws(self.callsite_ws);
this.seen_callers.pop(); this.seen_callers.pop();
@ -205,22 +199,16 @@ impl<'a, 'b> MacroInvocation<'a, 'b> {
// parameters being used multiple times. // parameters being used multiple times.
_ => { _ => {
value.clear(); value.clear();
let (before, after) = if !is_copyable(expr) {
("&(", ")")
} else {
("", "")
};
value.write_tokens(generator.visit_expr_root(self.callsite_ctx, expr)?); value.write_tokens(generator.visit_expr_root(self.callsite_ctx, expr)?);
// We need to normalize the arg to write it, thus we need to add it to let span = self.callsite_ctx.template_span;
// locals in the normalized manner let id = field_new(arg, span);
let normalized_arg = normalize_identifier(arg); buf.write_tokens(if !is_copyable(expr) {
buf.write( quote_spanned! { span => let #id = &(#value); }
format_args!("let {normalized_arg} = {before}{value}{after};"), } else {
self.callsite_ctx.template_span, quote_spanned! { span => let #id = #value; }
); });
generator
.locals generator.locals.insert_with_default(Cow::Borrowed(arg));
.insert_with_default(Cow::Borrowed(normalized_arg));
} }
} }
} }

View File

@ -11,15 +11,14 @@ use parser::node::{
}; };
use parser::{Expr, Node, Span, Target, WithSpan}; use parser::{Expr, Node, Span, Target, WithSpan};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote_spanned;
use syn::{Ident, Token};
use super::{ use super::{DisplayWrap, Generator, LocalMeta, MapChain, compile_time_escape, is_copyable};
DisplayWrap, FILTER_SOURCE, Generator, LocalMeta, MapChain, compile_time_escape, is_copyable, use crate::generator::{LocalCallerMeta, Writable, helpers, logic_op};
normalize_identifier,
};
use crate::generator::{LocalCallerMeta, Writable, helpers};
use crate::heritage::{Context, Heritage}; use crate::heritage::{Context, Heritage};
use crate::integration::{Buffer, string_escape}; use crate::integration::{Buffer, string_escape};
use crate::{CompileError, FileInfo, fmt_left, fmt_right}; use crate::{CompileError, FileInfo, field_new, fmt_left, fmt_right};
impl<'a> Generator<'a, '_> { impl<'a> Generator<'a, '_> {
pub(super) fn impl_template_inner( pub(super) fn impl_template_inner(
@ -162,12 +161,16 @@ impl<'a> Generator<'a, '_> {
Node::Break(ref ws) => { Node::Break(ref ws) => {
self.handle_ws(**ws); self.handle_ws(**ws);
self.write_buf_writable(ctx, buf)?; self.write_buf_writable(ctx, buf)?;
buf.write("break;", ctx.template_span); quote_into!(buf, ctx.template_span, {
break;
});
} }
Node::Continue(ref ws) => { Node::Continue(ref ws) => {
self.handle_ws(**ws); self.handle_ws(**ws);
self.write_buf_writable(ctx, buf)?; self.write_buf_writable(ctx, buf)?;
buf.write("continue;", ctx.template_span); quote_into!(buf, ctx.template_span, {
continue;
});
} }
} }
} }
@ -343,22 +346,22 @@ impl<'a> Generator<'a, '_> {
if pos == 0 { if pos == 0 {
if cond_info.generate_condition { if cond_info.generate_condition {
buf.write("if", span); buf.write_token(Token![if], ctx.template_span);
} else { } else {
has_cond = false; has_cond = false;
} }
// Otherwise it means it will be the only condition generated, // Otherwise it means it will be the only condition generated,
// so nothing to be added here. // so nothing to be added here.
} else if cond_info.generate_condition { } else if cond_info.generate_condition {
buf.write("else if", span); quote_into!(buf, ctx.template_span, { else if });
} else { } else {
buf.write("else", span); buf.write_token(Token![else], ctx.template_span);
has_else = true; has_else = true;
} }
if let Some(target) = target { if let Some(target) = target {
let mut expr_buf = Buffer::new(); let mut expr_buf = Buffer::new();
buf.write("let ", span); buf.write_token(Token![let], ctx.template_span);
// If this is a chain condition, then we need to declare the variable after the // 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 // left expression has been handled but before the right expression is handled
// but this one should have access to the let-bound variable. // but this one should have access to the let-bound variable.
@ -373,7 +376,8 @@ impl<'a> Generator<'a, '_> {
&v.lhs, &v.lhs,
display_wrap, display_wrap,
)?; )?;
buf.write(format_args!("= &{expr_buf} {} ", v.op), span); let op = logic_op(v.op, span);
quote_into!(buf, span, { = &#expr_buf #op });
this.visit_condition(ctx, buf, &v.rhs)?; this.visit_condition(ctx, buf, &v.rhs)?;
} }
_ => { _ => {
@ -381,7 +385,7 @@ impl<'a> Generator<'a, '_> {
this.visit_expr_first(ctx, &mut expr_buf, expr)?; this.visit_expr_first(ctx, &mut expr_buf, expr)?;
this.visit_target(ctx, buf, true, true, target); this.visit_target(ctx, buf, true, true, target);
this.visit_expr_not_first(ctx, &mut expr_buf, expr, display_wrap)?; this.visit_expr_not_first(ctx, &mut expr_buf, expr, display_wrap)?;
buf.write(format_args!("= &{expr_buf}"), span); quote_into!(buf, ctx.template_span, { = &#expr_buf });
} }
} }
} else if cond_info.generate_condition { } else if cond_info.generate_condition {
@ -389,7 +393,7 @@ impl<'a> Generator<'a, '_> {
} }
} else if pos != 0 { } else if pos != 0 {
// FIXME: Should have a span. // FIXME: Should have a span.
buf.write("else", ctx.template_span); buf.write_token(Token![else], ctx.template_span);
has_else = true; has_else = true;
} else { } else {
has_cond = false; has_cond = false;
@ -420,14 +424,7 @@ impl<'a> Generator<'a, '_> {
if has_cond { if has_cond {
let block_buf = block_buf.into_token_stream(); let block_buf = block_buf.into_token_stream();
// FIXME Should have a span. // FIXME Should have a span.
buf.write( quote_into!(buf, ctx.template_span, { { #block_buf } });
quote::quote!(
{
#block_buf
}
),
ctx.template_span,
);
} else { } else {
buf.write_buf(block_buf); buf.write_buf(block_buf);
} }
@ -475,7 +472,7 @@ impl<'a> Generator<'a, '_> {
let mut targets_buf = Buffer::new(); let mut targets_buf = Buffer::new();
for (index, target) in arm.target.iter().enumerate() { for (index, target) in arm.target.iter().enumerate() {
if index != 0 { if index != 0 {
targets_buf.write('|', span); targets_buf.write_token(Token![|], span);
} }
this.visit_target(ctx, &mut targets_buf, true, true, target); this.visit_target(ctx, &mut targets_buf, true, true, target);
} }
@ -492,15 +489,13 @@ impl<'a> Generator<'a, '_> {
} }
let targets_buf = targets_buf.into_token_stream(); let targets_buf = targets_buf.into_token_stream();
let arm_buf = arm_buf.into_token_stream(); let arm_buf = arm_buf.into_token_stream();
arms.write_tokens(spanned!(span=> #targets_buf => { #arm_buf })); quote_into!(&mut arms, span, { #targets_buf => { #arm_buf } });
Ok(0) Ok(0)
})?; })?;
} }
let arms = arms.into_token_stream(); let arms = arms.into_token_stream();
buf.write_tokens(spanned!(span=> match & #expr_code { quote_into!(buf, span, { match &#expr_code { #arms } });
#arms
}));
Ok(flushed + median(&mut arm_sizes)) Ok(flushed + median(&mut arm_sizes))
} }
@ -516,15 +511,19 @@ impl<'a> Generator<'a, '_> {
self.push_locals(|this| { self.push_locals(|this| {
let has_else_nodes = !loop_block.else_nodes.is_empty(); let has_else_nodes = !loop_block.else_nodes.is_empty();
let var_did_loop = crate::var_did_loop();
let var_item = crate::var_item();
let var_iter = crate::var_iter();
let flushed = this.write_buf_writable(ctx, buf)?; let flushed = this.write_buf_writable(ctx, buf)?;
let mut loop_buf = Buffer::new(); let mut loop_buf = Buffer::new();
if has_else_nodes { if has_else_nodes {
loop_buf.write("let mut __askama_did_loop = false;", span); quote_into!(&mut loop_buf, span, { let mut #var_did_loop = false; });
} }
loop_buf.write("let __askama_iter =", span); quote_into!(&mut loop_buf, span, { let #var_iter = });
this.visit_loop_iter(ctx, &mut loop_buf, &loop_block.iter)?; this.visit_loop_iter(ctx, &mut loop_buf, &loop_block.iter)?;
loop_buf.write(';', span); loop_buf.write_token(Token![;], span);
if let Some(cond) = &loop_block.cond { if let Some(cond) = &loop_block.cond {
this.push_locals(|this| { this.push_locals(|this| {
let mut target_buf = Buffer::new(); let mut target_buf = Buffer::new();
@ -533,11 +532,17 @@ impl<'a> Generator<'a, '_> {
let mut expr_buf = Buffer::new(); let mut expr_buf = Buffer::new();
this.visit_expr(ctx, &mut expr_buf, cond)?; this.visit_expr(ctx, &mut expr_buf, cond)?;
let expr_buf = expr_buf.into_token_stream(); let expr_buf = expr_buf.into_token_stream();
loop_buf.write_tokens(spanned!(span=> quote_into!(
let __askama_iter = __askama_iter.filter(|#target_buf| -> askama::helpers::core::primitive::bool { &mut loop_buf,
#expr_buf span,
}); {
)); let #var_iter = #var_iter.filter(
|#target_buf| -> askama::helpers::core::primitive::bool {
#expr_buf
}
);
}
);
Ok(0) Ok(0)
})?; })?;
} }
@ -549,14 +554,15 @@ impl<'a> Generator<'a, '_> {
let mut loop_body_buf = Buffer::new(); let mut loop_body_buf = Buffer::new();
if has_else_nodes { if has_else_nodes {
loop_body_buf.write("__askama_did_loop = true;", span); quote_into!(&mut loop_body_buf, span, { #var_did_loop = true; });
} }
let mut size_hint1 = this.handle(ctx, &loop_block.body, &mut loop_body_buf, AstLevel::Nested)?; let mut size_hint1 =
this.handle(ctx, &loop_block.body, &mut loop_body_buf, AstLevel::Nested)?;
this.handle_ws(loop_block.ws2); this.handle_ws(loop_block.ws2);
size_hint1 += this.write_buf_writable(ctx, &mut loop_body_buf)?; size_hint1 += this.write_buf_writable(ctx, &mut loop_body_buf)?;
let loop_body_buf = loop_body_buf.into_token_stream(); let loop_body_buf = loop_body_buf.into_token_stream();
loop_buf.write_tokens(spanned!(span=> loop_buf.write_tokens(spanned!(span=>
for (#target_buf, __askama_item) in askama::helpers::TemplateLoop::new(__askama_iter) { for (#target_buf, #var_item) in askama::helpers::TemplateLoop::new(#var_iter) {
#loop_body_buf #loop_body_buf
} }
)); ));
@ -574,7 +580,7 @@ impl<'a> Generator<'a, '_> {
Ok(size_hint) Ok(size_hint)
})?; })?;
let cond_buf = cond_buf.into_token_stream(); let cond_buf = cond_buf.into_token_stream();
loop_buf.write_tokens(spanned!(span=> if !__askama_did_loop { loop_buf.write_tokens(spanned!(span=> if !#var_did_loop {
#cond_buf #cond_buf
})); }));
} else { } else {
@ -647,6 +653,8 @@ impl<'a> Generator<'a, '_> {
buf: &mut Buffer, buf: &mut Buffer,
filter: &'a WithSpan<'a, FilterBlock<'_>>, filter: &'a WithSpan<'a, FilterBlock<'_>>,
) -> Result<usize, CompileError> { ) -> Result<usize, CompileError> {
let var_filter_source = crate::var_filter_source();
self.write_buf_writable(ctx, buf)?; self.write_buf_writable(ctx, buf)?;
self.flush_ws(filter.ws1); self.flush_ws(filter.ws1);
self.is_in_filter_block += 1; self.is_in_filter_block += 1;
@ -664,10 +672,10 @@ impl<'a> Generator<'a, '_> {
Ok(size_hint) Ok(size_hint)
})?; })?;
let filter_def_buf = filter_def_buf.into_token_stream(); let filter_def_buf = filter_def_buf.into_token_stream();
let filter_source = quote::format_ident!("{FILTER_SOURCE}"); let var_writer = crate::var_writer();
let filter_def_buf = spanned!(span=> let filter_def_buf = spanned!(span=>
let #filter_source = askama::helpers::FmtCell::new( let #var_filter_source = askama::helpers::FmtCell::new(
|__askama_writer: &mut askama::helpers::core::fmt::Formatter<'_>| -> askama::Result<()> { |#var_writer: &mut askama::helpers::core::fmt::Formatter<'_>| -> askama::Result<()> {
#filter_def_buf #filter_def_buf
askama::Result::Ok(()) askama::Result::Ok(())
} }
@ -695,14 +703,12 @@ impl<'a> Generator<'a, '_> {
) )
} }
}; };
buf.write_tokens(spanned!(span=> quote_into!(buf, span, { {
{ #filter_def_buf
#filter_def_buf if askama::helpers::core::write!(#var_writer, "{}", #filter_buf).is_err() {
if askama::helpers::core::write!(__askama_writer, "{}", #filter_buf).is_err() { return #var_filter_source.take_err();
return #filter_source.take_err();
}
} }
)); } });
self.is_in_filter_block -= 1; self.is_in_filter_block -= 1;
self.prepare_ws(filter.ws2); self.prepare_ws(filter.ws2);
@ -774,7 +780,6 @@ impl<'a> Generator<'a, '_> {
) -> Result<bool, CompileError> { ) -> Result<bool, CompileError> {
match var { match var {
Target::Name(name) => { Target::Name(name) => {
let name = normalize_identifier(name);
match self.locals.get(name) { match self.locals.get(name) {
// declares a new variable // declares a new variable
None => Ok(false), None => Ok(false),
@ -830,12 +835,12 @@ impl<'a> Generator<'a, '_> {
let Some(val) = &l.val else { let Some(val) = &l.val else {
self.write_buf_writable(ctx, buf)?; self.write_buf_writable(ctx, buf)?;
buf.write("let ", span); buf.write_token(Token![let], span);
if l.is_mutable { if l.is_mutable {
buf.write("mut ", span); buf.write_token(Token![mut], span);
} }
self.visit_target(ctx, buf, false, true, &l.var); self.visit_target(ctx, buf, false, true, &l.var);
buf.write(';', span); buf.write_token(Token![;], span);
return Ok(()); return Ok(());
}; };
@ -862,24 +867,23 @@ impl<'a> Generator<'a, '_> {
|| !matches!(l.var, Target::Name(_)) || !matches!(l.var, Target::Name(_))
|| matches!(&l.var, Target::Name(name) if self.locals.get(name).is_none()) || matches!(&l.var, Target::Name(name) if self.locals.get(name).is_none())
{ {
buf.write("let ", span); buf.write_token(Token![let], span);
if l.is_mutable { if l.is_mutable {
buf.write("mut ", span); buf.write_token(Token![mut], span);
} }
} }
self.visit_target(ctx, buf, true, true, &l.var); self.visit_target(ctx, buf, true, true, &l.var);
// If it's not taking the ownership of a local variable or copyable, then we need to add // If it's not taking the ownership of a local variable or copyable, then we need to add
// a reference. // a reference.
let (before, after) = if !matches!(***val, Expr::Try(..)) let borrow = !matches!(***val, Expr::Try(..))
&& !matches!(***val, Expr::Var(name) if self.locals.get(name).is_some()) && !matches!(***val, Expr::Var(name) if self.locals.get(name).is_some())
&& !is_copyable(val) && !is_copyable(val);
{ buf.write_tokens(if borrow {
("&(", ")") quote_spanned! { span => = &(#expr_buf); }
} else { } else {
("", "") quote_spanned! { span => = #expr_buf; }
}; });
buf.write(format_args!(" = {before}{expr_buf}{after};"), span);
Ok(()) Ok(())
} }
@ -1157,27 +1161,19 @@ impl<'a> Generator<'a, '_> {
// multiple times, e.g. in the case of macro // multiple times, e.g. in the case of macro
// parameters being used multiple times. // parameters being used multiple times.
_ => { _ => {
let (before, after) = if !is_copyable(expr) {
("&(", ")")
} else {
("", "")
};
value.write( value.write(
this.visit_expr_root(&call_ctx, expr)?, this.visit_expr_root(&call_ctx, expr)?,
span_span, span_span,
); );
let value = value.to_string();
// We need to normalize the arg to write it, thus we need to add it to // We need to normalize the arg to write it, thus we need to add it to
// locals in the normalized manner // locals in the normalized manner
let normalized_arg = normalize_identifier(arg); let id = field_new(arg, span_span);
variable_buf.write( variable_buf.write_tokens(if !is_copyable(expr) {
format_args!( quote_spanned! { span_span => let #id = &(#value); }
"let {normalized_arg} = {before}{value}{after};" } else {
), quote_spanned! { span_span => let #id = #value; }
span_span, });
); this.locals.insert_with_default(Cow::Borrowed(arg));
this.locals
.insert_with_default(Cow::Borrowed(normalized_arg));
} }
} }
} }
@ -1197,10 +1193,7 @@ impl<'a> Generator<'a, '_> {
size_hint += this.write_buf_writable(&call_ctx, &mut value)?; size_hint += this.write_buf_writable(&call_ctx, &mut value)?;
let value = value.into_token_stream(); let value = value.into_token_stream();
let variable_buf = variable_buf.into_token_stream(); let variable_buf = variable_buf.into_token_stream();
buf.write_tokens(spanned!(span_span=> { quote_into!(buf, span_span, { #variable_buf #value });
#variable_buf
#value
}));
Ok(size_hint) Ok(size_hint)
})?; })?;
return Ok(ControlFlow::Break(size_hint)); return Ok(ControlFlow::Break(size_hint));
@ -1326,39 +1319,43 @@ impl<'a> Generator<'a, '_> {
match expr_cache.entry(expr.to_string()) { match expr_cache.entry(expr.to_string()) {
Entry::Occupied(e) => *e.get(), Entry::Occupied(e) => *e.get(),
Entry::Vacant(e) => { Entry::Vacant(e) => {
matched_expr_buf.write(format_args!("&({}),", e.key()), span); let id = Ident::new(&format!("expr{idx}"), span);
targets.write(format_args!("expr{idx},"), span); quote_into!(&mut matched_expr_buf, span, { &(#expr), });
quote_into!(&mut targets, span, { #id, });
e.insert(idx); e.insert(idx);
idx idx
} }
} }
} else { } else {
matched_expr_buf.write_tokens(spanned!(span=> &(#expr),)); quote_into!(&mut matched_expr_buf, span, { &(#expr), });
targets.write(format_args!("expr{idx}, "), span); let id = Ident::new(&format!("expr{idx}"), span);
quote_into!(&mut targets, span, { #id, });
idx idx
}; };
lines.write( let id = Ident::new(&format!("expr{idx}"), span);
format_args!( let var_writer = crate::var_writer();
"(&&&askama::filters::Writable(expr{idx})).\ let var_values = crate::var_values();
askama_write(__askama_writer, __askama_values)?;", quote_into!(
), &mut lines,
span, span,
{
(&&&askama::filters::Writable(#id)).
askama_write(#var_writer, #var_values)?;
},
); );
} }
} }
} }
let matched_expr_buf = matched_expr_buf.into_token_stream(); quote_into!(
let targets = targets.into_token_stream(); buf,
let lines = lines.into_token_stream(); ctx.template_span,
buf.write( {
quote::quote!(
match (#matched_expr_buf) { match (#matched_expr_buf) {
(#targets) => { (#targets) => {
#lines #lines
} }
} }
), }
ctx.template_span,
); );
if !trailing_simple_lines.is_empty() { if !trailing_simple_lines.is_empty() {

View File

@ -1,18 +1,18 @@
use std::fmt::{Arguments, Display}; use std::fmt::Display;
use std::str::FromStr; use std::mem::take;
use parser::{PathComponent, WithSpan}; use parser::{PathComponent, WithSpan};
use proc_macro2::{TokenStream, TokenTree}; use proc_macro2::{Literal, TokenStream, TokenTree};
use quote::{ToTokens, quote}; use quote::{ToTokens, quote_spanned};
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::{ use syn::{
Data, DeriveInput, Fields, GenericParam, Generics, Ident, Lifetime, LifetimeParam, Token, Type, Data, DeriveInput, Fields, GenericParam, Generics, Ident, Lifetime, LifetimeParam, LitStr,
Variant, parse_quote, Token, Type, Variant, parse_quote,
}; };
use crate::generator::TmplKind; use crate::generator::TmplKind;
use crate::input::{PartialTemplateArgs, TemplateArgs}; use crate::input::{PartialTemplateArgs, TemplateArgs};
use crate::{CompileError, Context, Print, build_template_item}; use crate::{CompileError, Context, Print, build_template_item, field_new};
/// Implement every integration for the given item /// Implement every integration for the given item
pub(crate) fn impl_everything(ast: &DeriveInput, buf: &mut Buffer) { pub(crate) fn impl_everything(ast: &DeriveInput, buf: &mut Buffer) {
@ -30,31 +30,31 @@ pub(crate) fn write_header(
let (impl_generics, orig_ty_generics, where_clause) = ast.generics.split_for_impl(); let (impl_generics, orig_ty_generics, where_clause) = ast.generics.split_for_impl();
let ident = &ast.ident; let ident = &ast.ident;
buf.write_tokens(spanned!(span=> quote_into!(buf, span, {
#[automatically_derived]
impl #impl_generics #target for #ident #orig_ty_generics #where_clause impl #impl_generics #target for #ident #orig_ty_generics #where_clause
)); });
} }
/// Implement `Display` for the given item. /// Implement `Display` for the given item.
// FIXME: Add span
fn impl_display(ast: &DeriveInput, buf: &mut Buffer) { fn impl_display(ast: &DeriveInput, buf: &mut Buffer) {
let ident = &ast.ident; let ident = &ast.ident;
let span = ast.span(); let span = ident.span();
buf.write( let msg =
TokenStream::from_str(&format!( format!(" Implement the [`format!()`][askama::helpers::std::format] trait for [`{ident}`]");
"\ quote_into!(buf, span, {
/// Implement the [`format!()`][askama::helpers::std::format] trait for [`{ident}`]\n\ #[doc = #msg]
///\n\ ///
/// Please be aware of the rendering performance notice in the \ /// Please be aware of the rendering performance notice in the [`Template`][askama::Template] trait.
[`Template`][askama::Template] trait.\n\ });
", write_header(
)) ast,
.unwrap(), buf,
quote_spanned!(span => askama::helpers::core::fmt::Display),
span, span,
); );
write_header(ast, buf, quote!(askama::helpers::core::fmt::Display), span); quote_into!(buf, span, {
buf.write( {
quote!({
#[inline] #[inline]
fn fmt( fn fmt(
&self, &self,
@ -63,18 +63,16 @@ fn impl_display(ast: &DeriveInput, buf: &mut Buffer) {
askama::Template::render_into(self, f) askama::Template::render_into(self, f)
.map_err(|_| askama::helpers::core::fmt::Error) .map_err(|_| askama::helpers::core::fmt::Error)
} }
}), }
span, });
);
} }
/// Implement `FastWritable` for the given item. /// Implement `FastWritable` for the given item.
// FIXME: Add span
fn impl_fast_writable(ast: &DeriveInput, buf: &mut Buffer) { fn impl_fast_writable(ast: &DeriveInput, buf: &mut Buffer) {
let span = ast.span(); let span = ast.span();
write_header(ast, buf, quote!(askama::FastWritable), span); write_header(ast, buf, quote_spanned!(span => askama::FastWritable), span);
buf.write( quote_into!(buf, span, {
quote!({ {
#[inline] #[inline]
fn write_into<AskamaW>( fn write_into<AskamaW>(
&self, &self,
@ -86,9 +84,8 @@ fn impl_fast_writable(ast: &DeriveInput, buf: &mut Buffer) {
{ {
askama::Template::render_into_with_values(self, dest, values) askama::Template::render_into_with_values(self, dest, values)
} }
}), }
span, });
);
} }
#[derive(Debug)] #[derive(Debug)]
@ -105,6 +102,23 @@ impl Display for Buffer {
} }
} }
impl ToTokens for Buffer {
#[inline]
fn to_tokens(&self, tokens: &mut TokenStream) {
self.buf.to_tokens(tokens);
}
#[inline]
fn to_token_stream(&self) -> TokenStream {
self.buf.clone()
}
#[inline]
fn into_token_stream(self) -> TokenStream {
self.buf
}
}
impl Buffer { impl Buffer {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Self { Self {
@ -115,30 +129,22 @@ impl Buffer {
} }
fn handle_str_lit(&mut self) { fn handle_str_lit(&mut self) {
let str_literals = std::mem::take(&mut self.string_literals); let Some((literal, span)) = take(&mut self.string_literals).into_iter().reduce(
match str_literals.as_slice() { |(mut acc_lit, acc_span), (literal, span)| {
[] => {} acc_lit.push_str(&literal);
[(literal, span)] => { (acc_lit, acc_span.join(span).unwrap_or(acc_span))
let span = *span; },
let literal = ) else {
proc_macro2::TokenStream::from_str(&format!("\"{literal}\"")).unwrap(); return;
self.buf };
.extend(spanned!(span=> __askama_writer.write_str(#literal)?;));
} let mut literal: Literal = format!(r#""{literal}""#).parse().unwrap();
[(literal, span), rest @ ..] => { literal.set_span(span);
let (literal, span) = rest.iter().fold( let askama_writer = crate::var_writer();
(literal.clone(), *span), self.buf.extend(quote_spanned! {
|(mut acc_lit, acc_span), (literal, span)| { span =>
acc_lit.push_str(literal); #askama_writer.write_str(#literal)?;
(acc_lit, acc_span.join(*span).unwrap_or(acc_span)) });
},
);
let literal =
proc_macro2::TokenStream::from_str(&format!("\"{literal}\"")).unwrap();
self.buf
.extend(spanned!(span=> __askama_writer.write_str(#literal)?;));
}
}
} }
pub(crate) fn write_str_lit(&mut self, literal: String, span: proc_macro2::Span) { pub(crate) fn write_str_lit(&mut self, literal: String, span: proc_macro2::Span) {
@ -178,6 +184,40 @@ impl Buffer {
self.buf.extend(src); self.buf.extend(src);
} }
pub(crate) fn write_token<F, T>(&mut self, token: F, span: proc_macro2::Span)
where
F: Fn(proc_macro2::Span) -> T,
T: syn::token::Token + ToTokens,
{
if self.discard {
return;
}
self.handle_str_lit();
token(span).to_tokens(&mut self.buf);
}
pub(crate) fn write_literal(&mut self, repr: &str, span: proc_macro2::Span) {
if self.discard {
return;
}
self.handle_str_lit();
self._write_literal_repr(repr, span);
}
fn _write_literal_repr(&mut self, repr: &str, span: proc_macro2::Span) {
let mut literal: Literal = repr.parse().unwrap();
literal.set_span(span);
literal.to_tokens(&mut self.buf);
}
pub(crate) fn write_field(&mut self, name: &str, span: proc_macro2::Span) {
if self.discard {
return;
}
self.handle_str_lit();
self.buf.extend(field_new(name, span));
}
pub(crate) fn write_separated_path( pub(crate) fn write_separated_path(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
@ -191,10 +231,11 @@ impl Buffer {
for (idx, item) in path.iter().enumerate() { for (idx, item) in path.iter().enumerate() {
let span = ctx.span_for_node(item.span()); let span = ctx.span_for_node(item.span());
if idx > 0 { if idx > 0 {
self.buf.extend(spanned!(span=> ::)); Token![::](span).to_tokens(&mut self.buf);
}
if !item.name.is_empty() {
Ident::new(item.name, span).to_tokens(&mut self.buf);
} }
let name = quote::format_ident!("{}", item.name);
self.buf.extend(spanned!(span=> #name));
} }
} }
@ -203,9 +244,7 @@ impl Buffer {
return; return;
} }
self.handle_str_lit(); self.handle_str_lit();
let mut buf = String::with_capacity(s.len()); LitStr::new(s, span).to_tokens(&mut self.buf);
string_escape(&mut buf, s);
self.buf.extend(spanned!(span=> #buf));
} }
pub(crate) fn clear(&mut self) { pub(crate) fn clear(&mut self) {
@ -234,39 +273,6 @@ pub(crate) trait BufferFmt {
fn append_to(self, buf: &mut TokenStream, span: proc_macro2::Span); fn append_to(self, buf: &mut TokenStream, span: proc_macro2::Span);
} }
macro_rules! impl_bufferfmt {
($($ty_name:ty),+) => {
$(
impl BufferFmt for $ty_name {
fn append_to(self, buf: &mut TokenStream, span: proc_macro2::Span) {
let Ok(stream) = TokenStream::from_str(&self) else {
panic!("Invalid token stream input:\n----------\n{self}\n----------");
};
buf.extend(spanned!(span=> #stream));
}
}
)+
}
}
impl_bufferfmt!(&str, String);
impl BufferFmt for char {
fn append_to(self, buf: &mut TokenStream, span: proc_macro2::Span) {
self.to_string().append_to(buf, span);
}
}
impl BufferFmt for Arguments<'_> {
fn append_to(self, buf: &mut TokenStream, span: proc_macro2::Span) {
if let Some(s) = self.as_str() {
s.append_to(buf, span);
} else {
self.to_string().append_to(buf, span);
}
}
}
impl BufferFmt for TokenStream { impl BufferFmt for TokenStream {
fn append_to(self, buf: &mut TokenStream, _span: proc_macro2::Span) { fn append_to(self, buf: &mut TokenStream, _span: proc_macro2::Span) {
buf.extend(self); buf.extend(self);
@ -307,6 +313,7 @@ pub(crate) fn build_template_enum(
let Data::Enum(enum_data) = &enum_ast.data else { let Data::Enum(enum_data) = &enum_ast.data else {
unreachable!(); unreachable!();
}; };
let span = enum_ast.span();
impl_everything(enum_ast, buf); impl_everything(enum_ast, buf);
@ -334,7 +341,7 @@ pub(crate) fn build_template_enum(
}; };
let var_ast = type_for_enum_variant(enum_ast, &generics, var); let var_ast = type_for_enum_variant(enum_ast, &generics, var);
buf.write(quote!(#var_ast), enum_ast.span()); quote_into!(buf, span, { #var_ast });
// not inherited: template, meta_docs, block, print // not inherited: template, meta_docs, block, print
if let Some(enum_args) = &mut enum_args { if let Some(enum_args) = &mut enum_args {
@ -377,29 +384,35 @@ pub(crate) fn build_template_enum(
)?; )?;
biggest_size_hint = biggest_size_hint.max(size_hint); biggest_size_hint = biggest_size_hint.max(size_hint);
render_into_arms.extend(quote! { let var_arg = crate::var_arg();
ref __askama_arg => { let var_writer = crate::var_writer();
let var_values = crate::var_values();
render_into_arms.extend(quote_spanned! {
span =>
ref #var_arg => {
<_ as askama::helpers::EnumVariantTemplate>::render_into_with_values( <_ as askama::helpers::EnumVariantTemplate>::render_into_with_values(
__askama_arg, #var_arg,
__askama_writer, #var_writer,
__askama_values, #var_values,
) )
} }
}); });
size_hint_arms.extend(quote! { size_hint_arms.extend(quote_spanned! {
span =>
_ => { _ => {
#size_hint #size_hint
} }
}); });
} }
write_header(enum_ast, buf, quote!(askama::Template), enum_ast.span());
let mut methods = TokenStream::new(); let mut methods = TokenStream::new();
methods.extend(quote!( let var_writer = crate::var_writer();
let var_values = crate::var_values();
methods.extend(quote_spanned!(span =>
fn render_into_with_values<AskamaW>( fn render_into_with_values<AskamaW>(
&self, &self,
__askama_writer: &mut AskamaW, #var_writer: &mut AskamaW,
__askama_values: &dyn askama::Values, #var_values: &dyn askama::Values,
) -> askama::Result<()> ) -> askama::Result<()>
where where
AskamaW: askama::helpers::core::fmt::Write + ?askama::helpers::core::marker::Sized AskamaW: askama::helpers::core::fmt::Write + ?askama::helpers::core::marker::Sized
@ -411,28 +424,34 @@ pub(crate) fn build_template_enum(
)); ));
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
methods.extend(quote!( methods.extend(quote_spanned!(
fn render_with_values( span =>
&self, fn render_with_values(
__askama_values: &dyn askama::Values, &self,
) -> askama::Result<askama::helpers::alloc::string::String> { #var_values: &dyn askama::Values,
let size_hint = match self { ) -> askama::Result<askama::helpers::alloc::string::String> {
#size_hint_arms let size_hint = match self {
}; #size_hint_arms
let mut buf = askama::helpers::alloc::string::String::new(); };
let _ = buf.try_reserve(size_hint); let mut buf = askama::helpers::alloc::string::String::new();
self.render_into_with_values(&mut buf, __askama_values)?; let _ = buf.try_reserve(size_hint);
askama::Result::Ok(buf) self.render_into_with_values(&mut buf, #var_values)?;
})); askama::Result::Ok(buf)
}
));
buf.write( write_header(
quote!( enum_ast,
buf,
quote_spanned!(span => askama::Template),
span,
);
quote_into!(buf, span, {
{ {
#methods #methods
const SIZE_HINT: askama::helpers::core::primitive::usize = #biggest_size_hint; const SIZE_HINT: askama::helpers::core::primitive::usize = #biggest_size_hint;
}), }
enum_ast.span(), });
);
if print_code { if print_code {
eprintln!("{buf}"); eprintln!("{buf}");
} }
@ -545,8 +564,8 @@ fn variant_as_arm(
for (idx, field) in fields.named.iter().enumerate() { for (idx, field) in fields.named.iter().enumerate() {
let arg = Ident::new(&format!("__askama_arg_{idx}"), field.span()); let arg = Ident::new(&format!("__askama_arg_{idx}"), field.span());
let id = field.ident.as_ref().unwrap(); let id = field.ident.as_ref().unwrap();
src.extend(quote!(#id: ref #arg,)); src.extend(quote_spanned!(span => #id: ref #arg,));
this.extend(quote!(#id: #arg,)); this.extend(quote_spanned!(span => #id: #arg,));
} }
let phantom = match &ast_data.fields { let phantom = match &ast_data.fields {
@ -560,7 +579,9 @@ fn variant_as_arm(
.unwrap(), .unwrap(),
Fields::Unnamed(_) | Fields::Unit => unreachable!(), Fields::Unnamed(_) | Fields::Unit => unreachable!(),
}; };
this.extend(quote!(#phantom: askama::helpers::core::marker::PhantomData {},)); this.extend(quote_spanned!(
span => #phantom: askama::helpers::core::marker::PhantomData {},
));
} }
Fields::Unnamed(fields) => { Fields::Unnamed(fields) => {
@ -568,27 +589,33 @@ fn variant_as_arm(
let span = field.ident.span(); let span = field.ident.span();
let arg = Ident::new(&format!("__askama_arg_{idx}"), span); let arg = Ident::new(&format!("__askama_arg_{idx}"), span);
let idx = syn::LitInt::new(&format!("{idx}"), span); let idx = syn::LitInt::new(&format!("{idx}"), span);
src.extend(quote!(#idx: ref #arg,)); src.extend(quote_spanned!(span => #idx: ref #arg,));
this.extend(quote!(#idx: #arg,)); this.extend(quote_spanned!(span => #idx: #arg,));
} }
let idx = syn::LitInt::new(&format!("{}", fields.unnamed.len()), span); let idx = syn::LitInt::new(&format!("{}", fields.unnamed.len()), span);
this.extend(quote!(#idx: askama::helpers::core::marker::PhantomData {},)); this.extend(
quote_spanned!(span => #idx: askama::helpers::core::marker::PhantomData {},),
);
} }
Fields::Unit => { Fields::Unit => {
this.extend(quote!(0: askama::helpers::core::marker::PhantomData {},)); this.extend(quote_spanned!(span => 0: askama::helpers::core::marker::PhantomData {},));
} }
}; };
render_into_arms.extend(quote! { let var_writer = crate::var_writer();
let var_values = crate::var_values();
render_into_arms.extend(quote_spanned! {
span =>
Self :: #ident { #src } => { Self :: #ident { #src } => {
<_ as askama::helpers::EnumVariantTemplate>::render_into_with_values( <_ as askama::helpers::EnumVariantTemplate>::render_into_with_values(
& #var_id #ty_generics { #this }, & #var_id #ty_generics { #this },
__askama_writer, #var_writer,
__askama_values, #var_values,
) )
} }
}); });
size_hint_arms.extend(quote! { size_hint_arms.extend(quote_spanned! {
span =>
Self :: #ident { .. } => { Self :: #ident { .. } => {
#size_hint #size_hint
} }

View File

@ -7,6 +7,7 @@ extern crate proc_macro;
#[macro_use] #[macro_use]
mod macros; mod macros;
mod ascii_str;
mod config; mod config;
mod generator; mod generator;
mod heritage; mod heritage;
@ -31,10 +32,11 @@ use std::hash::{BuildHasher, Hash};
use std::path::Path; use std::path::Path;
use std::sync::Mutex; use std::sync::Mutex;
use parser::{Parsed, ascii_str, strip_common}; use parser::{Parsed, is_rust_keyword, strip_common};
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Literal, Span, TokenStream};
use quote::quote; use quote::{ToTokens, quote, quote_spanned};
use rustc_hash::FxBuildHasher; use rustc_hash::FxBuildHasher;
use syn::Ident;
use syn::spanned::Spanned; use syn::spanned::Spanned;
use crate::config::{Config, read_config_file}; use crate::config::{Config, read_config_file};
@ -272,8 +274,20 @@ pub fn derive_template(input: TokenStream, import_askama: fn() -> TokenStream) -
Some(crate_name) => quote!(use #crate_name as askama;), Some(crate_name) => quote!(use #crate_name as askama;),
None => import_askama(), None => import_askama(),
}; };
quote! { quote_spanned! {
#[allow(dead_code, non_camel_case_types, non_snake_case)] ast.ident.span() =>
#[allow(
// We use `Struct { 0: arg0, 1: arg1 }` in enum specialization.
clippy::init_numbered_fields, non_shorthand_field_patterns,
// The generated code is not indented at all.
clippy::suspicious_else_formatting,
// We don't care if the user does not use `Template`, `FastWritable`, etc.
dead_code,
// We intentionally add extraneous underscores in type names.
non_camel_case_types,
// We intentionally add extraneous underscores in variable names.
non_snake_case,
)]
const _: () = { const _: () = {
#import_askama #import_askama
#ts #ts
@ -561,6 +575,54 @@ impl fmt::Display for MsgValidEscapers<'_> {
} }
} }
fn field_new(name: &str, span: proc_macro2::Span) -> TokenStream {
if name.starts_with(|c: char| c.is_ascii_digit()) {
let mut literal: Literal = name.parse().unwrap();
literal.set_span(span);
literal.into_token_stream()
} else if is_rust_keyword(name) && !matches!(name, "self" | "Self" | "crate" | "super") {
Ident::new_raw(name, span).into_token_stream()
} else {
Ident::new(name, span).into_token_stream()
}
}
fn var_writer() -> Ident {
syn::Ident::new("__askama_writer", proc_macro2::Span::mixed_site())
}
fn var_filter_source() -> Ident {
syn::Ident::new("__askama_filter_block", proc_macro2::Span::mixed_site())
}
fn var_values() -> Ident {
syn::Ident::new("__askama_values", proc_macro2::Span::mixed_site())
}
fn var_arg() -> Ident {
syn::Ident::new("__askama_arg", proc_macro2::Span::mixed_site())
}
fn var_item() -> Ident {
syn::Ident::new("__askama_item", proc_macro2::Span::mixed_site())
}
fn var_len() -> Ident {
syn::Ident::new("__askama_len", proc_macro2::Span::mixed_site())
}
fn var_iter() -> Ident {
syn::Ident::new("__askama_iter", proc_macro2::Span::mixed_site())
}
fn var_cycle() -> Ident {
syn::Ident::new("__askama_cycle", proc_macro2::Span::mixed_site())
}
fn var_did_loop() -> Ident {
syn::Ident::new("__askama_did_loop", proc_macro2::Span::mixed_site())
}
#[derive(Debug)] #[derive(Debug)]
struct OnceMap<K, V>([Mutex<HashMap<K, V, FxBuildHasher>>; 8]); struct OnceMap<K, V>([Mutex<HashMap<K, V, FxBuildHasher>>; 8]);

View File

@ -7,3 +7,11 @@ macro_rules! spanned {
// } // }
} }
} }
macro_rules! quote_into {
($buffer:expr, $span:expr, { $($x:tt)+ } $(,)?) => {{
let buffer: &mut $crate::integration::Buffer = $buffer;
let span: ::proc_macro2::Span = $span;
buffer.write_tokens(::quote::quote_spanned!(span => $($x)+));
}};
}

View File

@ -13,11 +13,11 @@ use crate::integration::Buffer;
use crate::{AnyTemplateArgs, derive_template}; use crate::{AnyTemplateArgs, derive_template};
#[track_caller] #[track_caller]
fn build_template(ast: &syn::DeriveInput) -> Result<String, crate::CompileError> { fn build_template(ast: &syn::DeriveInput) -> Result<TokenStream, crate::CompileError> {
let mut buf = Buffer::new(); let mut buf = Buffer::new();
let args = AnyTemplateArgs::new(ast)?; let args = AnyTemplateArgs::new(ast)?;
crate::build_template(&mut buf, ast, args)?; crate::build_template(&mut buf, ast, args)?;
Ok(buf.to_string()) Ok(buf.into_token_stream())
} }
fn import_askama() -> TokenStream { fn import_askama() -> TokenStream {
@ -41,10 +41,11 @@ fn compare_ex(
size_hint: usize, size_hint: usize,
prefix: &str, prefix: &str,
) { ) {
let generated = jinja_to_rust(jinja, fields, prefix).unwrap(); let generated = jinja_to_rust(jinja, fields, prefix);
let expected: TokenStream = expected.parse().unwrap(); let expected: TokenStream = expected.parse().unwrap();
let expected: syn::File = syn::parse_quote! { let expected: syn::File = syn::parse_quote! {
#[automatically_derived]
impl askama::Template for Foo { impl askama::Template for Foo {
fn render_into_with_values<AskamaW>( fn render_into_with_values<AskamaW>(
&self, &self,
@ -68,6 +69,7 @@ fn compare_ex(
/// Implement the [`format!()`][askama::helpers::std::format] trait for [`Foo`] /// Implement the [`format!()`][askama::helpers::std::format] trait for [`Foo`]
/// ///
/// Please be aware of the rendering performance notice in the [`Template`][askama::Template] trait. /// Please be aware of the rendering performance notice in the [`Template`][askama::Template] trait.
#[automatically_derived]
impl askama::helpers::core::fmt::Display for Foo { impl askama::helpers::core::fmt::Display for Foo {
#[inline] #[inline]
fn fmt(&self, f: &mut askama::helpers::core::fmt::Formatter<'_>) -> askama::helpers::core::fmt::Result { fn fmt(&self, f: &mut askama::helpers::core::fmt::Formatter<'_>) -> askama::helpers::core::fmt::Result {
@ -75,6 +77,7 @@ fn compare_ex(
} }
} }
#[automatically_derived]
impl askama::FastWritable for Foo { impl askama::FastWritable for Foo {
#[inline] #[inline]
fn write_into<AskamaW>( fn write_into<AskamaW>(
@ -143,7 +146,7 @@ fn compare_ex(
} }
} }
fn jinja_to_rust(jinja: &str, fields: &[(&str, &str)], prefix: &str) -> syn::Result<syn::File> { fn jinja_to_rust(jinja: &str, fields: &[(&str, &str)], prefix: &str) -> syn::File {
let jinja = format!( let jinja = format!(
r##"#[template(source = {jinja:?}, ext = "txt")] r##"#[template(source = {jinja:?}, ext = "txt")]
{prefix} {prefix}
@ -156,7 +159,7 @@ struct Foo {{ {} }}"##,
); );
let generated = build_template(&syn::parse_str::<syn::DeriveInput>(&jinja).unwrap()).unwrap(); let generated = build_template(&syn::parse_str::<syn::DeriveInput>(&jinja).unwrap()).unwrap();
let generated = match generated.parse() { match syn::parse2(generated.clone()) {
Ok(generated) => generated, Ok(generated) => generated,
Err(err) => panic!( Err(err) => panic!(
"\n\ "\n\
@ -168,8 +171,7 @@ struct Foo {{ {} }}"##,
\n\ \n\
{err}" {err}"
), ),
}; }
syn::parse2(generated)
} }
#[test] #[test]
@ -822,7 +824,7 @@ fn test_code_in_comment() {
struct Tmpl; struct Tmpl;
"#; "#;
let ast = syn::parse_str(ts).unwrap(); let ast = syn::parse_str(ts).unwrap();
let generated = build_template(&ast).unwrap(); let generated = build_template(&ast).unwrap().to_string();
assert!(generated.contains("Hello world!")); assert!(generated.contains("Hello world!"));
assert!(!generated.contains("compile_error")); assert!(!generated.contains("compile_error"));
@ -835,7 +837,7 @@ fn test_code_in_comment() {
struct Tmpl; struct Tmpl;
"#; "#;
let ast = syn::parse_str(ts).unwrap(); let ast = syn::parse_str(ts).unwrap();
let generated = build_template(&ast).unwrap(); let generated = build_template(&ast).unwrap().to_string();
assert!(generated.contains("Hello\nworld!")); assert!(generated.contains("Hello\nworld!"));
assert!(!generated.contains("compile_error")); assert!(!generated.contains("compile_error"));
@ -848,7 +850,7 @@ fn test_code_in_comment() {
struct Tmpl; struct Tmpl;
"#; "#;
let ast = syn::parse_str(ts).unwrap(); let ast = syn::parse_str(ts).unwrap();
let generated = build_template(&ast).unwrap(); let generated = build_template(&ast).unwrap().to_string();
assert!(generated.contains("Hello\nworld!")); assert!(generated.contains("Hello\nworld!"));
assert!(!generated.contains("compile_error")); assert!(!generated.contains("compile_error"));
@ -865,7 +867,7 @@ fn test_code_in_comment() {
struct Tmpl; struct Tmpl;
"#; "#;
let ast = syn::parse_str(ts).unwrap(); let ast = syn::parse_str(ts).unwrap();
let generated = build_template(&ast).unwrap(); let generated = build_template(&ast).unwrap().to_string();
assert!(generated.contains("Hello\nworld!")); assert!(generated.contains("Hello\nworld!"));
assert!(!generated.contains("compile_error")); assert!(!generated.contains("compile_error"));
@ -875,7 +877,7 @@ fn test_code_in_comment() {
struct Tmpl; struct Tmpl;
"; ";
let ast = syn::parse_str(ts).unwrap(); let ast = syn::parse_str(ts).unwrap();
let generated = build_template(&ast).unwrap(); let generated = build_template(&ast).unwrap().to_string();
assert!(generated.contains("Hello\nworld!")); assert!(generated.contains("Hello\nworld!"));
assert!(!generated.contains("compile_error")); assert!(!generated.contains("compile_error"));
@ -906,7 +908,7 @@ fn test_code_in_comment() {
struct BlockOnBlock; struct BlockOnBlock;
"; ";
let ast = syn::parse_str(ts).unwrap(); let ast = syn::parse_str(ts).unwrap();
let generated = build_template(&ast).unwrap(); let generated = build_template(&ast).unwrap().to_string();
assert!(!generated.contains("compile_error")); assert!(!generated.contains("compile_error"));
} }
@ -1147,12 +1149,12 @@ fn test_concat() {
fn extends_with_whitespace_control() { fn extends_with_whitespace_control() {
const CONTROL: &[&str] = &["", "\t", "-", "+", "~"]; const CONTROL: &[&str] = &["", "\t", "-", "+", "~"];
let expected = jinja_to_rust(r#"front {% extends "a.html" %} back"#, &[], "").unwrap(); let expected = jinja_to_rust(r#"front {% extends "a.html" %} back"#, &[], "");
let expected = unparse(&expected); let expected = unparse(&expected);
for front in CONTROL { for front in CONTROL {
for back in CONTROL { for back in CONTROL {
let src = format!(r#"front {{%{front} extends "a.html" {back}%}} back"#); let src = format!(r#"front {{%{front} extends "a.html" {back}%}} back"#);
let actual = jinja_to_rust(&src, &[], "").unwrap(); let actual = jinja_to_rust(&src, &[], "");
let actual = unparse(&actual); let actual = unparse(&actual);
assert_eq!(expected, actual, "source: {src:?}"); assert_eq!(expected, actual, "source: {src:?}");
} }

View File

@ -3,7 +3,7 @@
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
#![allow(clippy::vec_box)] // intentional, less copying #![allow(clippy::vec_box)] // intentional, less copying
pub mod ascii_str; mod ascii_str;
pub mod expr; pub mod expr;
pub mod node; pub mod node;
mod target; mod target;
@ -1344,139 +1344,95 @@ const PRIMITIVE_TYPES: &[&str] = &{
list list
}; };
pub const MAX_RUST_KEYWORD_LEN: usize = 8; const MAX_RUST_KEYWORD_LEN: usize = 8;
pub const MAX_RUST_RAW_KEYWORD_LEN: usize = MAX_RUST_KEYWORD_LEN + 2;
pub const RUST_KEYWORDS: &[&[[AsciiChar; MAX_RUST_RAW_KEYWORD_LEN]]; MAX_RUST_KEYWORD_LEN + 1] = &{ const RUST_KEYWORDS: &[&[[AsciiChar; MAX_RUST_KEYWORD_LEN]]; MAX_RUST_KEYWORD_LEN + 1] = &{
const NO_KWS: &[[AsciiChar; MAX_RUST_RAW_KEYWORD_LEN]] = &[]; const NO_KWS: &[[AsciiChar; MAX_RUST_KEYWORD_LEN]] = &[];
const KW2: &[[AsciiChar; MAX_RUST_RAW_KEYWORD_LEN]] = &[ const KW2: &[[AsciiChar; MAX_RUST_KEYWORD_LEN]] = &[
AsciiStr::new_sized("r#as"), AsciiStr::new_sized("as"),
AsciiStr::new_sized("r#do"), AsciiStr::new_sized("do"),
AsciiStr::new_sized("r#fn"), AsciiStr::new_sized("fn"),
AsciiStr::new_sized("r#if"), AsciiStr::new_sized("if"),
AsciiStr::new_sized("r#in"), AsciiStr::new_sized("in"),
]; ];
const KW3: &[[AsciiChar; MAX_RUST_RAW_KEYWORD_LEN]] = &[ const KW3: &[[AsciiChar; MAX_RUST_KEYWORD_LEN]] = &[
AsciiStr::new_sized("r#box"), AsciiStr::new_sized("box"),
AsciiStr::new_sized("r#dyn"), AsciiStr::new_sized("dyn"),
AsciiStr::new_sized("r#for"), AsciiStr::new_sized("for"),
AsciiStr::new_sized("r#gen"), AsciiStr::new_sized("gen"),
AsciiStr::new_sized("r#let"), AsciiStr::new_sized("let"),
AsciiStr::new_sized("r#mod"), AsciiStr::new_sized("mod"),
AsciiStr::new_sized("r#mut"), AsciiStr::new_sized("mut"),
AsciiStr::new_sized("r#pub"), AsciiStr::new_sized("pub"),
AsciiStr::new_sized("r#ref"), AsciiStr::new_sized("ref"),
AsciiStr::new_sized("r#try"), AsciiStr::new_sized("try"),
AsciiStr::new_sized("r#use"), AsciiStr::new_sized("use"),
]; ];
const KW4: &[[AsciiChar; MAX_RUST_RAW_KEYWORD_LEN]] = &[ const KW4: &[[AsciiChar; MAX_RUST_KEYWORD_LEN]] = &[
AsciiStr::new_sized("r#else"), AsciiStr::new_sized("else"),
AsciiStr::new_sized("r#enum"), AsciiStr::new_sized("enum"),
AsciiStr::new_sized("r#impl"), AsciiStr::new_sized("impl"),
AsciiStr::new_sized("r#move"), AsciiStr::new_sized("loop"),
AsciiStr::new_sized("r#priv"), AsciiStr::new_sized("move"),
AsciiStr::new_sized("r#true"), AsciiStr::new_sized("priv"),
AsciiStr::new_sized("r#type"), AsciiStr::new_sized("self"),
AsciiStr::new_sized("Self"),
AsciiStr::new_sized("true"),
AsciiStr::new_sized("type"),
]; ];
const KW5: &[[AsciiChar; MAX_RUST_RAW_KEYWORD_LEN]] = &[ const KW5: &[[AsciiChar; MAX_RUST_KEYWORD_LEN]] = &[
AsciiStr::new_sized("r#async"), AsciiStr::new_sized("async"),
AsciiStr::new_sized("r#await"), AsciiStr::new_sized("await"),
AsciiStr::new_sized("r#break"), AsciiStr::new_sized("break"),
AsciiStr::new_sized("r#const"), AsciiStr::new_sized("const"),
AsciiStr::new_sized("r#crate"), AsciiStr::new_sized("crate"),
AsciiStr::new_sized("r#false"), AsciiStr::new_sized("false"),
AsciiStr::new_sized("r#final"), AsciiStr::new_sized("final"),
AsciiStr::new_sized("r#macro"), AsciiStr::new_sized("macro"),
AsciiStr::new_sized("r#match"), AsciiStr::new_sized("match"),
AsciiStr::new_sized("r#trait"), AsciiStr::new_sized("super"),
AsciiStr::new_sized("r#where"), AsciiStr::new_sized("trait"),
AsciiStr::new_sized("r#while"), AsciiStr::new_sized("union"),
AsciiStr::new_sized("r#yield"), AsciiStr::new_sized("where"),
AsciiStr::new_sized("while"),
AsciiStr::new_sized("yield"),
]; ];
const KW6: &[[AsciiChar; MAX_RUST_RAW_KEYWORD_LEN]] = &[ const KW6: &[[AsciiChar; MAX_RUST_KEYWORD_LEN]] = &[
AsciiStr::new_sized("r#become"), AsciiStr::new_sized("become"),
AsciiStr::new_sized("r#extern"), AsciiStr::new_sized("extern"),
AsciiStr::new_sized("r#return"), AsciiStr::new_sized("return"),
AsciiStr::new_sized("r#static"), AsciiStr::new_sized("static"),
AsciiStr::new_sized("r#struct"), AsciiStr::new_sized("struct"),
AsciiStr::new_sized("r#typeof"), AsciiStr::new_sized("typeof"),
AsciiStr::new_sized("r#unsafe"), AsciiStr::new_sized("unsafe"),
AsciiStr::new_sized("caller"),
]; ];
const KW7: &[[AsciiChar; MAX_RUST_RAW_KEYWORD_LEN]] = &[ const KW7: &[[AsciiChar; MAX_RUST_KEYWORD_LEN]] = &[
AsciiStr::new_sized("r#unsized"), AsciiStr::new_sized("unsized"),
AsciiStr::new_sized("r#virtual"), AsciiStr::new_sized("virtual"),
]; ];
const KW8: &[[AsciiChar; MAX_RUST_RAW_KEYWORD_LEN]] = &[ const KW8: &[[AsciiChar; MAX_RUST_KEYWORD_LEN]] = &[
AsciiStr::new_sized("r#abstract"), AsciiStr::new_sized("abstract"),
AsciiStr::new_sized("r#continue"), AsciiStr::new_sized("continue"),
AsciiStr::new_sized("r#override"), AsciiStr::new_sized("override"),
]; ];
[NO_KWS, NO_KWS, KW2, KW3, KW4, KW5, KW6, KW7, KW8] [NO_KWS, NO_KWS, KW2, KW3, KW4, KW5, KW6, KW7, KW8]
}; };
// These ones are only used in the parser, hence why they're private. pub fn is_rust_keyword(ident: &str) -> bool {
const KWS_PARSER: &[&[[AsciiChar; MAX_RUST_RAW_KEYWORD_LEN]]; MAX_RUST_KEYWORD_LEN + 1] = &{
const KW4: &[[AsciiChar; MAX_RUST_RAW_KEYWORD_LEN]] = &{
let mut result = [AsciiStr::new_sized("r#"); RUST_KEYWORDS[4].len() + 3];
let mut i = 0;
while i < RUST_KEYWORDS[4].len() {
result[i] = RUST_KEYWORDS[4][i];
i += 1;
}
result[result.len() - 3] = AsciiStr::new_sized("r#loop");
result[result.len() - 2] = AsciiStr::new_sized("r#self");
result[result.len() - 1] = AsciiStr::new_sized("r#Self");
result
};
const KW5: &[[AsciiChar; MAX_RUST_RAW_KEYWORD_LEN]] = &{
let mut result = [AsciiStr::new_sized("r#"); RUST_KEYWORDS[5].len() + 2];
let mut i = 0;
while i < RUST_KEYWORDS[5].len() {
result[i] = RUST_KEYWORDS[5][i];
i += 1;
}
result[result.len() - 2] = AsciiStr::new_sized("r#super");
result[result.len() - 1] = AsciiStr::new_sized("r#union");
result
};
const KW6: &[[AsciiChar; MAX_RUST_RAW_KEYWORD_LEN]] = &{
let mut result = [AsciiStr::new_sized("r#"); RUST_KEYWORDS[6].len() + 1];
let mut i = 0;
while i < RUST_KEYWORDS[6].len() {
result[i] = RUST_KEYWORDS[6][i];
i += 1;
}
result[result.len() - 1] = AsciiStr::new_sized("r#caller");
result
};
[
RUST_KEYWORDS[0],
RUST_KEYWORDS[1],
RUST_KEYWORDS[2],
RUST_KEYWORDS[3],
KW4,
KW5,
KW6,
RUST_KEYWORDS[7],
RUST_KEYWORDS[8],
]
};
fn is_rust_keyword(ident: &str) -> bool {
let ident_len = ident.len(); let ident_len = ident.len();
if ident_len > MAX_RUST_KEYWORD_LEN { if ident_len > MAX_RUST_KEYWORD_LEN {
return false; return false;
} }
let kws = KWS_PARSER[ident.len()]; let kws = RUST_KEYWORDS[ident.len()];
let mut padded_ident = [0; MAX_RUST_KEYWORD_LEN]; let mut padded_ident = [0; MAX_RUST_KEYWORD_LEN];
padded_ident[..ident_len].copy_from_slice(ident.as_bytes()); 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. // Since the individual buckets are quite short, a linear search is faster than a binary search.
for probe in kws { for probe in kws {
if padded_ident == *AsciiChar::slice_as_bytes(probe[2..].try_into().unwrap()) { if padded_ident == *AsciiChar::slice_as_bytes(probe) {
return true; return true;
} }
} }

View File

@ -275,6 +275,7 @@ fn filter_block_conditions() {
// The output of `|upper` is not marked as `|safe`, so the output of `|paragraphbreaks` gets // The output of `|upper` is not marked as `|safe`, so the output of `|paragraphbreaks` gets
// escaped. The '&' in the input is is not marked as `|safe`, so it should get escaped, twice. // escaped. The '&' in the input is is not marked as `|safe`, so it should get escaped, twice.
#[test] #[test]
#[expect(unused_variables)] // `canary` inside the filter block is intentionally unused
fn filter_nested_filter_blocks() { fn filter_nested_filter_blocks() {
#[derive(Template)] #[derive(Template)]
#[template( #[template(

View File

@ -33,6 +33,7 @@ fn underscore_ident1() {
} }
// Ensures that variables can be named `_`. // Ensures that variables can be named `_`.
#[expect(clippy::redundant_pattern_matching)] // We want to test if `Some(_)` is recognized.
#[test] #[test]
fn underscore_ident2() { fn underscore_ident2() {
#[derive(Template)] #[derive(Template)]