use std::borrow::Cow;
use std::collections::hash_map::Entry;
use std::fmt::Debug;
use std::mem;
use std::ops::ControlFlow;
use std::str::FromStr;
use parser::expr::BinOp;
use parser::node::{
Call, Comment, Compound, Cond, CondTest, Declare, FilterBlock, If, Include, Let, Lit, Loop,
Match, Whitespace, Ws,
};
use parser::{Expr, LetValueOrBlock, Node, Span, Target, WithSpan};
use proc_macro2::TokenStream;
use quote::quote_spanned;
use rustc_hash::FxBuildHasher;
use syn::Token;
use super::{
DisplayWrap, Generator, LocalMeta, MapChain, RenderFor, compile_time_escape, is_copyable,
};
use crate::generator::{LocalCallerMeta, Writable, helpers, logic_op};
use crate::heritage::{Context, Heritage};
use crate::integration::{Buffer, string_escape};
use crate::{
CompileError, FileInfo, HashMap, SizeHint, field_new, fmt_left, fmt_right, quote_into,
};
impl<'a> Generator<'a, '_> {
pub(super) fn impl_template_inner(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
) -> Result<SizeHint, CompileError> {
buf.set_discard(self.buf_writable.discard);
let size_hint = if let Some(heritage) = self.heritage {
let _: SizeHint =
self.handle(ctx, ctx.nodes, buf, AstLevel::Top, RenderFor::Extends)?;
self.next_ws = None;
self.handle(
heritage.root,
heritage.root.nodes,
buf,
AstLevel::Top,
RenderFor::Template,
)
} else {
self.handle(ctx, ctx.nodes, buf, AstLevel::Top, RenderFor::Template)
}?;
self.flush_ws(Ws(None, None));
buf.set_discard(false);
Ok(size_hint)
}
pub(crate) fn push_locals<T, F>(&mut self, callback: F) -> Result<T, CompileError>
where
F: FnOnce(&mut Self) -> Result<T, CompileError>,
{
self.locals.stack_push();
let res = callback(self);
self.locals.stack_pop();
res
}
fn with_child<'b, T, F>(
&mut self,
heritage: Option<&'b Heritage<'a, 'b>>,
callback: F,
) -> Result<T, CompileError>
where
F: FnOnce(&mut Generator<'a, 'b>) -> Result<T, CompileError>,
{
self.locals.stack_push();
let buf_writable = mem::take(&mut self.buf_writable);
let locals = mem::replace(&mut self.locals, MapChain::new_empty());
let mut child = Generator::new(
self.input,
self.contexts,
heritage,
locals,
self.buf_writable.discard,
self.is_in_block,
);
child.buf_writable = buf_writable;
let res = callback(&mut child);
Generator {
locals: self.locals,
buf_writable: self.buf_writable,
..
} = child;
self.locals.stack_pop();
res
}
pub(crate) fn handle(
&mut self,
ctx: &Context<'a>,
nodes: &'a [Box<Node<'_>>],
buf: &mut Buffer,
level: AstLevel,
render_for: RenderFor,
) -> Result<SizeHint, CompileError> {
let mut size_hint = SizeHint::EMPTY;
for n in nodes {
match **n {
Node::Lit(ref lit) => {
if render_for == RenderFor::Template {
self.write_lit(lit);
}
}
Node::Comment(ref comment) => {
if render_for == RenderFor::Template {
self.write_comment(comment);
}
}
Node::Expr(ws, ref val) => {
size_hint += self.write_expr(ctx, buf, ws, val, render_for)?;
}
Node::Let(ref l) => {
size_hint += self.write_let(ctx, buf, l)?;
}
Node::Compound(ref c) => {
self.write_compound(ctx, buf, c)?;
}
Node::Declare(ref c) => {
size_hint += self.write_decl(ctx, buf, c)?;
}
Node::If(ref i) => {
size_hint += self.write_if(ctx, buf, i, render_for)?;
}
Node::Match(ref m) => {
size_hint += self.write_match(ctx, buf, m, render_for)?;
}
Node::Loop(ref loop_block) => {
size_hint += self.write_loop(ctx, buf, loop_block, render_for)?;
}
Node::BlockDef(ref b) => {
if render_for == RenderFor::Template {
size_hint += self.write_block(
ctx,
buf,
Some(b.name),
Ws(b.ws1.0, b.ws2.1),
b.span(),
)?;
}
}
Node::Include(ref i) => {
size_hint += self.handle_include(ctx, buf, i, render_for)?;
}
Node::Call(ref call) => {
size_hint += self.write_call(ctx, buf, call, render_for)?;
}
Node::FilterBlock(ref filter) => {
size_hint += self.write_filter_block(ctx, buf, filter)?;
}
Node::Macro(ref m) => {
if level != AstLevel::Top {
return Err(ctx.generate_error(
"macro blocks only allowed at the top level",
m.span(),
));
}
self.flush_ws(m.ws1);
self.prepare_ws(m.ws2);
}
Node::Raw(ref raw) => {
if render_for == RenderFor::Template {
self.handle_ws(raw.ws1);
self.write_lit(&raw.lit);
self.handle_ws(raw.ws2);
}
}
Node::Import(ref i) => {
if level != AstLevel::Top {
return Err(ctx.generate_error(
"import blocks only allowed at the top level",
i.span(),
));
}
self.handle_ws(i.ws);
}
Node::Extends(ref e) => {
if level != AstLevel::Top {
return Err(ctx.generate_error(
"extends blocks only allowed at the top level",
e.span(),
));
}
}
Node::Break(ref ws) => {
self.handle_ws(**ws);
size_hint += self.write_buf_writable(ctx, buf)?;
quote_into!(buf, ctx.span_for_node(ws.span()), {
break;
});
}
Node::Continue(ref ws) => {
self.handle_ws(**ws);
size_hint += self.write_buf_writable(ctx, buf)?;
quote_into!(buf, ctx.span_for_node(ws.span()), {
continue;
});
}
}
}
if AstLevel::Top == level {
if self.next_ws.is_some() {
self.flush_ws(Ws(Some(self.skip_ws), None));
}
size_hint += self.write_buf_writable(ctx, buf)?;
}
Ok(size_hint)
}
fn evaluate_condition(
&self,
expr: WithSpan<Box<Expr<'a>>>,
only_contains_is_defined: &mut bool,
) -> EvaluatedResult<'a> {
let (expr, span) = expr.deconstruct();
match *expr {
Expr::NumLit(_, _)
| Expr::StrLit(_)
| Expr::CharLit(_)
| Expr::Var(_)
| Expr::Path(_)
| Expr::Array(_)
| Expr::ArrayRepeat(_, _)
| Expr::AssociatedItem(_, _)
| Expr::Index(_, _)
| Expr::Filter(_)
| Expr::Range(_)
| Expr::Call { .. }
| Expr::Struct(_)
| Expr::RustMacro(_, _)
| Expr::Try(_)
| Expr::Tuple(_)
| Expr::NamedArgument(_, _)
| Expr::FilterSource
| Expr::As(_, _)
| Expr::Concat(_)
| Expr::LetCond(_)
| Expr::ArgumentPlaceholder => {
*only_contains_is_defined = false;
EvaluatedResult::Unknown(WithSpan::new(expr, span))
}
Expr::BoolLit(true) => EvaluatedResult::AlwaysTrue,
Expr::BoolLit(false) => EvaluatedResult::AlwaysFalse,
Expr::Unary("!", inner) => {
match self.evaluate_condition(inner, only_contains_is_defined) {
EvaluatedResult::AlwaysTrue => EvaluatedResult::AlwaysFalse,
EvaluatedResult::AlwaysFalse => EvaluatedResult::AlwaysTrue,
EvaluatedResult::Unknown(expr) => EvaluatedResult::Unknown(WithSpan::new(
Box::new(Expr::Unary("!", expr)),
span,
)),
}
}
Expr::Unary(_, _) => EvaluatedResult::Unknown(WithSpan::new(expr, span)),
Expr::BinOp(v) if v.op == "&&" => {
let lhs = match self.evaluate_condition(v.lhs, only_contains_is_defined) {
EvaluatedResult::AlwaysTrue => {
return self.evaluate_condition(v.rhs, only_contains_is_defined);
}
EvaluatedResult::AlwaysFalse => {
return EvaluatedResult::AlwaysFalse;
}
EvaluatedResult::Unknown(lhs) => lhs,
};
match self.evaluate_condition(v.rhs, only_contains_is_defined) {
EvaluatedResult::AlwaysTrue => {
EvaluatedResult::Unknown(lhs)
}
EvaluatedResult::AlwaysFalse => {
let rhs = WithSpan::no_span(Box::new(Expr::BoolLit(false)));
EvaluatedResult::Unknown(bin_op(span, v.op, lhs, rhs))
}
EvaluatedResult::Unknown(rhs) => {
EvaluatedResult::Unknown(bin_op(span, v.op, lhs, rhs))
}
}
}
Expr::BinOp(v) if v.op == "||" => {
let lhs = match self.evaluate_condition(v.lhs, only_contains_is_defined) {
EvaluatedResult::AlwaysTrue => {
return EvaluatedResult::AlwaysTrue;
}
EvaluatedResult::AlwaysFalse => {
return self.evaluate_condition(v.rhs, only_contains_is_defined);
}
EvaluatedResult::Unknown(lhs) => lhs,
};
match self.evaluate_condition(v.rhs, only_contains_is_defined) {
EvaluatedResult::AlwaysTrue => {
let rhs = WithSpan::no_span(Box::new(Expr::BoolLit(true)));
EvaluatedResult::Unknown(bin_op(span, v.op, lhs, rhs))
}
EvaluatedResult::AlwaysFalse => {
EvaluatedResult::Unknown(lhs)
}
EvaluatedResult::Unknown(rhs) => {
EvaluatedResult::Unknown(bin_op(span, v.op, lhs, rhs))
}
}
}
Expr::BinOp(_) => {
*only_contains_is_defined = false;
EvaluatedResult::Unknown(WithSpan::new(expr, span))
}
Expr::Group(inner) => match self.evaluate_condition(inner, only_contains_is_defined) {
EvaluatedResult::Unknown(expr) => {
EvaluatedResult::Unknown(WithSpan::new(Box::new(Expr::Group(expr)), span))
}
known => known,
},
Expr::IsDefined(left) => {
if self.is_var_defined(left) {
EvaluatedResult::AlwaysTrue
} else {
EvaluatedResult::AlwaysFalse
}
}
Expr::IsNotDefined(left) => {
if self.is_var_defined(left) {
EvaluatedResult::AlwaysFalse
} else {
EvaluatedResult::AlwaysTrue
}
}
}
}
fn write_if(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
if_: &'a If<'_>,
render_for: RenderFor,
) -> Result<SizeHint, CompileError> {
let mut flushed = SizeHint::EMPTY;
let mut arm_sizes = Vec::new();
let mut has_else = false;
let conds = Conds::compute_branches(self, if_);
if let Some(ws_before) = conds.ws_before {
self.handle_ws(ws_before);
}
let mut iter = conds.conds.iter().enumerate().peekable();
while let Some((pos, cond_info)) = iter.next() {
let cond = cond_info.cond;
if pos == 0 {
self.handle_ws(cond.ws);
flushed += self.write_buf_writable(ctx, buf)?;
}
self.push_locals(|this| {
let mut has_cond = true;
if let Some(CondTest { target, expr, .. }) = cond.cond.as_deref() {
let expr = cond_info.cond_expr.as_ref().unwrap_or(expr);
let expr_span = ctx.span_for_node(expr.span());
if pos == 0 {
if cond_info.generate_condition {
buf.write_token(Token![if], expr_span);
} else {
has_cond = false;
}
} else if cond_info.generate_condition {
quote_into!(buf, expr_span, { else if });
} else {
buf.write_token(Token![else], expr_span);
has_else = true;
}
if let Some(target) = target {
let mut expr_buf = Buffer::new();
let target_span = ctx.span_for_node(target.span());
buf.write_token(Token![let], target_span);
match &***expr {
Expr::BinOp(v) if matches!(v.op, "||" | "&&") => {
let display_wrap =
this.visit_expr_first(ctx, &mut expr_buf, &v.lhs)?;
this.visit_target(ctx, buf, true, true, target, expr_span);
this.visit_expr_not_first(
ctx,
&mut expr_buf,
&v.lhs,
display_wrap,
)?;
let op = logic_op(v.op, expr_span);
quote_into!(buf, expr_span, { = &#expr_buf #op });
this.visit_condition(ctx, buf, &v.rhs)?;
}
_ => {
let display_wrap =
this.visit_expr_first(ctx, &mut expr_buf, expr)?;
this.visit_target(ctx, buf, true, true, target, expr_span);
this.visit_expr_not_first(ctx, &mut expr_buf, expr, display_wrap)?;
quote_into!(buf, target_span, { = &#expr_buf });
}
}
} else if cond_info.generate_condition {
this.visit_condition(ctx, buf, expr)?;
}
} else if pos != 0 {
buf.write_token(Token![else], ctx.span_for_node(cond.span()));
has_else = true;
} else {
has_cond = false;
}
let mut block_buf = Buffer::new();
if cond_info.generate_content {
arm_sizes.push(this.handle(
ctx,
&cond.nodes,
&mut block_buf,
AstLevel::Nested,
render_for,
)?);
}
if let Some((_, cond_info)) = iter.peek() {
let cond = cond_info.cond;
this.handle_ws(cond.ws);
flushed += this.write_buf_writable(ctx, &mut block_buf)?;
} else {
if let Some(ws_after) = conds.ws_after {
this.handle_ws(ws_after);
}
this.handle_ws(if_.ws);
flushed += this.write_buf_writable(ctx, &mut block_buf)?;
}
if has_cond {
let block_buf = block_buf.into_token_stream();
quote_into!(buf, ctx.span_for_node(cond.span()), { { #block_buf } });
} else {
buf.write_buf(block_buf);
}
Ok(0)
})?;
}
if !has_else && !conds.conds.is_empty() {
arm_sizes.push(SizeHint::EMPTY);
}
Ok(flushed + median(&mut arm_sizes))
}
#[allow(clippy::too_many_arguments)]
fn write_match(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
m: &'a Match<'a>,
render_for: RenderFor,
) -> Result<SizeHint, CompileError> {
let Match {
ws1,
ref expr,
ref arms,
ws2,
} = *m;
self.flush_ws(ws1);
let flushed = self.write_buf_writable(ctx, buf)?;
let mut arm_sizes = Vec::new();
let expr_code = self.visit_expr_root(ctx, expr)?;
let span = ctx.span_for_node(expr.span());
let mut arm_size = SizeHint::EMPTY;
let mut iter = arms.iter().enumerate().peekable();
let mut arms = Buffer::new();
while let Some((i, arm)) = iter.next() {
if i == 0 {
self.handle_ws(arm.ws);
}
self.push_locals(|this| {
let mut targets_buf = Buffer::new();
for (index, target) in arm.target.iter().enumerate() {
if index != 0 {
targets_buf.write_token(Token![|], span);
}
this.visit_target(ctx, &mut targets_buf, true, true, target, span);
}
let mut arm_buf = Buffer::new();
arm_size =
this.handle(ctx, &arm.nodes, &mut arm_buf, AstLevel::Nested, render_for)?;
if let Some((_, arm)) = iter.peek() {
this.handle_ws(arm.ws);
arm_sizes.push(arm_size + this.write_buf_writable(ctx, &mut arm_buf)?);
} else {
this.handle_ws(ws2);
arm_sizes.push(arm_size + this.write_buf_writable(ctx, &mut arm_buf)?);
}
let targets_buf = targets_buf.into_token_stream();
let arm_buf = arm_buf.into_token_stream();
quote_into!(&mut arms, span, { #targets_buf => { #arm_buf } });
Ok(0)
})?;
}
let arms = arms.into_token_stream();
quote_into!(buf, span, { match &#expr_code { #arms } });
Ok(flushed + median(&mut arm_sizes))
}
fn write_loop(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
loop_block: &'a WithSpan<Loop<'_>>,
render_for: RenderFor,
) -> Result<SizeHint, CompileError> {
self.handle_ws(loop_block.ws1);
let span = ctx.span_for_node(loop_block.span());
self.push_locals(|this| {
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 mut loop_buf = Buffer::new();
if has_else_nodes {
quote_into!(&mut loop_buf, span, { let mut #var_did_loop = false; });
}
quote_into!(&mut loop_buf, span, { let #var_iter = });
this.visit_loop_iter(ctx, &mut loop_buf, &loop_block.iter)?;
loop_buf.write_token(Token![;], span);
if let Some(cond) = &loop_block.cond {
this.push_locals(|this| {
let mut target_buf = Buffer::new();
this.visit_target(ctx, &mut target_buf, true, true, &loop_block.var, span);
let target_buf = target_buf.into_token_stream();
let mut expr_buf = Buffer::new();
this.visit_expr(ctx, &mut expr_buf, cond)?;
let expr_buf = expr_buf.into_token_stream();
quote_into!(
&mut loop_buf,
span,
{
let #var_iter = #var_iter.filter(
|#target_buf| -> askama::helpers::core::primitive::bool {
#expr_buf
}
);
}
);
Ok(0)
})?;
}
let mut then_size_hint = this.push_locals(|this| {
let mut target_buf = Buffer::new();
this.visit_target(ctx, &mut target_buf, true, true, &loop_block.var, span);
let target_buf = target_buf.into_token_stream();
let mut loop_body_buf = Buffer::new();
if has_else_nodes {
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,
render_for,
)?;
this.handle_ws(loop_block.ws2);
size_hint1 += this.write_buf_writable(ctx, &mut loop_body_buf)?;
let loop_body_buf = loop_body_buf.into_token_stream();
quote_into!(&mut loop_buf, span, {
for (#target_buf, #var_item) in askama::helpers::TemplateLoop::new(#var_iter) {
#loop_body_buf
}
});
Ok(size_hint1)
})?;
let else_size_hint = if has_else_nodes {
let mut cond_buf = Buffer::new();
let else_size_hint = this.push_locals(|this| {
let mut size_hint = this.handle(
ctx,
&loop_block.else_nodes,
&mut cond_buf,
AstLevel::Nested,
render_for,
)?;
this.handle_ws(loop_block.ws3);
size_hint += this.write_buf_writable(ctx, &mut cond_buf)?;
Ok(size_hint)
})?;
let cond_buf = cond_buf.into_token_stream();
quote_into!(&mut loop_buf, span, {
if !#var_did_loop {
#cond_buf
}
});
else_size_hint
} else {
this.handle_ws(loop_block.ws3);
then_size_hint += this.write_buf_writable(ctx, &mut loop_buf)?;
SizeHint::EMPTY
};
buf.write_tokens(loop_buf.into_token_stream());
Ok(flushed + (then_size_hint * 3).midpoint(else_size_hint))
})
}
fn write_call(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
call: &'a WithSpan<Call<'_>>,
render_for: RenderFor,
) -> Result<SizeHint, CompileError> {
let Call {
ws1,
scope,
name,
ref args,
ws2,
..
} = **call;
let (def, own_ctx) = if let Some(s) = scope {
let path = ctx.imports.get(*s).ok_or_else(|| {
ctx.generate_error(
format_args!("no import found for scope `{}`", s.escape_debug()),
call.span(),
)
})?;
let mctx = self.contexts.get(path).ok_or_else(|| {
ctx.generate_error(
format_args!("context for `{}` not found", path.display()),
call.span(),
)
})?;
let def = mctx.macros.get(&*name).ok_or_else(|| {
ctx.generate_error(
format_args!(
"macro `{}` not found in scope `{}`",
name.escape_debug(),
s.escape_debug(),
),
call.span(),
)
})?;
(*def, mctx)
} else {
let def = ctx.macros.get(&*name).ok_or_else(|| {
ctx.generate_error(
format_args!("macro `{}` not found", name.escape_debug()),
call.span(),
)
})?;
(*def, ctx)
};
helpers::MacroInvocation {
callsite_ctx: ctx,
callsite_span: call.span(),
call: Some(call),
callsite_ws: Ws(ws1.0, ws2.1),
call_args: args.as_deref().unwrap_or_default(),
macro_def: def,
macro_ctx: own_ctx,
}
.write(buf, self, render_for)
}
fn write_filter_block(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
filter: &'a WithSpan<FilterBlock<'_>>,
) -> Result<SizeHint, CompileError> {
let var_filter_source = crate::var_filter_source();
let mut size_hint = self.write_buf_writable(ctx, buf)?;
self.flush_ws(filter.ws1);
self.is_in_block.increase("filter");
size_hint += self.write_buf_writable(ctx, buf)?;
let span = ctx.span_for_node(filter.span());
let mut filter_def_buf = Buffer::new();
size_hint += self.push_locals(|this| {
this.prepare_ws(filter.ws1);
let mut size_hint = this.handle(
ctx,
&filter.nodes,
&mut filter_def_buf,
AstLevel::Nested,
RenderFor::Template,
)?;
this.flush_ws(filter.ws2);
size_hint += this.write_buf_writable(ctx, &mut filter_def_buf)?;
Ok(size_hint)
})?;
let filter_def_buf = filter_def_buf.into_token_stream();
let var_writer = crate::var_writer();
let filter_def_buf = quote_spanned!(span=>
let #var_filter_source = askama::helpers::FmtCell::new(
|#var_writer: &mut askama::helpers::core::fmt::Formatter<'_>| -> askama::Result<()> {
#filter_def_buf
askama::Result::Ok(())
}
);
);
let mut filter_buf = Buffer::new();
let display_wrap = self.visit_filter(
ctx,
&mut filter_buf,
&filter.filters.name,
&filter.filters.arguments,
filter.span(),
)?;
let filter_buf = filter_buf.into_token_stream();
let filter_buf = match display_wrap {
DisplayWrap::Wrapped => filter_buf,
DisplayWrap::Unwrapped => {
let escaper = TokenStream::from_str(self.input.escaper).unwrap();
quote_spanned!(span=>
(&&askama::filters::AutoEscaper::new(
&(#filter_buf), #escaper
)).askama_auto_escape()?
)
}
};
quote_into!(buf, span, { {
#filter_def_buf
if askama::helpers::core::write!(#var_writer, "{}", #filter_buf).is_err() {
return #var_filter_source.take_err();
}
} });
self.is_in_block.decrease();
self.prepare_ws(filter.ws2);
Ok(size_hint)
}
fn handle_include(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
i: &'a WithSpan<Include<'_>>,
render_for: RenderFor,
) -> Result<SizeHint, CompileError> {
self.flush_ws(i.ws);
let mut size_hint = self.write_buf_writable(ctx, buf)?;
let file_info = ctx
.path
.map(|path| FileInfo::of(i.span(), path, ctx.parsed));
let path = self.input.config.find_template(
i.path,
Some(ctx.path.unwrap_or(self.input.path.as_ref())),
file_info,
Some(ctx.span_for_node(i.span())),
)?;
let child_ctx = &mut self.contexts[&path].clone();
for (name, mac) in &ctx.macros {
child_ctx.macros.entry(*name).or_insert(mac);
}
for (name, import) in &ctx.imports {
child_ctx
.imports
.entry(name)
.or_insert_with(|| import.clone());
}
let heritage = if !child_ctx.blocks.is_empty() || child_ctx.extends.is_some() {
Some(Heritage::new(child_ctx, self.contexts))
} else {
None
};
let handle_ctx = match &heritage {
Some(heritage) => heritage.root,
None => child_ctx,
};
size_hint += self.with_child(heritage.as_ref(), |child| {
let mut size_hint = SizeHint::EMPTY;
size_hint +=
child.handle(handle_ctx, handle_ctx.nodes, buf, AstLevel::Top, render_for)?;
size_hint += child.write_buf_writable(handle_ctx, buf)?;
Ok(size_hint)
})?;
self.prepare_ws(i.ws);
Ok(size_hint)
}
fn is_shadowing_variable(
&self,
ctx: &Context<'_>,
var: &Target<'a>,
l: Span,
) -> Result<bool, CompileError> {
match var {
Target::Name(name) => {
match self.locals.get(name) {
None => Ok(false),
Some(meta) if meta.initialized => Ok(true),
_ => Ok(false),
}
}
Target::Placeholder(_) => Ok(false),
Target::Rest(var_name) => {
if let Some(var_name) = **var_name {
match self.is_shadowing_variable(ctx, &Target::Name(var_name), l) {
Ok(false) => {}
outcome => return outcome,
}
}
Ok(false)
}
Target::Tuple(v) => {
for target in &v.1 {
match self.is_shadowing_variable(ctx, target, l) {
Ok(false) => continue,
outcome => return outcome,
}
}
Ok(false)
}
Target::Struct(v) => {
for target in &v.1 {
match self.is_shadowing_variable(ctx, &target.dest, l) {
Ok(false) => continue,
outcome => return outcome,
}
}
Ok(false)
}
Target::Array(v) => {
for target in v.iter() {
match self.is_shadowing_variable(ctx, target, l) {
Ok(false) => continue,
outcome => return outcome,
}
}
Ok(false)
}
_ => Err(ctx.generate_error(
"literals are not allowed on the left-hand side of an assignment",
l,
)),
}
}
fn write_compound(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
c: &'a WithSpan<Compound<'_>>,
) -> Result<(), CompileError> {
self.handle_ws(c.ws);
let op = &c.op;
self.visit_binop(ctx, buf, op.op, &op.lhs, &op.rhs, c.span())?;
buf.write_token(Token![;], ctx.span_for_node(c.span()));
Ok(())
}
fn write_let(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
l: &'a WithSpan<Let<'_>>,
) -> Result<SizeHint, CompileError> {
self.handle_ws(l.ws);
match &l.val {
LetValueOrBlock::Value(val) => self.write_let_value(ctx, buf, l, val),
LetValueOrBlock::Block { nodes, ws } => self.write_let_block(ctx, buf, l, nodes, *ws),
}
}
fn write_let_target(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
l: &'a WithSpan<Let<'_>>,
span: proc_macro2::Span,
) -> Result<SizeHint, CompileError> {
let shadowed = self.is_shadowing_variable(ctx, &l.var, l.span())?;
let size_hint = if shadowed {
self.write_buf_writable(ctx, buf)?
} else {
SizeHint::EMPTY
};
if shadowed
|| !matches!(l.var, Target::Name(_))
|| matches!(&l.var, Target::Name(name) if self.locals.get(name).is_none())
{
buf.write_token(Token![let], span);
if l.is_mutable {
buf.write_token(Token![mut], span);
}
}
self.visit_target(ctx, buf, true, true, &l.var, span);
Ok(size_hint)
}
fn write_let_block(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
l: &'a WithSpan<Let<'_>>,
nodes: &'a [Box<Node<'a>>],
ws: Ws,
) -> Result<SizeHint, CompileError> {
let var_let_source = crate::var_let_source();
let mut size_hint = self.write_buf_writable(ctx, buf)?;
self.flush_ws(l.ws);
self.is_in_block.increase("let/set");
size_hint += self.write_buf_writable(ctx, buf)?;
let span = ctx.span_for_node(l.span());
let mut filter_def_buf = Buffer::new();
size_hint += self.push_locals(|this| {
this.prepare_ws(l.ws);
let mut size_hint = this.handle(
ctx,
nodes,
&mut filter_def_buf,
AstLevel::Nested,
RenderFor::Template,
)?;
this.flush_ws(ws);
size_hint += this.write_buf_writable(ctx, &mut filter_def_buf)?;
Ok(size_hint)
})?;
let filter_def_buf = filter_def_buf.into_token_stream();
size_hint += self.write_let_target(ctx, buf, l, span)?;
buf.write_token(Token![=], span);
let var_writer = crate::var_writer();
let filter_def_buf = quote_spanned!(span=>
let #var_let_source = askama::helpers::FmtCell::new(
|#var_writer: &mut askama::helpers::core::fmt::Formatter<'_>| -> askama::Result<()> {
#filter_def_buf
askama::Result::Ok(())
}
);
);
let mut filter_buf = Buffer::new();
quote_into!(&mut filter_buf, span, { askama::filters::Safe(&#var_let_source) });
let filter_buf = filter_buf.into_token_stream();
let escaper = TokenStream::from_str(self.input.escaper).unwrap();
let filter_buf = quote_spanned!(span=>
(&&askama::filters::AutoEscaper::new(
&(#filter_buf), #escaper
)).askama_auto_escape()?
);
quote_into!(buf, span, { {
#filter_def_buf
let mut __askama_tmp_write = String::new();
if askama::helpers::core::write!(&mut __askama_tmp_write, "{}", #filter_buf).is_err() {
return #var_let_source.take_err();
}
__askama_tmp_write
}; });
self.is_in_block.decrease();
self.prepare_ws(ws);
Ok(size_hint)
}
fn handle_let_value_caller(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
l: &'a WithSpan<Let<'_>>,
val: &'a WithSpan<Box<Expr<'a>>>,
) -> Result<Option<SizeHint>, CompileError> {
let Expr::Call(ref call) = ***val else {
return Ok(None);
};
let Expr::Var(var_name) = **call.path else {
return Ok(None);
};
let mut filter_def_buf = Buffer::new();
let mut size_hint = match self.push_locals(|this| {
let Some(size_hint) = this.handle_caller(
ctx,
&mut filter_def_buf,
l.span(),
call,
var_name,
Ws(None, None),
)?
else {
return Ok(None);
};
this.write_buf_writable(ctx, &mut filter_def_buf)
.map(|size| Some(size_hint + size))
})? {
Some(size_hint) => size_hint,
None => return Ok(None),
};
self.handle_ws(l.ws);
let filter_def_buf = filter_def_buf.into_token_stream();
let span = ctx.span_for_node(l.span());
size_hint += self.write_let_target(ctx, buf, l, span)?;
buf.write_token(Token![=], span);
let var_writer = crate::var_writer();
let var_let_caller = crate::var_let_caller();
let filter_def_buf = quote_spanned!(span=>
let #var_let_caller = askama::helpers::FmtCell::new(
|#var_writer: &mut askama::helpers::core::fmt::Formatter<'_>| -> askama::Result<()> {
#filter_def_buf
askama::Result::Ok(())
}
);
);
let mut filter_buf = Buffer::new();
quote_into!(&mut filter_buf, span, { askama::filters::Safe(&#var_let_caller) });
let filter_buf = filter_buf.into_token_stream();
let escaper = TokenStream::from_str(self.input.escaper).unwrap();
let filter_buf = quote_spanned!(span=>
(&&askama::filters::AutoEscaper::new(
&(#filter_buf), #escaper
)).askama_auto_escape()?
);
quote_into!(buf, span, { {
#filter_def_buf
let mut __askama_tmp_write = String::new();
if askama::helpers::core::write!(&mut __askama_tmp_write, "{}", #filter_buf).is_err() {
return #var_let_caller.take_err();
}
__askama_tmp_write
}; });
self.flush_ws(l.ws);
Ok(Some(size_hint))
}
fn write_let_value(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
l: &'a WithSpan<Let<'_>>,
val: &'a WithSpan<Box<Expr<'a>>>,
) -> Result<SizeHint, CompileError> {
if let Target::Name(dstvar) = l.var {
if let Expr::Var(srcvar) = ***val {
if let Some(caller_alias) = self.locals.get_caller(srcvar) {
self.locals.insert(
Cow::Borrowed(*dstvar),
LocalMeta::CallerAlias(caller_alias.clone()),
);
return Ok(SizeHint::EMPTY);
}
} else if let Some(size_hint) = self.handle_let_value_caller(ctx, buf, l, val)? {
return Ok(size_hint);
}
}
let span = ctx.span_for_node(l.span());
let mut expr_buf = Buffer::new();
self.visit_expr(ctx, &mut expr_buf, val)?;
let size_hint = self.write_let_target(ctx, buf, l, span)?;
let borrow = !matches!(***val, Expr::Try(..))
&& !matches!(***val, Expr::Var(name) if self.locals.get(name).is_some())
&& !is_copyable(val);
buf.write_tokens(if borrow {
quote_spanned! { span => = &(#expr_buf); }
} else {
quote_spanned! { span => = #expr_buf; }
});
Ok(size_hint)
}
fn write_decl(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
c: &'a WithSpan<Declare<'_>>,
) -> Result<SizeHint, CompileError> {
let span = ctx.span_for_node(c.span());
if *c.var_name == "_" {
return Err(ctx.generate_error(
"`_` cannot be used when there is no value assigned, use `let` instead",
c.var_name.span(),
));
}
self.handle_ws(c.ws);
let size_hint = self.write_buf_writable(ctx, buf)?;
buf.write_token(Token![let], span);
if c.is_mutable {
buf.write_token(Token![mut], span);
}
self.visit_target(ctx, buf, false, true, &Target::Name(c.var_name), span);
buf.write_token(Token![;], span);
Ok(size_hint)
}
fn write_block(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
name: Option<WithSpan<&'a str>>,
outer: Ws,
node: Span,
) -> Result<SizeHint, CompileError> {
if self.is_in_block.level > 0 {
return Err(ctx.generate_error(
format!(
"cannot have a block inside a {} block",
self.is_in_block.block_name
),
node,
));
}
self.flush_ws(outer);
let cur = match (name, self.super_block) {
(Some(cur_name), None) => (*cur_name, 0),
(Some(cur_name), Some((prev_name, _))) if *cur_name == prev_name => {
return Err(ctx.generate_error(
format_args!(
"cannot define recursive blocks (`{}`)",
cur_name.escape_debug(),
),
node,
));
}
(Some(cur_name), Some((_, _))) => (*cur_name, 0),
(None, Some((prev_name, r#gen))) => (prev_name, r#gen + 1),
(None, None) => {
return Err(ctx.generate_error("cannot call `super()` outside block", node));
}
};
let mut size_hint = self.write_buf_writable(ctx, buf)?;
let block_fragment_write = self.input.block.map(|(block, _)| block)
== name.as_deref().copied()
&& self.buf_writable.discard;
if block_fragment_write {
self.buf_writable.discard = false;
}
let prev_buf_discard = buf.is_discard();
buf.set_discard(self.buf_writable.discard);
let heritage = self
.heritage
.ok_or_else(|| ctx.generate_error("no block ancestors available", node))?;
let (child_ctx, def) = *heritage.blocks[&cur.0].get(cur.1).ok_or_else(|| {
ctx.generate_error(
match name {
None => fmt_left!(
"no `super()` block found for block `{}`",
cur.0.escape_debug()
),
Some(name) => {
fmt_right!(move "no block found for name `{}", name.escape_debug())
}
},
node,
)
})?;
let mut child_ctx = child_ctx.clone();
for (name, mac) in &ctx.macros {
child_ctx.macros.entry(*name).or_insert(mac);
}
for (name, import) in &ctx.imports {
child_ctx
.imports
.entry(name)
.or_insert_with(|| import.clone());
}
for (name, block) in &ctx.blocks {
child_ctx.blocks.entry(name).or_insert(block);
}
size_hint += self.with_child(Some(heritage), |child| {
child.prepare_ws(def.ws1);
child.super_block = Some(cur);
let mut size_hint = child.handle(
&child_ctx,
&def.nodes,
buf,
AstLevel::Block,
RenderFor::Template,
)?;
if !child.locals.is_current_empty() {
size_hint += child.write_buf_writable(ctx, buf)?;
}
child.flush_ws(def.ws2);
Ok(size_hint)
})?;
self.prepare_ws(outer);
if buf.is_discard() != prev_buf_discard {
size_hint += self.write_buf_writable(ctx, buf)?;
}
if block_fragment_write {
self.buf_writable.discard = true;
}
buf.set_discard(prev_buf_discard);
Ok(size_hint)
}
fn write_expr(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
ws: Ws,
mut expr: &'a WithSpan<Box<Expr<'a>>>,
render_for: RenderFor,
) -> Result<SizeHint, CompileError> {
while let Expr::Group(inner) = &***expr {
expr = inner;
}
if let Expr::Call(call) = &***expr
&& let ControlFlow::Break(size_hint) =
self.write_expr_call(ctx, buf, ws, expr.span(), call, render_for)?
{
self.handle_ws(ws);
return Ok(size_hint);
}
if render_for == RenderFor::Template {
self.handle_ws(ws);
self.write_expr_item(expr);
}
Ok(SizeHint::EMPTY)
}
fn write_expr_item(&mut self, expr: &'a WithSpan<Box<Expr<'a>>>) {
match &***expr {
Expr::Group(expr) => self.write_expr_item(expr),
Expr::Concat(items) => {
for expr in items {
self.write_expr_item(expr);
}
}
_ => {
self.buf_writable.push(
compile_time_escape(expr, self.input.escaper).unwrap_or(Writable::Expr(expr)),
);
}
}
}
fn handle_caller(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
span: Span,
call: &'a parser::expr::Call<'a>,
var_name: &str,
ws: Ws,
) -> Result<Option<SizeHint>, CompileError> {
let caller_alias = self.locals.get_caller(var_name);
if var_name == "caller" && caller_alias.is_none() {
return Err(ctx.generate_error("block is not defined for `caller`", span));
}
if let Some(LocalCallerMeta { call_ctx, def }) = caller_alias.cloned() {
self.handle_ws(ws);
let span_span = ctx.span_for_node(span);
let size_hint = self.push_locals(|this| {
this.locals.insert("caller".into(), LocalMeta::Negative);
let mut size_hint = this.write_buf_writable(&call_ctx, buf)?;
this.prepare_ws(def.ws1);
let mut value = Buffer::new();
let mut variable_buf = Buffer::new();
check_num_args(
span,
&call_ctx,
def.caller_args.len(),
call.args.len(),
"caller",
)?;
for (index, arg) in def.caller_args.iter().enumerate() {
match call.args.get(index) {
Some(expr) => {
value.clear();
match &***expr {
&Expr::Var(name) if name != "self" => {
let var = this.locals.resolve_or_self(name);
this.locals
.insert(Cow::Borrowed(arg), LocalMeta::var_with_ref(var));
}
Expr::AssociatedItem(obj, associated_item) => {
let mut associated_item_buf = Buffer::new();
this.visit_associated_item(
&call_ctx,
&mut associated_item_buf,
obj,
associated_item,
)?;
let associated_item = associated_item_buf.to_string();
let var = this
.locals
.resolve(&associated_item)
.unwrap_or(associated_item);
this.locals
.insert(Cow::Borrowed(arg), LocalMeta::var_with_ref(var));
}
_ => {
value.write_tokens(this.visit_expr_root(&call_ctx, expr)?);
let id = field_new(arg, span_span);
variable_buf.write_tokens(if !is_copyable(expr) {
quote_spanned! { span_span => let #id = &(#value); }
} else {
quote_spanned! { span_span => let #id = #value; }
});
this.locals.insert_with_default(Cow::Borrowed(arg));
}
}
}
None => {
return Err(call_ctx.generate_error(
format_args!("missing `{arg}` argument in `caller`"),
span,
));
}
}
}
value.clear();
size_hint += this.handle(
&call_ctx,
&def.nodes,
&mut value,
AstLevel::Nested,
RenderFor::Template,
)?;
this.flush_ws(def.ws2);
size_hint += this.write_buf_writable(&call_ctx, &mut value)?;
let value = value.into_token_stream();
let variable_buf = variable_buf.into_token_stream();
quote_into!(buf, span_span, { #variable_buf #value });
Ok(size_hint)
})?;
return Ok(Some(size_hint));
}
Ok(None)
}
fn write_expr_call(
&mut self,
ctx: &Context<'a>,
buf: &mut Buffer,
ws: Ws,
span: Span,
call: &'a parser::expr::Call<'a>,
render_for: RenderFor,
) -> Result<ControlFlow<SizeHint>, CompileError> {
if let Expr::Var(var_name) = **call.path {
if var_name == "super" {
check_num_args(span, ctx, 0, call.args.len(), "super")?;
return self
.write_block(ctx, buf, None, ws, span)
.map(ControlFlow::Break);
}
if let Some(res) = self.handle_caller(ctx, buf, span, call, var_name, ws)? {
return Ok(ControlFlow::Break(res));
}
if let Some(macro_def) = ctx.macros.get(&var_name) {
return helpers::MacroInvocation {
callsite_ctx: ctx,
callsite_span: span,
call: None,
callsite_ws: ws,
call_args: &call.args,
macro_def,
macro_ctx: ctx,
}
.write(buf, self, render_for)
.map(ControlFlow::Break);
}
}
if let Expr::Path(path_components) = &**call.path
&& let [scope, macro_name] = path_components.as_slice()
&& scope.generics.is_none()
&& macro_name.generics.is_none()
&& let Some(scope) = ctx.imports.get(*scope.name)
&& let Some(macro_ctx) = self.contexts.get(scope)
&& let Some(macro_def) = macro_ctx.macros.get(*macro_name.name)
{
return helpers::MacroInvocation {
callsite_ctx: ctx,
callsite_span: span,
call: None,
callsite_ws: ws,
call_args: &call.args,
macro_def,
macro_ctx,
}
.write(buf, self, render_for)
.map(ControlFlow::Break);
}
if let Expr::Path(path_components) = &**call.path
&& let [scope, macro_name] = path_components.as_slice()
&& scope.generics.is_none()
&& macro_name.generics.is_none()
&& let Some(scope) = ctx.imports.get(*scope.name)
&& let Some(macro_ctx) = self.contexts.get(scope)
&& let Some(macro_def) = macro_ctx.macros.get(*macro_name.name)
{
return helpers::MacroInvocation {
callsite_ctx: ctx,
callsite_span: span,
call: None,
callsite_ws: ws,
call_args: &call.args,
macro_def,
macro_ctx,
}
.write(buf, self, render_for)
.map(ControlFlow::Break);
}
Ok(ControlFlow::Continue(()))
}
pub(crate) fn write_buf_writable(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
) -> Result<SizeHint, CompileError> {
let mut size_hint = SizeHint::EMPTY;
let items = mem::take(&mut self.buf_writable.buf);
let mut it = items.iter().enumerate().peekable();
let Some((_, start)) = it.peek() else {
return Ok(SizeHint::EMPTY);
};
let start_span = match start {
Writable::Lit(v) => v.span(),
Writable::Expr(v) => v.span(),
};
if let Some((_, Writable::Lit(lit))) = it.peek() {
let mut literal = String::new();
while let Some((_, Writable::Lit(s))) = it.peek() {
size_hint += s.len();
string_escape(&mut literal, s);
it.next();
}
let span = ctx.span_for_node(lit.span());
buf.write_str_lit(literal, span);
}
if it.peek().is_none() {
return Ok(size_hint);
}
let mut targets = Buffer::new();
let mut lines = Buffer::new();
let mut expr_cache =
HashMap::with_capacity_and_hasher(self.buf_writable.len(), FxBuildHasher);
let mut trailing_simple_lines = Vec::new();
let mut matched_expr_buf = Buffer::new();
while let Some((idx, s)) = it.next() {
match s {
Writable::Lit(s) => {
let mut items = vec![s];
while let Some((_, Writable::Lit(s))) = it.peek() {
items.push(s);
it.next();
}
if it.peek().is_some() {
let mut literal = String::new();
let span = ctx.span_for_node(items[0].span());
for s in items {
size_hint += s.len();
string_escape(&mut literal, s);
}
lines.write_str_lit(literal, span);
} else {
trailing_simple_lines = items;
break;
}
}
Writable::Expr(s) => {
size_hint += 3;
let mut expr_buf = Buffer::new();
let span = ctx.span_for_node(s.span());
let expr = match self.visit_expr(ctx, &mut expr_buf, s)? {
DisplayWrap::Wrapped => expr_buf.into_token_stream(),
DisplayWrap::Unwrapped => {
let escaper = TokenStream::from_str(self.input.escaper).unwrap();
let expr_buf = expr_buf.into_token_stream();
quote_spanned!(span=>
(&&askama::filters::AutoEscaper::new(&(#expr_buf), #escaper)).
askama_auto_escape()?
)
}
};
let (id, entry);
let id = if is_cacheable(s) {
match expr_cache.entry(expr.to_string()) {
Entry::Occupied(e) => {
entry = e;
entry.get()
}
Entry::Vacant(entry) => {
let id = &*entry.insert(crate::var_expr_n(idx, span));
quote_into!(&mut matched_expr_buf, span, { &(#expr), });
quote_into!(&mut targets, span, { #id, });
id
}
}
} else {
quote_into!(&mut matched_expr_buf, span, { &(#expr), });
id = crate::var_expr_n(idx, span);
quote_into!(&mut targets, span, { #id, });
&id
};
let var_writer = crate::var_writer();
let var_values = crate::var_values();
quote_into!(&mut lines, span, {
(&&&askama::filters::Writable(#id)).askama_write(#var_writer, #var_values)?;
});
}
}
}
quote_into!(buf, ctx.span_for_node(start_span), {
match (#matched_expr_buf) {
(#targets) => {
#lines
}
}
});
if !trailing_simple_lines.is_empty() {
let mut literal = String::new();
let span = ctx.span_for_node(trailing_simple_lines[0].span());
for s in trailing_simple_lines {
size_hint += s.len();
string_escape(&mut literal, s);
}
buf.write_str_lit(literal, span);
}
Ok(size_hint)
}
fn write_comment(&mut self, comment: &'a WithSpan<Comment<'_>>) {
self.handle_ws(comment.ws);
}
fn write_lit(&mut self, lit: &'a WithSpan<Lit<'_>>) {
assert!(self.next_ws.is_none());
let Lit { lws, val, rws } = **lit;
if !lws.is_empty() {
match self.skip_ws {
Whitespace::Suppress => {}
_ if val.is_empty() => {
assert!(rws.is_empty());
self.next_ws = Some(lws);
}
Whitespace::Preserve => {
self.buf_writable.push(Writable::Lit(WithSpan::new(
Cow::Borrowed(*lws),
lws.span(),
)));
}
Whitespace::Minimize => {
self.buf_writable.push(Writable::Lit(WithSpan::new(
Cow::Borrowed(match lws.contains('\n') {
true => "\n",
false => " ",
}),
lws.span(),
)));
}
}
}
if !val.is_empty() {
self.skip_ws = Whitespace::Preserve;
self.buf_writable.push(Writable::Lit(WithSpan::new(
Cow::Borrowed(*val),
val.span(),
)));
}
if !rws.is_empty() {
self.next_ws = Some(rws);
}
}
pub(crate) fn handle_ws(&mut self, ws: Ws) {
self.flush_ws(ws);
self.prepare_ws(ws);
}
fn should_trim_ws(&self, ws: Option<Whitespace>) -> Whitespace {
ws.unwrap_or(self.input.config.whitespace)
}
pub(crate) fn flush_ws(&mut self, ws: Ws) {
if self.next_ws.is_none() {
return;
}
match self.should_trim_ws(ws.0) {
Whitespace::Preserve => {
let val = self.next_ws.unwrap();
if !val.is_empty() {
self.buf_writable.push(Writable::Lit(WithSpan::new(
Cow::Borrowed(*val),
val.span(),
)));
}
}
Whitespace::Minimize => {
let val = self.next_ws.unwrap();
if !val.is_empty() {
self.buf_writable.push(Writable::Lit(WithSpan::new(
Cow::Borrowed(match val.contains('\n') {
true => "\n",
false => " ",
}),
val.span(),
)));
}
}
Whitespace::Suppress => {}
}
self.next_ws = None;
}
pub(crate) fn prepare_ws(&mut self, ws: Ws) {
self.skip_ws = self.should_trim_ws(ws.1);
}
}
fn bin_op<'a>(
span: impl Into<Span>,
op: &'a str,
lhs: WithSpan<Box<Expr<'a>>>,
rhs: WithSpan<Box<Expr<'a>>>,
) -> WithSpan<Box<Expr<'a>>> {
WithSpan::new(Box::new(Expr::BinOp(BinOp { op, lhs, rhs })), span)
}
struct CondInfo<'a> {
cond: &'a WithSpan<Cond<'a>>,
cond_expr: Option<WithSpan<Box<Expr<'a>>>>,
generate_condition: bool,
generate_content: bool,
}
struct Conds<'a> {
conds: Vec<CondInfo<'a>>,
ws_before: Option<Ws>,
ws_after: Option<Ws>,
}
#[derive(Debug, Clone, PartialEq)]
enum EvaluatedResult<'a> {
AlwaysTrue,
AlwaysFalse,
Unknown(WithSpan<Box<Expr<'a>>>),
}
impl<'a> Conds<'a> {
fn compute_branches(generator: &Generator<'a, '_>, i: &'a If<'a>) -> Self {
let mut conds = Vec::with_capacity(i.branches.len());
let mut ws_before = None;
let mut ws_after = None;
let mut stop_loop = false;
for cond in &i.branches {
if stop_loop {
ws_after = Some(cond.ws);
break;
}
if let Some(CondTest {
expr,
contains_bool_lit_or_is_defined,
..
}) = cond.cond.as_deref()
{
let mut only_contains_is_defined = true;
let span = expr.span();
let evaluated_result = if *contains_bool_lit_or_is_defined {
Some(generator.evaluate_condition(expr.clone(), &mut only_contains_is_defined))
} else {
None
};
match evaluated_result {
Some(EvaluatedResult::AlwaysFalse) => {
if only_contains_is_defined {
if conds.is_empty() && ws_before.is_none() {
ws_before = Some(cond.ws);
}
continue;
}
conds.push(CondInfo {
cond,
cond_expr: Some(WithSpan::new(Box::new(Expr::BoolLit(false)), span)),
generate_condition: true,
generate_content: false,
});
}
Some(EvaluatedResult::AlwaysTrue) => {
let generate_condition = !only_contains_is_defined;
conds.push(CondInfo {
cond,
cond_expr: Some(WithSpan::new(Box::new(Expr::BoolLit(true)), span)),
generate_condition,
generate_content: true,
});
stop_loop = true;
}
Some(EvaluatedResult::Unknown(cond_expr)) => {
conds.push(CondInfo {
cond,
cond_expr: Some(cond_expr),
generate_condition: true,
generate_content: true,
});
}
None => {
conds.push(CondInfo {
cond,
cond_expr: None,
generate_condition: true,
generate_content: true,
});
}
}
} else {
let generate_condition = !conds.is_empty();
conds.push(CondInfo {
cond,
cond_expr: None,
generate_condition,
generate_content: true,
});
}
}
Self {
conds,
ws_before,
ws_after,
}
}
}
fn median(sizes: &mut [SizeHint]) -> SizeHint {
if sizes.is_empty() {
return SizeHint::EMPTY;
}
sizes.sort_unstable();
if sizes.len() % 2 == 1 {
sizes[sizes.len() / 2]
} else {
(sizes[sizes.len() / 2 - 1] + sizes[sizes.len() / 2]) / 2
}
}
#[derive(Clone, Copy, PartialEq)]
pub(crate) enum AstLevel {
Top,
Block,
Nested,
}
fn is_cacheable(expr: &WithSpan<Box<Expr<'_>>>) -> bool {
match &***expr {
Expr::BoolLit(_) => true,
Expr::NumLit(_, _) => true,
Expr::StrLit(_) => true,
Expr::CharLit(_) => true,
Expr::Var(_) => true,
Expr::Path(_) => true,
Expr::Array(args) => args.iter().all(is_cacheable),
Expr::ArrayRepeat(elem, cnt) => is_cacheable(elem) && is_cacheable(cnt),
Expr::AssociatedItem(lhs, _) => is_cacheable(lhs),
Expr::Index(lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs),
Expr::Filter(v) => v.arguments.iter().all(is_cacheable),
Expr::Unary(_, arg) => is_cacheable(arg),
Expr::BinOp(v) => is_cacheable(&v.lhs) && is_cacheable(&v.rhs),
Expr::IsDefined(_) | Expr::IsNotDefined(_) => true,
Expr::Range(v) => {
v.lhs.as_ref().is_none_or(is_cacheable) && v.rhs.as_ref().is_none_or(is_cacheable)
}
Expr::Group(arg) => is_cacheable(arg),
Expr::Tuple(args) => args.iter().all(is_cacheable),
Expr::NamedArgument(_, expr) => is_cacheable(expr),
Expr::As(expr, _) => is_cacheable(expr),
Expr::Try(expr) => is_cacheable(expr),
Expr::Concat(args) => args.iter().all(is_cacheable),
Expr::LetCond(_) => false,
Expr::Call { .. } => false,
Expr::Struct(s) => {
s.base.is_none()
&& s.fields
.iter()
.filter_map(|field| field.value.as_ref())
.all(|value| is_cacheable(value))
}
Expr::RustMacro(_, _) => false,
Expr::FilterSource => unreachable!("FilterSource in expression?"),
Expr::ArgumentPlaceholder => unreachable!("ExpressionPlaceholder in expression?"),
}
}
fn check_num_args<'a>(
span: Span,
ctx: &Context<'a>,
expected: usize,
found: usize,
name: &str,
) -> Result<(), CompileError> {
if expected != found {
Err(ctx.generate_error(
format!(
"expected {expected} argument{} in `{name}`, found {found}",
if expected != 1 { "s" } else { "" }
),
span,
))
} else {
Ok(())
}
}