From a33f77ba93b8525d3fb004be30916fda93bc550a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 May 2024 15:22:51 +0200 Subject: [PATCH 01/14] Add `generate_row_and_column` and `generate_error_info` public functions in `askama_parser` --- askama_parser/src/lib.rs | 94 ++++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 27 deletions(-) diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index 666c3fb5..95f86353 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -102,36 +102,10 @@ impl<'a> Ast<'a> { Err(nom::Err::Incomplete(_)) => return Err(ParseError("parsing incomplete".into())), }; - let offset = src.len() - input.len(); - let (source_before, source_after) = src.split_at(offset); - - let source_after = match source_after.char_indices().enumerate().take(41).last() { - Some((40, (i, _))) => format!("{:?}...", &source_after[..i]), - _ => format!("{source_after:?}"), - }; - - let (row, last_line) = source_before.lines().enumerate().last().unwrap_or_default(); - let column = last_line.chars().count(); - - let file_info = file_path.and_then(|file_path| { - let cwd = std::env::current_dir().ok()?; - Some((cwd, file_path)) - }); let message = message .map(|message| format!("{message}\n")) .unwrap_or_default(); - let error_msg = if let Some((cwd, file_path)) = file_info { - format!( - "{message}failed to parse template source\n --> {path}:{row}:{column}\n{source_after}", - path = strip_common(&cwd, &file_path), - row = row + 1, - ) - } else { - format!( - "{message}failed to parse template source at row {}, column {column} near:\n{source_after}", - row + 1, - ) - }; + let error_msg = generate_error_message(&message, src, input, &file_path); Err(ParseError(error_msg)) } @@ -155,6 +129,72 @@ impl fmt::Display for ParseError { pub(crate) type ParseErr<'a> = nom::Err>; pub(crate) type ParseResult<'a, T = &'a str> = Result<(&'a str, T), ParseErr<'a>>; +pub struct ErrorInfo { + pub row: usize, + pub column: usize, + pub source_after: String, +} + +pub fn generate_row_and_column(src: &str, input: &str) -> ErrorInfo { + let offset = src.len() - input.len(); + let (source_before, source_after) = src.split_at(offset); + + let source_after = match source_after.char_indices().enumerate().take(41).last() { + Some((40, (i, _))) => format!("{:?}...", &source_after[..i]), + _ => format!("{source_after:?}"), + }; + + let (row, last_line) = source_before.lines().enumerate().last().unwrap_or_default(); + let column = last_line.chars().count(); + ErrorInfo { + row, + column, + source_after, + } +} + +/// Return the error related information and its display file path. +pub fn generate_error_info(src: &str, input: &str, file_path: &Path) -> (ErrorInfo, String) { + let file_path = match std::env::current_dir() { + Ok(cwd) => strip_common(&cwd, file_path), + Err(_) => file_path.display().to_string(), + }; + let error_info = generate_row_and_column(src, input); + (error_info, file_path) +} + +fn generate_error_message( + message: &str, + src: &str, + input: &str, + file_path: &Option>, +) -> String { + if let Some(file_path) = file_path { + let ( + ErrorInfo { + row, + column, + source_after, + }, + file_path, + ) = generate_error_info(src, input, file_path); + format!( + "{message}failed to parse template source\n --> {file_path}:{row}:{column}\n{source_after}", + row = row + 1, + ) + } else { + let ErrorInfo { + row, + column, + source_after, + } = generate_row_and_column(src, input); + format!( + "{message}failed to parse template source at row {}, column {column} near:\n{source_after}", + row + 1, + ) + } +} + /// This type is used to handle `nom` errors and in particular to add custom error messages. /// It used to generate `ParserError`. /// From e452b7683d43fe2fed64c9232d1e5a86e426d08b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 May 2024 15:45:50 +0200 Subject: [PATCH 02/14] Create `askama_parser::WithSpan` type --- askama_parser/src/lib.rs | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index 95f86353..09581e95 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use std::cell::Cell; +use std::ops::{Deref, DerefMut}; use std::path::Path; use std::rc::Rc; use std::{fmt, str}; @@ -115,6 +116,59 @@ impl<'a> Ast<'a> { } } +/// Struct used to wrap types with their associated "span" which is used when generating errors +/// in the code generation. +pub struct WithSpan<'a, T> { + inner: T, + span: &'a str, +} + +impl<'a, T> WithSpan<'a, T> { + pub fn new(inner: T, span: &'a str) -> Self { + Self { inner, span } + } + + pub fn span(&self) -> &str { + self.span + } +} + +impl<'a, T> Deref for WithSpan<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl<'a, T> DerefMut for WithSpan<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl<'a, T: fmt::Debug> fmt::Debug for WithSpan<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.inner) + } +} + +impl<'a, T: Clone> Clone for WithSpan<'a, T> { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + span: self.span, + } + } +} + +impl<'a, T: PartialEq> PartialEq for WithSpan<'a, T> { + fn eq(&self, other: &Self) -> bool { + // We never want to compare the span information. + self.inner == other.inner + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct ParseError(String); From 368fd820b276fe473e651a0dffbd9b2e80dc1ed0 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 May 2024 17:43:24 +0200 Subject: [PATCH 03/14] Wrap `Expr` into `WithSpan` --- askama_parser/src/expr.rs | 149 +++++++++++++++++++++++--------------- askama_parser/src/lib.rs | 2 +- askama_parser/src/node.rs | 24 +++--- 3 files changed, 104 insertions(+), 71 deletions(-) diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index 16069b2d..563b300e 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -14,12 +14,13 @@ use super::{ char_lit, filter, identifier, not_ws, num_lit, path_or_identifier, str_lit, ws, Level, PathOrIdentifier, }; -use crate::{ErrorContext, ParseResult}; +use crate::{ErrorContext, ParseResult, WithSpan}; macro_rules! expr_prec_layer { ( $name:ident, $inner:ident, $op:expr ) => { - fn $name(i: &'a str, level: Level) -> ParseResult<'a, Self> { + fn $name(i: &'a str, level: Level) -> ParseResult<'a, WithSpan<'a, Self>> { let (_, level) = level.nest(i)?; + let start = i; let (i, left) = Self::$inner(i, level)?; let (i, right) = many0(pair( ws(tag($op)), @@ -28,14 +29,15 @@ macro_rules! expr_prec_layer { Ok(( i, right.into_iter().fold(left, |left, (op, right)| { - Self::BinOp(op, Box::new(left), Box::new(right)) + WithSpan::new(Self::BinOp(op, Box::new(left), Box::new(right)), start) }), )) } }; ( $name:ident, $inner:ident, $( $op:expr ),+ ) => { - fn $name(i: &'a str, level: Level) -> ParseResult<'a, Self> { + fn $name(i: &'a str, level: Level) -> ParseResult<'a, WithSpan<'a, Self>> { let (_, level) = level.nest(i)?; + let start = i; let (i, left) = Self::$inner(i, level)?; let (i, right) = many0(pair( ws(alt(($( tag($op) ),+,))), @@ -44,7 +46,7 @@ macro_rules! expr_prec_layer { Ok(( i, right.into_iter().fold(left, |left, (op, right)| { - Self::BinOp(op, Box::new(left), Box::new(right)) + WithSpan::new(Self::BinOp(op, Box::new(left), Box::new(right)), start) }), )) } @@ -59,19 +61,27 @@ pub enum Expr<'a> { CharLit(&'a str), Var(&'a str), Path(Vec<&'a str>), - Array(Vec>), - Attr(Box>, &'a str), - Index(Box>, Box>), + Array(Vec>>), + Attr(Box>>, &'a str), + Index(Box>>, Box>>), Filter(Filter<'a>), - NamedArgument(&'a str, Box>), - Unary(&'a str, Box>), - BinOp(&'a str, Box>, Box>), - Range(&'a str, Option>>, Option>>), - Group(Box>), - Tuple(Vec>), - Call(Box>, Vec>), + NamedArgument(&'a str, Box>>), + Unary(&'a str, Box>>), + BinOp( + &'a str, + Box>>, + Box>>, + ), + Range( + &'a str, + Option>>>, + Option>>>, + ), + Group(Box>>), + Tuple(Vec>>), + Call(Box>>, Vec>>), RustMacro(Vec<&'a str>, &'a str), - Try(Box>), + Try(Box>>), /// This variant should never be used directly. It is created when generating filter blocks. Generated(String), } @@ -81,7 +91,7 @@ impl<'a> Expr<'a> { i: &'a str, level: Level, is_template_macro: bool, - ) -> ParseResult<'a, Vec> { + ) -> ParseResult<'a, Vec>> { let (_, level) = level.nest(i)?; let mut named_arguments = HashSet::new(); let start = i; @@ -109,7 +119,7 @@ impl<'a> Expr<'a> { }, move |i| Self::parse(i, level), ))(i)?; - if has_named_arguments && !matches!(expr, Self::NamedArgument(_, _)) { + if has_named_arguments && !matches!(*expr, Self::NamedArgument(_, _)) { Err(nom::Err::Failure(ErrorContext::new( "named arguments must always be passed last", start, @@ -130,7 +140,7 @@ impl<'a> Expr<'a> { named_arguments: &mut HashSet<&'a str>, start: &'a str, is_template_macro: bool, - ) -> ParseResult<'a, Self> { + ) -> ParseResult<'a, WithSpan<'a, Self>> { if !is_template_macro { // If this is not a template macro, we don't want to parse named arguments so // we instead return an error which will allow to continue the parsing. @@ -141,7 +151,10 @@ impl<'a> Expr<'a> { let (i, (argument, _, value)) = tuple((identifier, ws(char('=')), move |i| Self::parse(i, level)))(i)?; if named_arguments.insert(argument) { - Ok((i, Self::NamedArgument(argument, Box::new(value)))) + Ok(( + i, + WithSpan::new(Self::NamedArgument(argument, Box::new(value)), start), + )) } else { Err(nom::Err::Failure(ErrorContext::new( format!("named argument `{argument}` was passed more than once"), @@ -150,8 +163,9 @@ impl<'a> Expr<'a> { } } - pub(super) fn parse(i: &'a str, level: Level) -> ParseResult<'a, Self> { + pub(super) fn parse(i: &'a str, level: Level) -> ParseResult<'a, WithSpan<'a, Self>> { let (_, level) = level.nest(i)?; + let start = i; let range_right = move |i| { pair( ws(alt((tag("..="), tag("..")))), @@ -160,12 +174,15 @@ impl<'a> Expr<'a> { }; alt(( map(range_right, |(op, right)| { - Self::Range(op, None, right.map(Box::new)) + WithSpan::new(Self::Range(op, None, right.map(Box::new)), start) }), map( pair(move |i| Self::or(i, level), opt(range_right)), |(left, right)| match right { - Some((op, right)) => Self::Range(op, Some(Box::new(left)), right.map(Box::new)), + Some((op, right)) => WithSpan::new( + Self::Range(op, Some(Box::new(left)), right.map(Box::new)), + start, + ), None => left, }, ), @@ -182,28 +199,33 @@ impl<'a> Expr<'a> { expr_prec_layer!(addsub, muldivmod, "+", "-"); expr_prec_layer!(muldivmod, filtered, "*", "/", "%"); - fn filtered(i: &'a str, level: Level) -> ParseResult<'a, Self> { + fn filtered(i: &'a str, level: Level) -> ParseResult<'a, WithSpan<'a, Self>> { let (_, level) = level.nest(i)?; + let start = i; let (i, (obj, filters)) = tuple((|i| Self::prefix(i, level), many0(|i| filter(i, level))))(i)?; let mut res = obj; for (fname, args) in filters { - res = Self::Filter(Filter { - name: fname, - arguments: { - let mut args = args.unwrap_or_default(); - args.insert(0, res); - args - }, - }); + res = WithSpan::new( + Self::Filter(Filter { + name: fname, + arguments: { + let mut args = args.unwrap_or_default(); + args.insert(0, res); + args + }, + }), + start, + ); } Ok((i, res)) } - fn prefix(i: &'a str, mut level: Level) -> ParseResult<'a, Self> { + fn prefix(i: &'a str, mut level: Level) -> ParseResult<'a, WithSpan<'a, Self>> { let (_, nested) = level.nest(i)?; + let start = i; let (i, (ops, mut expr)) = pair(many0(ws(alt((tag("!"), tag("-"))))), |i| { Suffix::parse(i, nested) })(i)?; @@ -213,13 +235,13 @@ impl<'a> Expr<'a> { // without recursing the parser call stack. However, this can lead // to stack overflows in drop glue when the AST is very deep. level = level.nest(i)?.1; - expr = Self::Unary(op, Box::new(expr)); + expr = WithSpan::new(Self::Unary(op, Box::new(expr)), start); } Ok((i, expr)) } - fn single(i: &'a str, level: Level) -> ParseResult<'a, Self> { + fn single(i: &'a str, level: Level) -> ParseResult<'a, WithSpan<'a, Self>> { let (_, level) = level.nest(i)?; alt(( Self::num, @@ -231,21 +253,22 @@ impl<'a> Expr<'a> { ))(i) } - fn group(i: &'a str, level: Level) -> ParseResult<'a, Self> { + fn group(i: &'a str, level: Level) -> ParseResult<'a, WithSpan<'a, Self>> { let (_, level) = level.nest(i)?; + let start = i; let (i, expr) = preceded(ws(char('(')), opt(|i| Self::parse(i, level)))(i)?; let expr = match expr { Some(expr) => expr, None => { let (i, _) = char(')')(i)?; - return Ok((i, Self::Tuple(vec![]))); + return Ok((i, WithSpan::new(Self::Tuple(vec![]), start))); } }; let (i, comma) = ws(opt(peek(char(','))))(i)?; if comma.is_none() { let (i, _) = char(')')(i)?; - return Ok((i, Self::Group(Box::new(expr)))); + return Ok((i, WithSpan::new(Self::Group(Box::new(expr)), start))); } let mut exprs = vec![expr]; @@ -257,62 +280,68 @@ impl<'a> Expr<'a> { }, )(i)?; let (i, _) = pair(ws(opt(char(','))), char(')'))(i)?; - Ok((i, Self::Tuple(exprs))) + Ok((i, WithSpan::new(Self::Tuple(exprs), start))) } - fn array(i: &'a str, level: Level) -> ParseResult<'a, Self> { + fn array(i: &'a str, level: Level) -> ParseResult<'a, WithSpan<'a, Self>> { let (_, level) = level.nest(i)?; + let start = i; preceded( ws(char('[')), cut(terminated( map( separated_list0(char(','), ws(move |i| Self::parse(i, level))), - Self::Array, + |i| WithSpan::new(Self::Array(i), start), ), char(']'), )), )(i) } - fn path_var_bool(i: &'a str) -> ParseResult<'a, Self> { + fn path_var_bool(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; map(path_or_identifier, |v| match v { PathOrIdentifier::Path(v) => Self::Path(v), PathOrIdentifier::Identifier(v @ "true") => Self::BoolLit(v), PathOrIdentifier::Identifier(v @ "false") => Self::BoolLit(v), PathOrIdentifier::Identifier(v) => Self::Var(v), })(i) + .map(|(i, expr)| (i, WithSpan::new(expr, start))) } - fn str(i: &'a str) -> ParseResult<'a, Self> { - map(str_lit, Self::StrLit)(i) + fn str(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; + map(str_lit, |i| WithSpan::new(Self::StrLit(i), start))(i) } - fn num(i: &'a str) -> ParseResult<'a, Self> { - map(num_lit, Self::NumLit)(i) + fn num(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; + map(num_lit, |i| WithSpan::new(Self::NumLit(i), start))(i) } - fn char(i: &'a str) -> ParseResult<'a, Self> { - map(char_lit, Self::CharLit)(i) + fn char(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; + map(char_lit, |i| WithSpan::new(Self::CharLit(i), start))(i) } } #[derive(Clone, Debug, PartialEq)] pub struct Filter<'a> { pub name: &'a str, - pub arguments: Vec>, + pub arguments: Vec>>, } enum Suffix<'a> { Attr(&'a str), - Index(Expr<'a>), - Call(Vec>), + Index(WithSpan<'a, Expr<'a>>), + Call(Vec>>), // The value is the arguments of the macro call. MacroCall(&'a str), Try, } impl<'a> Suffix<'a> { - fn parse(i: &'a str, level: Level) -> ParseResult<'a, Expr<'a>> { + fn parse(i: &'a str, level: Level) -> ParseResult<'a, WithSpan<'a, Expr<'a>>> { let (_, level) = level.nest(i)?; let (mut i, mut expr) = Expr::single(i, level)?; loop { @@ -325,13 +354,15 @@ impl<'a> Suffix<'a> { )))(i)?; match suffix { - Some(Self::Attr(attr)) => expr = Expr::Attr(expr.into(), attr), - Some(Self::Index(index)) => expr = Expr::Index(expr.into(), index.into()), - Some(Self::Call(args)) => expr = Expr::Call(expr.into(), args), - Some(Self::Try) => expr = Expr::Try(expr.into()), - Some(Self::MacroCall(args)) => match expr { - Expr::Path(path) => expr = Expr::RustMacro(path, args), - Expr::Var(name) => expr = Expr::RustMacro(vec![name], args), + Some(Self::Attr(attr)) => expr = WithSpan::new(Expr::Attr(expr.into(), attr), i), + Some(Self::Index(index)) => { + expr = WithSpan::new(Expr::Index(expr.into(), index.into()), i) + } + Some(Self::Call(args)) => expr = WithSpan::new(Expr::Call(expr.into(), args), i), + Some(Self::Try) => expr = WithSpan::new(Expr::Try(expr.into()), i), + Some(Self::MacroCall(args)) => match expr.inner { + Expr::Path(path) => expr = WithSpan::new(Expr::RustMacro(path, args), i), + Expr::Var(name) => expr = WithSpan::new(Expr::RustMacro(vec![name], args), i), _ => return Err(nom::Err::Failure(error_position!(i, ErrorKind::Tag))), }, None => break, diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index 09581e95..fc33ba6e 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -708,7 +708,7 @@ impl Level { } #[allow(clippy::type_complexity)] -fn filter(i: &str, level: Level) -> ParseResult<'_, (&str, Option>>)> { +fn filter(i: &str, level: Level) -> ParseResult<'_, (&str, Option>>>)> { let (i, (_, fname, args)) = tuple(( char('|'), ws(identifier), diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index 708d8809..55e1c49b 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -11,7 +11,7 @@ use nom::error_position; use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1}; use nom::sequence::{delimited, pair, preceded, terminated, tuple}; -use crate::{not_ws, ErrorContext, ParseResult}; +use crate::{not_ws, ErrorContext, ParseResult, WithSpan}; use super::{ bool_lit, char_lit, filter, identifier, is_ws, keyword, num_lit, path_or_identifier, skip_till, @@ -22,7 +22,7 @@ use super::{ pub enum Node<'a> { Lit(Lit<'a>), Comment(Comment<'a>), - Expr(Ws, Expr<'a>), + Expr(Ws, WithSpan<'a, Expr<'a>>), Call(Call<'a>), Let(Let<'a>), If(If<'a>), @@ -397,7 +397,7 @@ impl<'a> Cond<'a> { #[derive(Debug, PartialEq)] pub struct CondTest<'a> { pub target: Option>, - pub expr: Expr<'a>, + pub expr: WithSpan<'a, Expr<'a>>, } impl<'a> CondTest<'a> { @@ -439,8 +439,8 @@ impl Whitespace { pub struct Loop<'a> { pub ws1: Ws, pub var: Target<'a>, - pub iter: Expr<'a>, - pub cond: Option>, + pub iter: WithSpan<'a, Expr<'a>>, + pub cond: Option>>, pub body: Vec>, pub ws2: Ws, pub else_nodes: Vec>, @@ -604,7 +604,9 @@ impl<'a> FilterBlock<'a> { cut(tuple(( ws(identifier), opt(|i| Expr::arguments(i, s.level.get(), false)), - many0(|i| filter(i, s.level.get())), + many0(|i| { + filter(i, s.level.get()).map(|(j, (name, params))| (j, (name, params, i))) + }), ws(|i| Ok((i, ()))), opt(Whitespace::parse), |i| s.tag_block_end(i), @@ -616,12 +618,12 @@ impl<'a> FilterBlock<'a> { name: filter_name, arguments: params.unwrap_or_default(), }; - for (filter_name, args) in extra_filters { + for (filter_name, args, span) in extra_filters { filters = Filter { name: filter_name, arguments: { let mut args = args.unwrap_or_default(); - args.insert(0, Expr::Filter(filters)); + args.insert(0, WithSpan::new(Expr::Filter(filters), span)); args }, }; @@ -685,7 +687,7 @@ pub struct Call<'a> { pub ws: Ws, pub scope: Option<&'a str>, pub name: &'a str, - pub args: Vec>, + pub args: Vec>>, } impl<'a> Call<'a> { @@ -718,7 +720,7 @@ impl<'a> Call<'a> { #[derive(Debug, PartialEq)] pub struct Match<'a> { pub ws1: Ws, - pub expr: Expr<'a>, + pub expr: WithSpan<'a, Expr<'a>>, pub arms: Vec>, pub ws2: Ws, } @@ -913,7 +915,7 @@ impl<'a> Raw<'a> { pub struct Let<'a> { pub ws: Ws, pub var: Target<'a>, - pub val: Option>, + pub val: Option>>, } impl<'a> Let<'a> { From a2c50d1271eab523addce2c251aa5261e33b174e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 May 2024 17:48:13 +0200 Subject: [PATCH 04/14] Update `Node` variants to use `WithSpan` --- askama_parser/src/node.rs | 307 +++++++++++++++++++++++--------------- 1 file changed, 184 insertions(+), 123 deletions(-) diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index 55e1c49b..a814dac6 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -20,23 +20,23 @@ use super::{ #[derive(Debug, PartialEq)] pub enum Node<'a> { - Lit(Lit<'a>), - Comment(Comment<'a>), + Lit(WithSpan<'a, Lit<'a>>), + Comment(WithSpan<'a, Comment<'a>>), Expr(Ws, WithSpan<'a, Expr<'a>>), - Call(Call<'a>), - Let(Let<'a>), - If(If<'a>), - Match(Match<'a>), - Loop(Box>), - Extends(Extends<'a>), - BlockDef(BlockDef<'a>), - Include(Include<'a>), - Import(Import<'a>), - Macro(Macro<'a>), - Raw(Raw<'a>), - Break(Ws), - Continue(Ws), - FilterBlock(FilterBlock<'a>), + Call(WithSpan<'a, Call<'a>>), + Let(WithSpan<'a, Let<'a>>), + If(WithSpan<'a, If<'a>>), + Match(WithSpan<'a, Match<'a>>), + Loop(Box>>), + Extends(WithSpan<'a, Extends<'a>>), + BlockDef(WithSpan<'a, BlockDef<'a>>), + Include(WithSpan<'a, Include<'a>>), + Import(WithSpan<'a, Import<'a>>), + Macro(WithSpan<'a, Macro<'a>>), + Raw(WithSpan<'a, Raw<'a>>), + Break(WithSpan<'a, Ws>), + Continue(WithSpan<'a, Ws>), + FilterBlock(WithSpan<'a, FilterBlock<'a>>), } impl<'a> Node<'a> { @@ -117,7 +117,7 @@ impl<'a> Node<'a> { i, ))); } - Ok((j, Self::Break(Ws(pws, nws)))) + Ok((j, Self::Break(WithSpan::new(Ws(pws, nws), i)))) } fn r#continue(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { @@ -133,7 +133,7 @@ impl<'a> Node<'a> { i, ))); } - Ok((j, Self::Continue(Ws(pws, nws)))) + Ok((j, Self::Continue(WithSpan::new(Ws(pws, nws), i)))) } fn expr(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { @@ -313,7 +313,8 @@ pub struct When<'a> { } impl<'a> When<'a> { - fn r#match(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn r#match(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; let mut p = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), @@ -327,16 +328,20 @@ impl<'a> When<'a> { let (i, (_, pws, _, (nws, _, nodes))) = p(i)?; Ok(( i, - Self { - ws: Ws(pws, nws), - target: Target::Name("_"), - nodes, - }, + WithSpan::new( + Self { + ws: Ws(pws, nws), + target: Target::Name("_"), + nodes, + }, + start, + ), )) } #[allow(clippy::self_named_constructors)] - fn when(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn when(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; let mut p = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), @@ -351,11 +356,14 @@ impl<'a> When<'a> { let (i, (_, pws, _, (target, nws, _, nodes))) = p(i)?; Ok(( i, - Self { - ws: Ws(pws, nws), - target, - nodes, - }, + WithSpan::new( + Self { + ws: Ws(pws, nws), + target, + nodes, + }, + start, + ), )) } } @@ -368,7 +376,8 @@ pub struct Cond<'a> { } impl<'a> Cond<'a> { - fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; let (i, (_, pws, cond, nws, _, nodes)) = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), @@ -385,11 +394,14 @@ impl<'a> Cond<'a> { ))(i)?; Ok(( i, - Self { - ws: Ws(pws, nws), - cond, - nodes, - }, + WithSpan::new( + Self { + ws: Ws(pws, nws), + cond, + nodes, + }, + start, + ), )) } } @@ -448,7 +460,7 @@ pub struct Loop<'a> { } impl<'a> Loop<'a> { - fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { fn content<'a>(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec>> { s.enter_loop(); let result = Node::many(i, s); @@ -456,6 +468,7 @@ impl<'a> Loop<'a> { result } + let start = i; let if_cond = preceded( ws(keyword("if")), cut(ws(|i| Expr::parse(i, s.level.get()))), @@ -507,16 +520,19 @@ impl<'a> Loop<'a> { let (nws3, else_block, pws3) = else_block.unwrap_or_default(); Ok(( i, - Self { - ws1: Ws(pws1, nws1), - var, - iter, - cond, - body, - ws2: Ws(pws2, nws3), - else_nodes: else_block, - ws3: Ws(pws3, nws2), - }, + WithSpan::new( + Self { + ws1: Ws(pws1, nws1), + var, + iter, + cond, + body, + ws2: Ws(pws2, nws3), + else_nodes: else_block, + ws3: Ws(pws3, nws2), + }, + start, + ), )) } } @@ -531,7 +547,7 @@ pub struct Macro<'a> { } impl<'a> Macro<'a> { - fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { fn parameters(i: &str) -> ParseResult<'_, Vec<&str>> { delimited( ws(char('(')), @@ -540,6 +556,7 @@ impl<'a> Macro<'a> { )(i) } + let start_s = i; let mut start = tuple(( opt(Whitespace::parse), ws(keyword("macro")), @@ -577,13 +594,16 @@ impl<'a> Macro<'a> { Ok(( i, - Self { - ws1: Ws(pws1, nws1), - name, - args: params.unwrap_or_default(), - nodes: contents, - ws2: Ws(pws2, nws2), - }, + WithSpan::new( + Self { + ws1: Ws(pws1, nws1), + name, + args: params.unwrap_or_default(), + nodes: contents, + ws2: Ws(pws2, nws2), + }, + start_s, + ), )) } } @@ -597,7 +617,8 @@ pub struct FilterBlock<'a> { } impl<'a> FilterBlock<'a> { - fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { + let start_s = i; let mut start = tuple(( opt(Whitespace::parse), ws(keyword("filter")), @@ -642,12 +663,15 @@ impl<'a> FilterBlock<'a> { Ok(( i, - Self { - ws1: Ws(pws1, nws1), - filters, - nodes, - ws2: Ws(pws2, nws2), - }, + WithSpan::new( + Self { + ws1: Ws(pws1, nws1), + filters, + nodes, + ws2: Ws(pws2, nws2), + }, + start_s, + ), )) } } @@ -660,7 +684,8 @@ pub struct Import<'a> { } impl<'a> Import<'a> { - fn parse(i: &'a str) -> ParseResult<'a, Self> { + fn parse(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; let mut p = tuple(( opt(Whitespace::parse), ws(keyword("import")), @@ -673,11 +698,14 @@ impl<'a> Import<'a> { let (i, (pws, _, (path, _, (scope, nws)))) = p(i)?; Ok(( i, - Self { - ws: Ws(pws, nws), - path, - scope, - }, + WithSpan::new( + Self { + ws: Ws(pws, nws), + path, + scope, + }, + start, + ), )) } } @@ -691,7 +719,8 @@ pub struct Call<'a> { } impl<'a> Call<'a> { - fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; let mut p = tuple(( opt(Whitespace::parse), ws(keyword("call")), @@ -707,12 +736,15 @@ impl<'a> Call<'a> { let args = args.unwrap_or_default(); Ok(( i, - Self { - ws: Ws(pws, nws), - scope, - name, - args, - }, + WithSpan::new( + Self { + ws: Ws(pws, nws), + scope, + name, + args, + }, + start, + ), )) } } @@ -721,12 +753,13 @@ impl<'a> Call<'a> { pub struct Match<'a> { pub ws1: Ws, pub expr: WithSpan<'a, Expr<'a>>, - pub arms: Vec>, + pub arms: Vec>>, pub ws2: Ws, } impl<'a> Match<'a> { - fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; let mut p = tuple(( opt(Whitespace::parse), ws(keyword("match")), @@ -758,12 +791,15 @@ impl<'a> Match<'a> { Ok(( i, - Self { - ws1: Ws(pws1, nws1), - expr, - arms, - ws2: Ws(pws2, nws2), - }, + WithSpan::new( + Self { + ws1: Ws(pws1, nws1), + expr, + arms, + ws2: Ws(pws2, nws2), + }, + start, + ), )) } } @@ -777,7 +813,8 @@ pub struct BlockDef<'a> { } impl<'a> BlockDef<'a> { - fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { + let start_s = i; let mut start = tuple(( opt(Whitespace::parse), ws(keyword("block")), @@ -806,12 +843,15 @@ impl<'a> BlockDef<'a> { Ok(( i, - BlockDef { - ws1: Ws(pws1, nws1), - name, - nodes, - ws2: Ws(pws2, nws2), - }, + WithSpan::new( + BlockDef { + ws1: Ws(pws1, nws1), + name, + nodes, + ws2: Ws(pws2, nws2), + }, + start_s, + ), )) } } @@ -844,7 +884,8 @@ pub struct Lit<'a> { } impl<'a> Lit<'a> { - fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; let p_start = alt(( tag(s.syntax.block_start), tag(s.syntax.comment_start), @@ -861,7 +902,7 @@ impl<'a> Lit<'a> { Some(content) => (i, content), None => ("", i), // there is no {block,comment,expr}_start: take everything }; - Ok((i, Self::split_ws_parts(content))) + Ok((i, WithSpan::new(Self::split_ws_parts(content), start))) } pub(crate) fn split_ws_parts(s: &'a str) -> Self { @@ -884,7 +925,8 @@ pub struct Raw<'a> { } impl<'a> Raw<'a> { - fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; let endraw = tuple(( |i| s.tag_block_start(i), opt(Whitespace::parse), @@ -907,7 +949,7 @@ impl<'a> Raw<'a> { let lit = Lit::split_ws_parts(contents); let ws1 = Ws(pws1, nws1); let ws2 = Ws(pws2, nws2); - Ok((i, Self { ws1, lit, ws2 })) + Ok((i, WithSpan::new(Self { ws1, lit, ws2 }, start))) } } @@ -919,7 +961,8 @@ pub struct Let<'a> { } impl<'a> Let<'a> { - fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; let mut p = tuple(( opt(Whitespace::parse), ws(alt((keyword("let"), keyword("set")))), @@ -936,11 +979,14 @@ impl<'a> Let<'a> { Ok(( i, - Let { - ws: Ws(pws, nws), - var, - val, - }, + WithSpan::new( + Let { + ws: Ws(pws, nws), + var, + val, + }, + start, + ), )) } } @@ -948,11 +994,12 @@ impl<'a> Let<'a> { #[derive(Debug, PartialEq)] pub struct If<'a> { pub ws: Ws, - pub branches: Vec>, + pub branches: Vec>>, } impl<'a> If<'a> { - fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; let mut p = tuple(( opt(Whitespace::parse), |i| CondTest::parse(i, s), @@ -973,19 +1020,25 @@ impl<'a> If<'a> { )); let (i, (pws1, cond, (nws1, _, (nodes, elifs, (_, pws2, _, nws2))))) = p(i)?; - let mut branches = vec![Cond { - ws: Ws(pws1, nws1), - cond: Some(cond), - nodes, - }]; + let mut branches = vec![WithSpan::new( + Cond { + ws: Ws(pws1, nws1), + cond: Some(cond), + nodes, + }, + start, + )]; branches.extend(elifs); Ok(( i, - Self { - ws: Ws(pws2, nws2), - branches, - }, + WithSpan::new( + Self { + ws: Ws(pws2, nws2), + branches, + }, + start, + ), )) } } @@ -997,7 +1050,8 @@ pub struct Include<'a> { } impl<'a> Include<'a> { - fn parse(i: &'a str) -> ParseResult<'a, Self> { + fn parse(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = i; let mut p = tuple(( opt(Whitespace::parse), ws(keyword("include")), @@ -1006,10 +1060,13 @@ impl<'a> Include<'a> { let (i, (pws, _, (path, nws))) = p(i)?; Ok(( i, - Self { - ws: Ws(pws, nws), - path, - }, + WithSpan::new( + Self { + ws: Ws(pws, nws), + path, + }, + start, + ), )) } } @@ -1020,7 +1077,7 @@ pub struct Extends<'a> { } impl<'a> Extends<'a> { - fn parse(i: &'a str) -> ParseResult<'a, Self> { + fn parse(i: &'a str) -> ParseResult<'a, WithSpan<'a, Self>> { let start = i; let (i, (pws, _, (path, nws))) = tuple(( @@ -1029,7 +1086,7 @@ impl<'a> Extends<'a> { cut(pair(ws(str_lit), opt(Whitespace::parse))), ))(i)?; match (pws, nws) { - (None, None) => Ok((i, Self { path })), + (None, None) => Ok((i, WithSpan::new(Self { path }, start))), (_, _) => Err(nom::Err::Failure(ErrorContext::new( "whitespace control is not allowed on `extends`", start, @@ -1045,7 +1102,7 @@ pub struct Comment<'a> { } impl<'a> Comment<'a> { - fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { + fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { #[derive(Debug, Clone, Copy)] enum Tag { Open, @@ -1085,6 +1142,7 @@ impl<'a> Comment<'a> { } } + let start = i; let (i, (pws, content)) = pair( preceded(|i| s.tag_comment_start(i), opt(Whitespace::parse)), recognize(cut(|i| content(i, s))), @@ -1102,10 +1160,13 @@ impl<'a> Comment<'a> { Ok(( i, - Self { - ws: Ws(pws, nws), - content, - }, + WithSpan::new( + Self { + ws: Ws(pws, nws), + content, + }, + start, + ), )) } } From e40056e0b82469564f465eb1857545af24d5051c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 May 2024 20:33:01 +0200 Subject: [PATCH 05/14] Add `Node::span` method --- askama_parser/src/node.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index a814dac6..561940a4 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -154,6 +154,28 @@ impl<'a> Node<'a> { false => Err(ErrorContext::unclosed("expression", s.syntax.expr_end, i).into()), } } + + pub fn span(&self) -> &str { + match self { + Self::Lit(span) => span.span, + Self::Comment(span) => span.span, + Self::Expr(_, span) => span.span, + Self::Call(span) => span.span, + Self::Let(span) => span.span, + Self::If(span) => span.span, + Self::Match(span) => span.span, + Self::Loop(span) => span.span, + Self::Extends(span) => span.span, + Self::BlockDef(span) => span.span, + Self::Include(span) => span.span, + Self::Import(span) => span.span, + Self::Macro(span) => span.span, + Self::Raw(span) => span.span, + Self::Break(span) => span.span, + Self::Continue(span) => span.span, + Self::FilterBlock(span) => span.span, + } + } } #[derive(Clone, Debug, PartialEq)] From c36eeb316619dd0cfdf6aecd04299f64e38d2e47 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 May 2024 22:59:13 +0200 Subject: [PATCH 06/14] Update askama_parser AST tests --- askama_parser/src/tests.rs | 609 +++++++++++++++++++++++-------------- 1 file changed, 389 insertions(+), 220 deletions(-) diff --git a/askama_parser/src/tests.rs b/askama_parser/src/tests.rs index 3035c080..28d4fa22 100644 --- a/askama_parser/src/tests.rs +++ b/askama_parser/src/tests.rs @@ -1,5 +1,11 @@ use super::node::{Lit, Whitespace, Ws}; -use super::{Ast, Expr, Filter, Node, Syntax}; +use super::{Ast, Expr, Filter, Node, Syntax, WithSpan}; + +impl WithSpan<'static, T> { + fn no_span(inner: T) -> Self { + Self { inner, span: "" } + } +} fn check_ws_split(s: &str, res: &(&str, &str, &str)) { let Lit { lws, val, rws } = Lit::split_ws_parts(s); @@ -32,30 +38,33 @@ fn test_parse_filter() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::Filter(Filter { + WithSpan::no_span(Expr::Filter(Filter { name: "e", - arguments: vec![Expr::Var("strvar")] - }), + arguments: vec![WithSpan::no_span(Expr::Var("strvar"))] + })), )], ); assert_eq!( Ast::from_str("{{ 2|abs }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Filter(Filter { + WithSpan::no_span(Expr::Filter(Filter { name: "abs", - arguments: vec![Expr::NumLit("2")] - }), + arguments: vec![WithSpan::no_span(Expr::NumLit("2"))] + })), )], ); assert_eq!( Ast::from_str("{{ -2|abs }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Filter(Filter { + WithSpan::no_span(Expr::Filter(Filter { name: "abs", - arguments: vec![Expr::Unary("-", Expr::NumLit("2").into())] - }), + arguments: vec![WithSpan::no_span(Expr::Unary( + "-", + WithSpan::no_span(Expr::NumLit("2")).into() + ))] + })), )], ); assert_eq!( @@ -64,12 +73,17 @@ fn test_parse_filter() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::Filter(Filter { + WithSpan::no_span(Expr::Filter(Filter { name: "abs", - arguments: vec![Expr::Group( - Expr::BinOp("-", Expr::NumLit("1").into(), Expr::NumLit("2").into()).into() - )], - },), + arguments: vec![WithSpan::no_span(Expr::Group( + WithSpan::no_span(Expr::BinOp( + "-", + WithSpan::no_span(Expr::NumLit("1")).into(), + WithSpan::no_span(Expr::NumLit("2")).into() + )) + .into() + ))], + })), )], ); } @@ -79,11 +93,17 @@ fn test_parse_numbers() { let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ 2 }}", None, &syntax).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Expr::NumLit("2"),)], + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::NumLit("2")) + )], ); assert_eq!( Ast::from_str("{{ 2.5 }}", None, &syntax).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Expr::NumLit("2.5"),)], + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::NumLit("2.5")) + )], ); } @@ -93,16 +113,25 @@ fn test_parse_var() { assert_eq!( Ast::from_str("{{ foo }}", None, &s).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Expr::Var("foo"))], + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::Var("foo")) + )], ); assert_eq!( Ast::from_str("{{ foo_bar }}", None, &s).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Expr::Var("foo_bar"))], + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::Var("foo_bar")) + )], ); assert_eq!( Ast::from_str("{{ none }}", None, &s).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Expr::Var("none"))], + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::Var("none")) + )], ); } @@ -112,16 +141,25 @@ fn test_parse_const() { assert_eq!( Ast::from_str("{{ FOO }}", None, &s).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Expr::Path(vec!["FOO"]))], + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::Path(vec!["FOO"])) + )], ); assert_eq!( Ast::from_str("{{ FOO_BAR }}", None, &s).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Expr::Path(vec!["FOO_BAR"]))], + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::Path(vec!["FOO_BAR"])) + )], ); assert_eq!( Ast::from_str("{{ NONE }}", None, &s).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Expr::Path(vec!["NONE"]))], + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::Path(vec!["NONE"])) + )], ); } @@ -131,16 +169,19 @@ fn test_parse_path() { assert_eq!( Ast::from_str("{{ None }}", None, &s).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Expr::Path(vec!["None"]))], + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::Path(vec!["None"])) + )], ); assert_eq!( Ast::from_str("{{ Some(123) }}", None, &s).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Call( - Box::new(Expr::Path(vec!["Some"])), - vec![Expr::NumLit("123")] - ), + WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Path(vec!["Some"]))), + vec![WithSpan::no_span(Expr::NumLit("123"))] + )), )], ); @@ -148,14 +189,20 @@ fn test_parse_path() { Ast::from_str("{{ Ok(123) }}", None, &s).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Call(Box::new(Expr::Path(vec!["Ok"])), vec![Expr::NumLit("123")]), + WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Path(vec!["Ok"]))), + vec![WithSpan::no_span(Expr::NumLit("123"))] + )), )], ); assert_eq!( Ast::from_str("{{ Err(123) }}", None, &s).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Call(Box::new(Expr::Path(vec!["Err"])), vec![Expr::NumLit("123")]), + WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Path(vec!["Err"]))), + vec![WithSpan::no_span(Expr::NumLit("123"))] + )), )], ); } @@ -168,10 +215,13 @@ fn test_parse_var_call() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::Call( - Box::new(Expr::Var("function")), - vec![Expr::StrLit("123"), Expr::NumLit("3")] - ), + WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Var("function"))), + vec![ + WithSpan::no_span(Expr::StrLit("123")), + WithSpan::no_span(Expr::NumLit("3")) + ] + )), )], ); } @@ -184,7 +234,7 @@ fn test_parse_path_call() { Ast::from_str("{{ Option::None }}", None, &s).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Path(vec!["Option", "None"]) + WithSpan::no_span(Expr::Path(vec!["Option", "None"])) )], ); assert_eq!( @@ -193,10 +243,10 @@ fn test_parse_path_call() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::Call( - Box::new(Expr::Path(vec!["Option", "Some"])), - vec![Expr::NumLit("123")], - ), + WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Path(vec!["Option", "Some"]))), + vec![WithSpan::no_span(Expr::NumLit("123"))], + ),) )], ); @@ -206,10 +256,13 @@ fn test_parse_path_call() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::Call( - Box::new(Expr::Path(vec!["self", "function"])), - vec![Expr::StrLit("123"), Expr::NumLit("3")], - ), + WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Path(vec!["self", "function"]))), + vec![ + WithSpan::no_span(Expr::StrLit("123")), + WithSpan::no_span(Expr::NumLit("3")) + ], + ),) )], ); } @@ -223,10 +276,12 @@ fn test_parse_root_path() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::Call( - Box::new(Expr::Path(vec!["std", "string", "String", "new"])), + WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Path(vec![ + "std", "string", "String", "new" + ]))), vec![] - ), + )), )], ); assert_eq!( @@ -235,10 +290,12 @@ fn test_parse_root_path() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::Call( - Box::new(Expr::Path(vec!["", "std", "string", "String", "new"])), + WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Path(vec![ + "", "std", "string", "String", "new" + ]))), vec![] - ), + )), )], ); } @@ -252,7 +309,7 @@ fn test_rust_macro() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::RustMacro(vec!["vec"], "1, 2, 3",), + WithSpan::no_span(Expr::RustMacro(vec!["vec"], "1, 2, 3")), )], ); assert_eq!( @@ -261,28 +318,43 @@ fn test_rust_macro() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::RustMacro(vec!["alloc", "vec"], "1, 2, 3",), + WithSpan::no_span(Expr::RustMacro(vec!["alloc", "vec"], "1, 2, 3")), )], ); assert_eq!( Ast::from_str("{{a!()}}", None, &syntax).unwrap().nodes, - [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["a"], ""))], + [Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::RustMacro(vec!["a"], "")) + )], ); assert_eq!( Ast::from_str("{{a !()}}", None, &syntax).unwrap().nodes, - [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["a"], ""))], + [Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::RustMacro(vec!["a"], "")) + )], ); assert_eq!( Ast::from_str("{{a! ()}}", None, &syntax).unwrap().nodes, - [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["a"], ""))], + [Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::RustMacro(vec!["a"], "")) + )], ); assert_eq!( Ast::from_str("{{a ! ()}}", None, &syntax).unwrap().nodes, - [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["a"], ""))], + [Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::RustMacro(vec!["a"], "")) + )], ); assert_eq!( Ast::from_str("{{A!()}}", None, &syntax).unwrap().nodes, - [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["A"], ""),)], + [Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::RustMacro(vec!["A"], "")) + )], ); assert_eq!( &*Ast::from_str("{{a.b.c!( hello )}}", None, &syntax) @@ -312,11 +384,16 @@ fn test_precedence() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "==", - Expr::BinOp("+", Expr::Var("a").into(), Expr::Var("b").into()).into(), - Expr::Var("c").into(), - ) + WithSpan::no_span(Expr::BinOp( + "+", + WithSpan::no_span(Expr::Var("a")).into(), + WithSpan::no_span(Expr::Var("b")).into() + )) + .into(), + WithSpan::no_span(Expr::Var("c")).into() + ),) )], ); assert_eq!( @@ -325,16 +402,26 @@ fn test_precedence() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "-", - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "+", - Expr::Var("a").into(), - Expr::BinOp("*", Expr::Var("b").into(), Expr::Var("c").into()).into(), - ) + WithSpan::no_span(Expr::Var("a")).into(), + WithSpan::no_span(Expr::BinOp( + "*", + WithSpan::no_span(Expr::Var("b")).into(), + WithSpan::no_span(Expr::Var("c")).into() + )) + .into() + )) .into(), - Expr::BinOp("/", Expr::Var("d").into(), Expr::Var("e").into()).into(), - ) + WithSpan::no_span(Expr::BinOp( + "/", + WithSpan::no_span(Expr::Var("d")).into(), + WithSpan::no_span(Expr::Var("e")).into() + )) + .into(), + )) )], ); assert_eq!( @@ -343,19 +430,24 @@ fn test_precedence() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "/", - Expr::BinOp( + Box::new(WithSpan::no_span(Expr::BinOp( "*", - Expr::Var("a").into(), - Expr::Group( - Expr::BinOp("+", Expr::Var("b").into(), Expr::Var("c").into()).into() - ) - .into() - ) - .into(), - Expr::Unary("-", Expr::Var("d").into()).into() - ) + Box::new(WithSpan::no_span(Expr::Var("a"))), + Box::new(WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span( + Expr::BinOp( + "+", + Box::new(WithSpan::no_span(Expr::Var("b"))), + Box::new(WithSpan::no_span(Expr::Var("c"))) + ) + ))))) + ))), + Box::new(WithSpan::no_span(Expr::Unary( + "-", + Box::new(WithSpan::no_span(Expr::Var("d"))) + ))) + )) )], ); assert_eq!( @@ -364,16 +456,23 @@ fn test_precedence() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "||", - Expr::BinOp( + Box::new(WithSpan::no_span(Expr::BinOp( "||", - Expr::Var("a").into(), - Expr::BinOp("&&", Expr::Var("b").into(), Expr::Var("c").into()).into(), - ) - .into(), - Expr::BinOp("&&", Expr::Var("d").into(), Expr::Var("e").into()).into(), - ) + Box::new(WithSpan::no_span(Expr::Var("a"))), + Box::new(WithSpan::no_span(Expr::BinOp( + "&&", + Box::new(WithSpan::no_span(Expr::Var("b"))), + Box::new(WithSpan::no_span(Expr::Var("c"))) + ))), + ))), + Box::new(WithSpan::no_span(Expr::BinOp( + "&&", + Box::new(WithSpan::no_span(Expr::Var("d"))), + Box::new(WithSpan::no_span(Expr::Var("e"))) + ))), + )) )], ); } @@ -387,11 +486,15 @@ fn test_associativity() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "+", - Expr::BinOp("+", Expr::Var("a").into(), Expr::Var("b").into()).into(), - Expr::Var("c").into() - ) + Box::new(WithSpan::no_span(Expr::BinOp( + "+", + Box::new(WithSpan::no_span(Expr::Var("a"))), + Box::new(WithSpan::no_span(Expr::Var("b"))) + ))), + Box::new(WithSpan::no_span(Expr::Var("c"))) + )) )], ); assert_eq!( @@ -400,11 +503,15 @@ fn test_associativity() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "*", - Expr::BinOp("*", Expr::Var("a").into(), Expr::Var("b").into()).into(), - Expr::Var("c").into() - ) + Box::new(WithSpan::no_span(Expr::BinOp( + "*", + Box::new(WithSpan::no_span(Expr::Var("a"))), + Box::new(WithSpan::no_span(Expr::Var("b"))) + ))), + Box::new(WithSpan::no_span(Expr::Var("c"))) + )) )], ); assert_eq!( @@ -413,11 +520,15 @@ fn test_associativity() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "&&", - Expr::BinOp("&&", Expr::Var("a").into(), Expr::Var("b").into()).into(), - Expr::Var("c").into() - ) + Box::new(WithSpan::no_span(Expr::BinOp( + "&&", + Box::new(WithSpan::no_span(Expr::Var("a"))), + Box::new(WithSpan::no_span(Expr::Var("b"))) + ))), + Box::new(WithSpan::no_span(Expr::Var("c"))) + )) )], ); assert_eq!( @@ -426,16 +537,19 @@ fn test_associativity() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "+", - Expr::BinOp( + Box::new(WithSpan::no_span(Expr::BinOp( "-", - Expr::BinOp("+", Expr::Var("a").into(), Expr::Var("b").into()).into(), - Expr::Var("c").into() - ) - .into(), - Expr::Var("d").into() - ) + Box::new(WithSpan::no_span(Expr::BinOp( + "+", + Box::new(WithSpan::no_span(Expr::Var("a"))), + Box::new(WithSpan::no_span(Expr::Var("b"))) + ))), + Box::new(WithSpan::no_span(Expr::Var("c"))) + ))), + Box::new(WithSpan::no_span(Expr::Var("d"))) + )) )], ); assert_eq!( @@ -444,26 +558,27 @@ fn test_associativity() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "==", - Expr::BinOp( + Box::new(WithSpan::no_span(Expr::BinOp( ">", - Expr::BinOp( + Box::new(WithSpan::no_span(Expr::BinOp( ">", - Expr::BinOp( + Box::new(WithSpan::no_span(Expr::BinOp( "!=", - Expr::BinOp("==", Expr::Var("a").into(), Expr::Var("b").into()).into(), - Expr::Var("c").into() - ) - .into(), - Expr::Var("d").into() - ) - .into(), - Expr::Var("e").into() - ) - .into(), - Expr::Var("f").into() - ) + Box::new(WithSpan::no_span(Expr::BinOp( + "==", + Box::new(WithSpan::no_span(Expr::Var("a"))), + Box::new(WithSpan::no_span(Expr::Var("b"))) + ))), + Box::new(WithSpan::no_span(Expr::Var("c"))) + ))), + Box::new(WithSpan::no_span(Expr::Var("d"))) + ))), + Box::new(WithSpan::no_span(Expr::Var("e"))) + ))), + Box::new(WithSpan::no_span(Expr::Var("f"))) + )) )], ); } @@ -475,13 +590,13 @@ fn test_odd_calls() { Ast::from_str("{{ a[b](c) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Call( - Box::new(Expr::Index( - Box::new(Expr::Var("a")), - Box::new(Expr::Var("b")) - )), - vec![Expr::Var("c")], - ), + WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Index( + Box::new(WithSpan::no_span(Expr::Var("a"))), + Box::new(WithSpan::no_span(Expr::Var("b"))) + ))), + vec![WithSpan::no_span(Expr::Var("c"))], + ),) )], ); assert_eq!( @@ -490,14 +605,16 @@ fn test_odd_calls() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::Call( - Box::new(Expr::Group(Box::new(Expr::BinOp( - "+", - Box::new(Expr::Var("a")), - Box::new(Expr::Var("b")) - )))), - vec![Expr::Var("c")], - ), + WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span( + Expr::BinOp( + "+", + Box::new(WithSpan::no_span(Expr::Var("a"))), + Box::new(WithSpan::no_span(Expr::Var("b"))) + ) + ))))), + vec![WithSpan::no_span(Expr::Var("c"))], + ),) )], ); assert_eq!( @@ -506,65 +623,79 @@ fn test_odd_calls() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "+", - Box::new(Expr::Var("a")), - Box::new(Expr::Call(Box::new(Expr::Var("b")), vec![Expr::Var("c")])), - ), + Box::new(WithSpan::no_span(Expr::Var("a"))), + Box::new(WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Var("b"))), + vec![WithSpan::no_span(Expr::Var("c"))] + ))), + )), )], ); assert_eq!( Ast::from_str("{{ (-a)(b) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Call( - Box::new(Expr::Group(Box::new(Expr::Unary( - "-", - Box::new(Expr::Var("a")) - )))), - vec![Expr::Var("b")], - ), + WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span( + Expr::Unary("-", Box::new(WithSpan::no_span(Expr::Var("a")))) + ))))), + vec![WithSpan::no_span(Expr::Var("b"))], + ),) )], ); assert_eq!( Ast::from_str("{{ -a(b) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Unary( + WithSpan::no_span(Expr::Unary( "-", - Box::new(Expr::Call(Box::new(Expr::Var("a")), vec![Expr::Var("b")])) - ), + Box::new(WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Var("a"))), + vec![WithSpan::no_span(Expr::Var("b"))] + ))) + ),) )], ); assert_eq!( Ast::from_str("{{ a(b)|c }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Filter(Filter { + WithSpan::no_span(Expr::Filter(Filter { name: "c", - arguments: vec![Expr::Call(Box::new(Expr::Var("a")), vec![Expr::Var("b")])] - }), + arguments: vec![WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Var("a"))), + vec![WithSpan::no_span(Expr::Var("b"))] + ))] + }),) )] ); assert_eq!( Ast::from_str("{{ a(b)| c }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Filter(Filter { + WithSpan::no_span(Expr::Filter(Filter { name: "c", - arguments: vec![Expr::Call(Box::new(Expr::Var("a")), vec![Expr::Var("b")])] - }), + arguments: vec![WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Var("a"))), + vec![WithSpan::no_span(Expr::Var("b"))] + ))] + })), )] ); assert_eq!( Ast::from_str("{{ a(b) |c }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "|", - Box::new(Expr::Call(Box::new(Expr::Var("a")), vec![Expr::Var("b")])), - Box::new(Expr::Var("c")) - ), + Box::new(WithSpan::no_span(Expr::Call( + Box::new(WithSpan::no_span(Expr::Var("a"))), + vec![WithSpan::no_span(Expr::Var("b"))] + ))), + Box::new(WithSpan::no_span(Expr::Var("c"))) + ),) )] ); } @@ -633,55 +764,64 @@ fn test_parse_tuple() { let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ () }}", None, &syntax).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Expr::Tuple(vec![]),)], + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::Tuple(vec![]),) + )], ); assert_eq!( Ast::from_str("{{ (1) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Group(Box::new(Expr::NumLit("1"))), + WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span(Expr::NumLit("1"))),)) )], ); assert_eq!( Ast::from_str("{{ (1,) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Tuple(vec![Expr::NumLit("1")]), + WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(Expr::NumLit("1"))])), )], ); assert_eq!( Ast::from_str("{{ (1, ) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Tuple(vec![Expr::NumLit("1")]), + WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(Expr::NumLit("1"))])), )], ); assert_eq!( Ast::from_str("{{ (1 ,) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Tuple(vec![Expr::NumLit("1")]), + WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(Expr::NumLit("1"))])), )], ); assert_eq!( Ast::from_str("{{ (1 , ) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Tuple(vec![Expr::NumLit("1")]), + WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span(Expr::NumLit("1"))])), )], ); assert_eq!( Ast::from_str("{{ (1, 2) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Tuple(vec![Expr::NumLit("1"), Expr::NumLit("2")]), + WithSpan::no_span(Expr::Tuple(vec![ + WithSpan::no_span(Expr::NumLit("1")), + WithSpan::no_span(Expr::NumLit("2")) + ])), )], ); assert_eq!( Ast::from_str("{{ (1, 2,) }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Tuple(vec![Expr::NumLit("1"), Expr::NumLit("2")]), + WithSpan::no_span(Expr::Tuple(vec![ + WithSpan::no_span(Expr::NumLit("1")), + WithSpan::no_span(Expr::NumLit("2")) + ])), )], ); assert_eq!( @@ -690,21 +830,21 @@ fn test_parse_tuple() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::Tuple(vec![ - Expr::NumLit("1"), - Expr::NumLit("2"), - Expr::NumLit("3") - ]), + WithSpan::no_span(Expr::Tuple(vec![ + WithSpan::no_span(Expr::NumLit("1")), + WithSpan::no_span(Expr::NumLit("2")), + WithSpan::no_span(Expr::NumLit("3")) + ])), )], ); assert_eq!( Ast::from_str("{{ ()|abs }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Filter(Filter { + WithSpan::no_span(Expr::Filter(Filter { name: "abs", - arguments: vec![Expr::Tuple(vec![])] - }), + arguments: vec![WithSpan::no_span(Expr::Tuple(vec![]))] + })), )], ); assert_eq!( @@ -713,21 +853,23 @@ fn test_parse_tuple() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "|", - Box::new(Expr::Tuple(vec![])), - Box::new(Expr::Var("abs")) - ), + Box::new(WithSpan::no_span(Expr::Tuple(vec![]))), + Box::new(WithSpan::no_span(Expr::Var("abs"))) + )), )], ); assert_eq!( Ast::from_str("{{ (1)|abs }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Filter(Filter { + WithSpan::no_span(Expr::Filter(Filter { name: "abs", - arguments: vec![Expr::Group(Box::new(Expr::NumLit("1")))] - }), + arguments: vec![WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span( + Expr::NumLit("1") + ))))] + })), )], ); assert_eq!( @@ -736,11 +878,13 @@ fn test_parse_tuple() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "|", - Box::new(Expr::Group(Box::new(Expr::NumLit("1")))), - Box::new(Expr::Var("abs")) - ), + Box::new(WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span( + Expr::NumLit("1") + ))))), + Box::new(WithSpan::no_span(Expr::Var("abs"))) + )), )], ); assert_eq!( @@ -749,10 +893,12 @@ fn test_parse_tuple() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::Filter(Filter { + WithSpan::no_span(Expr::Filter(Filter { name: "abs", - arguments: vec![Expr::Tuple(vec![Expr::NumLit("1")])] - }), + arguments: vec![WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span( + Expr::NumLit("1") + )]))] + })), )], ); assert_eq!( @@ -761,11 +907,13 @@ fn test_parse_tuple() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "|", - Box::new(Expr::Tuple(vec![Expr::NumLit("1")])), - Box::new(Expr::Var("abs")) - ), + Box::new(WithSpan::no_span(Expr::Tuple(vec![WithSpan::no_span( + Expr::NumLit("1") + )]))), + Box::new(WithSpan::no_span(Expr::Var("abs"))) + )), )], ); assert_eq!( @@ -774,10 +922,13 @@ fn test_parse_tuple() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::Filter(Filter { + WithSpan::no_span(Expr::Filter(Filter { name: "abs", - arguments: vec![Expr::Tuple(vec![Expr::NumLit("1"), Expr::NumLit("2")])] - }), + arguments: vec![WithSpan::no_span(Expr::Tuple(vec![ + WithSpan::no_span(Expr::NumLit("1")), + WithSpan::no_span(Expr::NumLit("2")) + ]))] + })), )], ); assert_eq!( @@ -786,11 +937,14 @@ fn test_parse_tuple() { .nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "|", - Box::new(Expr::Tuple(vec![Expr::NumLit("1"), Expr::NumLit("2")])), - Box::new(Expr::Var("abs")) - ), + Box::new(WithSpan::no_span(Expr::Tuple(vec![ + WithSpan::no_span(Expr::NumLit("1")), + WithSpan::no_span(Expr::NumLit("2")) + ]))), + Box::new(WithSpan::no_span(Expr::Var("abs"))) + )), )], ); } @@ -810,86 +964,101 @@ fn test_parse_array() { let syntax = Syntax::default(); assert_eq!( Ast::from_str("{{ [] }}", None, &syntax).unwrap().nodes, - vec![Node::Expr(Ws(None, None), Expr::Array(vec![]))], + vec![Node::Expr( + Ws(None, None), + WithSpan::no_span(Expr::Array(vec![])) + )], ); assert_eq!( Ast::from_str("{{ [1] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Array(vec![Expr::NumLit("1")]) + WithSpan::no_span(Expr::Array(vec![WithSpan::no_span(Expr::NumLit("1"))])) )], ); assert_eq!( Ast::from_str("{{ [ 1] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Array(vec![Expr::NumLit("1")]) + WithSpan::no_span(Expr::Array(vec![WithSpan::no_span(Expr::NumLit("1"))])) )], ); assert_eq!( Ast::from_str("{{ [1 ] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Array(vec![Expr::NumLit("1")]) + WithSpan::no_span(Expr::Array(vec![WithSpan::no_span(Expr::NumLit("1"))])) )], ); assert_eq!( Ast::from_str("{{ [1,2] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Array(vec![Expr::NumLit("1"), Expr::NumLit("2")]) + WithSpan::no_span(Expr::Array(vec![ + WithSpan::no_span(Expr::NumLit("1")), + WithSpan::no_span(Expr::NumLit("2")) + ])) )], ); assert_eq!( Ast::from_str("{{ [1 ,2] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Array(vec![Expr::NumLit("1"), Expr::NumLit("2")]) + WithSpan::no_span(Expr::Array(vec![ + WithSpan::no_span(Expr::NumLit("1")), + WithSpan::no_span(Expr::NumLit("2")) + ])) )], ); assert_eq!( Ast::from_str("{{ [1, 2] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Array(vec![Expr::NumLit("1"), Expr::NumLit("2")]) + WithSpan::no_span(Expr::Array(vec![ + WithSpan::no_span(Expr::NumLit("1")), + WithSpan::no_span(Expr::NumLit("2")) + ])) )], ); assert_eq!( Ast::from_str("{{ [1,2 ] }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Array(vec![Expr::NumLit("1"), Expr::NumLit("2")]) + WithSpan::no_span(Expr::Array(vec![ + WithSpan::no_span(Expr::NumLit("1")), + WithSpan::no_span(Expr::NumLit("2")) + ])) )], ); assert_eq!( Ast::from_str("{{ []|foo }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Filter(Filter { + WithSpan::no_span(Expr::Filter(Filter { name: "foo", - arguments: vec![Expr::Array(vec![])] - }) + arguments: vec![WithSpan::no_span(Expr::Array(vec![]))] + })) )], ); assert_eq!( Ast::from_str("{{ []| foo }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::Filter(Filter { + WithSpan::no_span(Expr::Filter(Filter { name: "foo", - arguments: vec![Expr::Array(vec![])] - }) + arguments: vec![WithSpan::no_span(Expr::Array(vec![]))] + })) )], ); assert_eq!( Ast::from_str("{{ [] |foo }}", None, &syntax).unwrap().nodes, vec![Node::Expr( Ws(None, None), - Expr::BinOp( + WithSpan::no_span(Expr::BinOp( "|", - Box::new(Expr::Array(vec![])), - Box::new(Expr::Var("foo")) - ), + Box::new(WithSpan::no_span(Expr::Array(vec![]))), + Box::new(WithSpan::no_span(Expr::Var("foo"))) + )), )], ); } From e47b97363d24ae23e6aa42e680d1e2daa503efca Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 May 2024 21:23:06 +0200 Subject: [PATCH 07/14] Update to new `WithSpan` type in `askama_derive` --- askama_derive/src/generator.rs | 188 +++++++++++++++++---------------- askama_derive/src/heritage.rs | 10 +- 2 files changed, 104 insertions(+), 94 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index decb01cb..4e0fa36c 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -14,7 +14,7 @@ use parser::node::{ Call, Comment, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Match, Target, Whitespace, Ws, }; -use parser::{Expr, Filter, Node}; +use parser::{Expr, Filter, Node, WithSpan}; use quote::quote; pub(crate) struct Generator<'a> { @@ -312,13 +312,13 @@ impl<'a> Generator<'a> { // No whitespace handling: child template top-level is not used, // except for the blocks defined in it. } - Node::Break(ws) => { - self.handle_ws(ws); + Node::Break(ref ws) => { + self.handle_ws(**ws); self.write_buf_writable(buf)?; buf.writeln("break;")?; } - Node::Continue(ws) => { - self.handle_ws(ws); + Node::Continue(ref ws) => { + self.handle_ws(**ws); self.write_buf_writable(buf)?; buf.writeln("continue;")?; } @@ -368,7 +368,7 @@ impl<'a> Generator<'a> { // If this is a chain condition, then we need to declare the variable after the // left expression has been handled but before the right expression is handled // but this one should have access to the let-bound variable. - match expr { + match &**expr { Expr::BinOp(op, ref left, ref right) if *op == "||" || *op == "&&" => { self.visit_expr(&mut expr_buf, left)?; self.visit_target(buf, true, true, target); @@ -470,7 +470,7 @@ impl<'a> Generator<'a> { &mut self, ctx: &Context<'a>, buf: &mut Buffer, - loop_block: &'a Loop<'_>, + loop_block: &'a WithSpan<'_, Loop<'_>>, ) -> Result { self.handle_ws(loop_block.ws1); self.locals.push(); @@ -484,7 +484,7 @@ impl<'a> Generator<'a> { if has_else_nodes { buf.writeln("let mut _did_loop = false;")?; } - match loop_block.iter { + match &*loop_block.iter { Expr::Range(_, _, _) => buf.writeln(&format!("let _iter = {expr_code};")), Expr::Array(..) => buf.writeln(&format!("let _iter = {expr_code}.iter();")), // If `iter` is a call then we assume it's something that returns @@ -608,10 +608,10 @@ impl<'a> Generator<'a> { let mut named_arguments = HashMap::new(); // Since named arguments can only be passed last, we only need to check if the last argument // is a named one. - if let Some(Expr::NamedArgument(_, _)) = args.last() { + if let Some(Expr::NamedArgument(_, _)) = args.last().map(|expr| &**expr) { // First we check that all named arguments actually exist in the called item. for arg in args.iter().rev() { - let Expr::NamedArgument(arg_name, _) = arg else { + let Expr::NamedArgument(arg_name, _) = &**arg else { break; }; if !def.args.iter().any(|arg| arg == arg_name) { @@ -649,7 +649,7 @@ impl<'a> Generator<'a> { &args[index] } }; - match expr { + match &**expr { // If `expr` is already a form of variable then // don't reintroduce a new variable. This is // to avoid moving non-copyable values. @@ -992,7 +992,7 @@ impl<'a> Generator<'a> { Ok(size_hint) } - fn write_expr(&mut self, ws: Ws, s: &'a Expr<'a>) { + fn write_expr(&mut self, ws: Ws, s: &'a WithSpan<'a, Expr<'a>>) { self.handle_ws(ws); self.buf_writable.push(Writable::Expr(s)); } @@ -1169,13 +1169,13 @@ impl<'a> Generator<'a> { } } - fn write_comment(&mut self, comment: &'a Comment<'_>) { + fn write_comment(&mut self, comment: &'a WithSpan<'_, Comment<'_>>) { self.handle_ws(comment.ws); } /* Visitor methods for expression types */ - fn visit_expr_root(&mut self, expr: &Expr<'_>) -> Result { + fn visit_expr_root(&mut self, expr: &WithSpan<'_, Expr<'_>>) -> Result { let mut buf = Buffer::new(0); self.visit_expr(&mut buf, expr)?; Ok(buf.buf) @@ -1184,9 +1184,9 @@ impl<'a> Generator<'a> { fn visit_expr( &mut self, buf: &mut Buffer, - expr: &Expr<'_>, + expr: &WithSpan<'_, Expr<'_>>, ) -> Result { - Ok(match *expr { + Ok(match **expr { Expr::BoolLit(s) => self.visit_bool_lit(buf, s), Expr::NumLit(s) => self.visit_num_lit(buf, s), Expr::StrLit(s) => self.visit_str_lit(buf, s), @@ -1218,7 +1218,7 @@ impl<'a> Generator<'a> { fn visit_try( &mut self, buf: &mut Buffer, - expr: &Expr<'_>, + expr: &WithSpan<'_, Expr<'_>>, ) -> Result { buf.write("::core::result::Result::map_err("); self.visit_expr(buf, expr)?; @@ -1241,7 +1241,7 @@ impl<'a> Generator<'a> { &mut self, buf: &mut Buffer, name: &str, - args: &[Expr<'_>], + args: &[WithSpan<'_, Expr<'_>>], ) -> Result { match name { "as_ref" => return self._visit_as_ref_filter(buf, args), @@ -1268,7 +1268,7 @@ impl<'a> Generator<'a> { fn _visit_as_ref_filter( &mut self, buf: &mut Buffer, - args: &[Expr<'_>], + args: &[WithSpan<'_, Expr<'_>>], ) -> Result { let arg = match args { [arg] => arg, @@ -1282,7 +1282,7 @@ impl<'a> Generator<'a> { fn _visit_deref_filter( &mut self, buf: &mut Buffer, - args: &[Expr<'_>], + args: &[WithSpan<'_, Expr<'_>>], ) -> Result { let arg = match args { [arg] => arg, @@ -1296,7 +1296,7 @@ impl<'a> Generator<'a> { fn _visit_json_filter( &mut self, buf: &mut Buffer, - args: &[Expr<'_>], + args: &[WithSpan<'_, Expr<'_>>], ) -> Result { if cfg!(not(feature = "serde-json")) { return Err("the `json` filter requires the `serde-json` feature to be enabled".into()); @@ -1315,7 +1315,7 @@ impl<'a> Generator<'a> { fn _visit_safe_filter( &mut self, buf: &mut Buffer, - args: &[Expr<'_>], + args: &[WithSpan<'_, Expr<'_>>], ) -> Result { if args.len() != 1 { return Err("unexpected argument(s) in `safe` filter".into()); @@ -1332,12 +1332,12 @@ impl<'a> Generator<'a> { fn _visit_escape_filter( &mut self, buf: &mut Buffer, - args: &[Expr<'_>], + args: &[WithSpan<'_, Expr<'_>>], ) -> Result { if args.len() > 2 { return Err("only two arguments allowed to escape filter".into()); } - let opt_escaper = match args.get(1) { + let opt_escaper = match args.get(1).map(|expr| &**expr) { Some(Expr::StrLit(name)) => Some(*name), Some(_) => return Err("invalid escaper type for escape filter".into()), None => None, @@ -1364,44 +1364,46 @@ impl<'a> Generator<'a> { fn _visit_format_filter( &mut self, buf: &mut Buffer, - args: &[Expr<'_>], + args: &[WithSpan<'_, Expr<'_>>], ) -> Result { - let fmt = match args { - [Expr::StrLit(fmt), ..] => fmt, - _ => return Err(r#"use filter format like `"a={} b={}"|format(a, b)`"#.into()), - }; - buf.write("::std::format!("); - self.visit_str_lit(buf, fmt); - if args.len() > 1 { - buf.write(", "); - self._visit_args(buf, &args[1..])?; + if !args.is_empty() { + if let Expr::StrLit(fmt) = *args[0] { + buf.write("::std::format!("); + self.visit_str_lit(buf, fmt); + if args.len() > 1 { + buf.write(", "); + self._visit_args(buf, &args[1..])?; + } + buf.write(")"); + return Ok(DisplayWrap::Unwrapped); + } } - buf.write(")"); - Ok(DisplayWrap::Unwrapped) + Err(r#"use filter format like `"a={} b={}"|format(a, b)`"#.into()) } fn _visit_fmt_filter( &mut self, buf: &mut Buffer, - args: &[Expr<'_>], + args: &[WithSpan<'_, Expr<'_>>], ) -> Result { - let fmt = match args { - [_, Expr::StrLit(fmt)] => fmt, - _ => return Err(r#"use filter fmt like `value|fmt("{:?}")`"#.into()), - }; - buf.write("::std::format!("); - self.visit_str_lit(buf, fmt); - buf.write(", "); - self._visit_args(buf, &args[..1])?; - buf.write(")"); - Ok(DisplayWrap::Unwrapped) + if let [_, arg2] = args { + if let Expr::StrLit(fmt) = **arg2 { + buf.write("::std::format!("); + self.visit_str_lit(buf, fmt); + buf.write(", "); + self._visit_args(buf, &args[..1])?; + buf.write(")"); + return Ok(DisplayWrap::Unwrapped); + } + } + return Err(r#"use filter fmt like `value|fmt("{:?}")`"#.into()); } // Force type coercion on first argument to `join` filter (see #39). fn _visit_join_filter( &mut self, buf: &mut Buffer, - args: &[Expr<'_>], + args: &[WithSpan<'_, Expr<'_>>], ) -> Result { buf.write(CRATE); buf.write("::filters::join((&"); @@ -1418,7 +1420,11 @@ impl<'a> Generator<'a> { Ok(DisplayWrap::Unwrapped) } - fn _visit_args(&mut self, buf: &mut Buffer, args: &[Expr<'_>]) -> Result<(), CompileError> { + fn _visit_args( + &mut self, + buf: &mut Buffer, + args: &[WithSpan<'_, Expr<'_>>], + ) -> Result<(), CompileError> { if args.is_empty() { return Ok(()); } @@ -1433,8 +1439,8 @@ impl<'a> Generator<'a> { buf.write("&("); } - match arg { - Expr::Call(left, _) if !matches!(left.as_ref(), Expr::Path(_)) => { + match **arg { + Expr::Call(ref left, _) if !matches!(***left, Expr::Path(_)) => { buf.writeln("{")?; self.visit_expr(buf, arg)?; buf.writeln("}")?; @@ -1454,10 +1460,10 @@ impl<'a> Generator<'a> { fn visit_attr( &mut self, buf: &mut Buffer, - obj: &Expr<'_>, + obj: &WithSpan<'_, Expr<'_>>, attr: &str, ) -> Result { - if let Expr::Var(name) = *obj { + if let Expr::Var(name) = **obj { if name == "loop" { if attr == "index" { buf.write("(_loop_item.index + 1)"); @@ -1484,8 +1490,8 @@ impl<'a> Generator<'a> { fn visit_index( &mut self, buf: &mut Buffer, - obj: &Expr<'_>, - key: &Expr<'_>, + obj: &WithSpan<'_, Expr<'_>>, + key: &WithSpan<'_, Expr<'_>>, ) -> Result { buf.write("&"); self.visit_expr(buf, obj)?; @@ -1498,14 +1504,14 @@ impl<'a> Generator<'a> { fn visit_call( &mut self, buf: &mut Buffer, - left: &Expr<'_>, - args: &[Expr<'_>], + left: &WithSpan<'_, Expr<'_>>, + args: &[WithSpan<'_, Expr<'_>>], ) -> Result { - match left { - Expr::Attr(left, method) if **left == Expr::Var("loop") => match *method { + match &**left { + Expr::Attr(left, method) if ***left == Expr::Var("loop") => match *method { "cycle" => match args { [arg] => { - if matches!(arg, Expr::Array(arr) if arr.is_empty()) { + if matches!(**arg, Expr::Array(ref arr) if arr.is_empty()) { return Err("loop.cycle(…) cannot use an empty array".into()); } buf.write("({"); @@ -1525,17 +1531,16 @@ impl<'a> Generator<'a> { }, s => return Err(format!("unknown loop method: {s:?}").into()), }, - left => { - match left { + sub_left => { + match sub_left { Expr::Var(name) => match self.locals.resolve(name) { Some(resolved) => buf.write(&resolved), None => buf.write(&format!("(&self.{})", normalize_identifier(name))), }, - left => { + _ => { self.visit_expr(buf, left)?; } } - buf.write("("); self._visit_args(buf, args)?; buf.write(")"); @@ -1548,7 +1553,7 @@ impl<'a> Generator<'a> { &mut self, buf: &mut Buffer, op: &str, - inner: &Expr<'_>, + inner: &WithSpan<'_, Expr<'_>>, ) -> Result { buf.write(op); self.visit_expr(buf, inner)?; @@ -1559,8 +1564,8 @@ impl<'a> Generator<'a> { &mut self, buf: &mut Buffer, op: &str, - left: Option<&Expr<'_>>, - right: Option<&Expr<'_>>, + left: Option<&WithSpan<'_, Expr<'_>>>, + right: Option<&WithSpan<'_, Expr<'_>>>, ) -> Result { if let Some(left) = left { self.visit_expr(buf, left)?; @@ -1576,8 +1581,8 @@ impl<'a> Generator<'a> { &mut self, buf: &mut Buffer, op: &str, - left: &Expr<'_>, - right: &Expr<'_>, + left: &WithSpan<'_, Expr<'_>>, + right: &WithSpan<'_, Expr<'_>>, ) -> Result { self.visit_expr(buf, left)?; buf.write(&format!(" {op} ")); @@ -1588,7 +1593,7 @@ impl<'a> Generator<'a> { fn visit_group( &mut self, buf: &mut Buffer, - inner: &Expr<'_>, + inner: &WithSpan<'_, Expr<'_>>, ) -> Result { buf.write("("); self.visit_expr(buf, inner)?; @@ -1599,7 +1604,7 @@ impl<'a> Generator<'a> { fn visit_tuple( &mut self, buf: &mut Buffer, - exprs: &[Expr<'_>], + exprs: &[WithSpan<'_, Expr<'_>>], ) -> Result { buf.write("("); for (index, expr) in exprs.iter().enumerate() { @@ -1616,7 +1621,7 @@ impl<'a> Generator<'a> { fn visit_named_argument( &mut self, buf: &mut Buffer, - expr: &Expr<'_>, + expr: &WithSpan<'_, Expr<'_>>, ) -> Result { self.visit_expr(buf, expr)?; Ok(DisplayWrap::Unwrapped) @@ -1625,7 +1630,7 @@ impl<'a> Generator<'a> { fn visit_array( &mut self, buf: &mut Buffer, - elements: &[Expr<'_>], + elements: &[WithSpan<'_, Expr<'_>>], ) -> Result { buf.write("["); for (i, el) in elements.iter().enumerate() { @@ -2026,8 +2031,8 @@ fn is_copyable_within_op(expr: &Expr<'_>, within_op: bool) -> bool { /// Returns `true` if this is an `Attr` where the `obj` is `"self"`. pub(crate) fn is_attr_self(expr: &Expr<'_>) -> bool { match expr { - Expr::Attr(obj, _) if matches!(obj.as_ref(), Expr::Var("self")) => true, - Expr::Attr(obj, _) if matches!(obj.as_ref(), Expr::Attr(..)) => is_attr_self(obj), + Expr::Attr(obj, _) if matches!(***obj, Expr::Var("self")) => true, + Expr::Attr(obj, _) if matches!(***obj, Expr::Attr(..)) => is_attr_self(obj), _ => false, } } @@ -2035,8 +2040,8 @@ pub(crate) fn is_attr_self(expr: &Expr<'_>) -> bool { /// Returns `true` if the outcome of this expression may be used multiple times in the same /// `write!()` call, without evaluating the expression again, i.e. the expression should be /// side-effect free. -pub(crate) fn is_cacheable(expr: &Expr<'_>) -> bool { - match expr { +pub(crate) fn is_cacheable(expr: &WithSpan<'_, Expr<'_>>) -> bool { + match &**expr { // Literals are the definition of pure: Expr::BoolLit(_) => true, Expr::NumLit(_) => true, @@ -2047,18 +2052,18 @@ pub(crate) fn is_cacheable(expr: &Expr<'_>) -> bool { Expr::Path(_) => true, // Check recursively: Expr::Array(args) => args.iter().all(is_cacheable), - Expr::Attr(lhs, _) => is_cacheable(lhs), - Expr::Index(lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs), + Expr::Attr(lhs, _) => is_cacheable(&lhs), + Expr::Index(lhs, rhs) => is_cacheable(&lhs) && is_cacheable(&rhs), Expr::Filter(Filter { arguments, .. }) => arguments.iter().all(is_cacheable), - Expr::Unary(_, arg) => is_cacheable(arg), - Expr::BinOp(_, lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs), + Expr::Unary(_, arg) => is_cacheable(&*arg), + Expr::BinOp(_, lhs, rhs) => is_cacheable(&lhs) && is_cacheable(&rhs), Expr::Range(_, lhs, rhs) => { lhs.as_ref().map_or(true, |v| is_cacheable(v)) && rhs.as_ref().map_or(true, |v| is_cacheable(v)) } - Expr::Group(arg) => is_cacheable(arg), + Expr::Group(arg) => is_cacheable(&arg), Expr::Tuple(args) => args.iter().all(is_cacheable), - Expr::NamedArgument(_, expr) => is_cacheable(expr), + Expr::NamedArgument(_, expr) => is_cacheable(&expr), // We have too little information to tell if the expression is pure: Expr::Call(_, _) => false, Expr::RustMacro(_, _) => false, @@ -2108,12 +2113,17 @@ fn median(sizes: &mut [usize]) -> usize { /// So in here, we want to insert the variable containing the content of the filter block inside /// the call to `"a"`. To do so, we recursively go through all `Filter` and finally insert our /// variable as the first argument to the `"a"` call. -fn insert_first_filter_argument(args: &mut Vec>, var_name: String) { - if let Some(Expr::Filter(Filter { arguments, .. })) = args.first_mut() { - insert_first_filter_argument(arguments, var_name); - } else { - args.insert(0, Expr::Generated(var_name)); +fn insert_first_filter_argument(args: &mut Vec>>, var_name: String) { + if let Some(expr) = args.first_mut() { + if let Expr::Filter(Filter { + ref mut arguments, .. + }) = **expr + { + insert_first_filter_argument(arguments, var_name); + return; + } } + args.insert(0, WithSpan::new(Expr::Generated(var_name), "")); } #[derive(Clone, Copy, PartialEq)] @@ -2154,7 +2164,7 @@ impl<'a> Deref for WritableBuffer<'a> { #[derive(Debug)] enum Writable<'a> { Lit(&'a str), - Expr(&'a Expr<'a>), + Expr(&'a WithSpan<'a, Expr<'a>>), Generated(String, DisplayWrap), } diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index 76894cb6..fb3b0f98 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use crate::config::Config; use crate::CompileError; -use parser::node::{BlockDef, Macro, Match}; +use parser::node::{BlockDef, Macro}; use parser::Node; pub(crate) struct Heritage<'a> { @@ -68,7 +68,7 @@ impl Context<'_> { } }, Node::Macro(m) if top => { - macros.insert(m.name, m); + macros.insert(m.name, &**m); } Node::Import(import) if top => { let path = config.find_template(import.path, Some(path))?; @@ -80,7 +80,7 @@ impl Context<'_> { ); } Node::BlockDef(b) => { - blocks.insert(b.name, b); + blocks.insert(b.name, &**b); nested.push(&b.nodes); } Node::If(i) => { @@ -92,8 +92,8 @@ impl Context<'_> { nested.push(&l.body); nested.push(&l.else_nodes); } - Node::Match(Match { arms, .. }) => { - for arm in arms { + Node::Match(m) => { + for arm in &m.arms { nested.push(&arm.nodes); } } From a2881bb940ec209a65d842a6ec9322ff9b531162 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 May 2024 21:36:45 +0200 Subject: [PATCH 08/14] Add `askama_derive::FileInfo` type --- askama_derive/src/lib.rs | 51 ++++++++++++++++++++++++++++++++++++++-- askama_parser/src/lib.rs | 2 +- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index e3470019..7d43c6ef 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -1,13 +1,15 @@ #![deny(elided_lifetimes_in_paths)] #![deny(unreachable_pub)] +use std::borrow::Cow; +use std::collections::HashMap; use std::fmt; -use std::{borrow::Cow, collections::HashMap}; +use std::path::Path; use proc_macro::TokenStream; use proc_macro2::Span; -use parser::ParseError; +use parser::{generate_error_info, strip_common, ErrorInfo, ParseError}; mod config; use config::Config; @@ -144,6 +146,51 @@ impl From for CompileError { } } +struct FileInfo<'a, 'b, 'c> { + path: &'a Path, + source: Option<&'b str>, + node_source: Option<&'c str>, +} + +impl<'a, 'b, 'c> FileInfo<'a, 'b, 'c> { + fn new(path: &'a Path, source: Option<&'b str>, node_source: Option<&'c str>) -> Self { + Self { + path, + source, + node_source, + } + } +} + +impl<'a, 'b, 'c> fmt::Display for FileInfo<'a, 'b, 'c> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match (self.source, self.node_source) { + (Some(source), Some(node_source)) => { + let ( + ErrorInfo { + row, + column, + source_after, + }, + file_path, + ) = generate_error_info(source, node_source, self.path); + write!( + f, + "\n --> {file_path}:{row}:{column}\n{source_after}", + row = row + 1 + ) + } + _ => { + let file_path = match std::env::current_dir() { + Ok(cwd) => strip_common(&cwd, self.path), + Err(_) => self.path.display().to_string(), + }; + write!(f, "\n --> {file_path}") + } + } + } +} + // This is used by the code generator to decide whether a named filter is part of // Askama or should refer to a local `filters` module. It should contain all the // filters shipped with Askama, even the optional ones (since optional inclusion diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index fc33ba6e..53b34674 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -728,7 +728,7 @@ fn filter(i: &str, level: Level) -> ParseResult<'_, (&str, Option String { +pub fn strip_common(base: &Path, path: &Path) -> String { let path = match path.canonicalize() { Ok(path) => path, Err(_) => return path.display().to_string(), From debd9af534b55ab70936de1369251658a443ce71 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 May 2024 21:55:21 +0200 Subject: [PATCH 09/14] Migrate config errors to new more complete `CompileError` version --- askama_derive/src/config.rs | 97 ++++++++++++++++++++++++----------- askama_derive/src/heritage.rs | 28 +++++++--- askama_derive/src/input.rs | 21 ++++---- askama_derive/src/lib.rs | 42 ++++++++++----- askama_parser/src/lib.rs | 4 ++ 5 files changed, 133 insertions(+), 59 deletions(-) diff --git a/askama_derive/src/config.rs b/askama_derive/src/config.rs index 78ab5f5f..8ece2328 100644 --- a/askama_derive/src/config.rs +++ b/askama_derive/src/config.rs @@ -6,7 +6,7 @@ use std::{env, fs}; #[cfg(feature = "serde")] use serde::Deserialize; -use crate::{CompileError, CRATE}; +use crate::{CompileError, FileInfo, CRATE}; use parser::node::Whitespace; use parser::Syntax; @@ -22,6 +22,7 @@ pub(crate) struct Config<'a> { impl<'a> Config<'a> { pub(crate) fn new( s: &'a str, + config_path: Option<&str>, template_whitespace: Option<&str>, ) -> std::result::Result, CompileError> { let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); @@ -54,12 +55,18 @@ impl<'a> Config<'a> { WhitespaceHandling::default(), ), }; + let file_info = config_path.map(|path| FileInfo::new(Path::new(path), None, None)); if let Some(template_whitespace) = template_whitespace { whitespace = match template_whitespace { "suppress" => WhitespaceHandling::Suppress, "minimize" => WhitespaceHandling::Minimize, "preserve" => WhitespaceHandling::Preserve, - s => return Err(format!("invalid value for `whitespace`: \"{s}\"").into()), + s => { + return Err(CompileError::new( + format!("invalid value for `whitespace`: \"{s}\""), + file_info, + )) + } }; } @@ -71,13 +78,19 @@ impl<'a> Config<'a> { .insert(name.to_string(), raw_s.try_into()?) .is_some() { - return Err(format!("syntax \"{name}\" is already defined").into()); + return Err(CompileError::new( + format!("syntax \"{name}\" is already defined",), + file_info, + )); } } } if !syntaxes.contains_key(default_syntax) { - return Err(format!("default syntax \"{default_syntax}\" not found").into()); + return Err(CompileError::new( + format!("default syntax \"{default_syntax}\" not found"), + file_info, + )); } let mut escapers = Vec::new(); @@ -281,13 +294,32 @@ where } #[allow(clippy::match_wild_err_arm)] -pub(crate) fn get_template_source(tpl_path: &Path) -> std::result::Result { +pub(crate) fn get_template_source( + tpl_path: &Path, + import_from: Option<(&Rc, &str, &str)>, +) -> std::result::Result { match fs::read_to_string(tpl_path) { - Err(_) => Err(format!( - "unable to open template file '{}'", - tpl_path.to_str().unwrap() - ) - .into()), + Err(_) => { + if let Some((node_file, file_source, node_source)) = import_from { + Err(CompileError::new( + format!( + "unable to open template file '{}'", + tpl_path.to_str().unwrap(), + ), + Some(FileInfo::new( + node_file, + Some(file_source), + Some(node_source), + )), + )) + } else { + Err(format!( + "unable to open template file '{}'", + tpl_path.to_str().unwrap() + ) + .into()) + } + } Ok(mut source) => { if source.ends_with('\n') { let _ = source.pop(); @@ -314,17 +346,17 @@ mod tests { #[test] fn get_source() { - let path = Config::new("", None) + let path = Config::new("", None, None) .and_then(|config| config.find_template("b.html", None)) .unwrap(); - assert_eq!(get_template_source(&path).unwrap(), "bar"); + assert_eq!(get_template_source(&path, None).unwrap(), "bar"); } #[test] fn test_default_config() { let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); root.push("templates"); - let config = Config::new("", None).unwrap(); + let config = Config::new("", None, None).unwrap(); assert_eq!(config.dirs, vec![root]); } @@ -333,7 +365,7 @@ mod tests { fn test_config_dirs() { let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); root.push("tpl"); - let config = Config::new("[general]\ndirs = [\"tpl\"]", None).unwrap(); + let config = Config::new("[general]\ndirs = [\"tpl\"]", None, None).unwrap(); assert_eq!(config.dirs, vec![root]); } @@ -347,7 +379,7 @@ mod tests { #[test] fn find_absolute() { - let config = Config::new("", None).unwrap(); + let config = Config::new("", None, None).unwrap(); let root = config.find_template("a.html", None).unwrap(); let path = config.find_template("sub/b.html", Some(&root)).unwrap(); assert_eq_rooted(&path, "sub/b.html"); @@ -356,14 +388,14 @@ mod tests { #[test] #[should_panic] fn find_relative_nonexistent() { - let config = Config::new("", None).unwrap(); + let config = Config::new("", None, None).unwrap(); let root = config.find_template("a.html", None).unwrap(); config.find_template("c.html", Some(&root)).unwrap(); } #[test] fn find_relative() { - let config = Config::new("", None).unwrap(); + let config = Config::new("", None, None).unwrap(); let root = config.find_template("sub/b.html", None).unwrap(); let path = config.find_template("c.html", Some(&root)).unwrap(); assert_eq_rooted(&path, "sub/c.html"); @@ -371,7 +403,7 @@ mod tests { #[test] fn find_relative_sub() { - let config = Config::new("", None).unwrap(); + let config = Config::new("", None, None).unwrap(); let root = config.find_template("sub/b.html", None).unwrap(); let path = config.find_template("sub1/d.html", Some(&root)).unwrap(); assert_eq_rooted(&path, "sub/sub1/d.html"); @@ -394,7 +426,7 @@ mod tests { "#; let default_syntax = Syntax::default(); - let config = Config::new(raw_config, None).unwrap(); + let config = Config::new(raw_config, None, None).unwrap(); assert_eq!(config.default_syntax, "foo"); let foo = config.syntaxes.get("foo").unwrap(); @@ -426,7 +458,7 @@ mod tests { "#; let default_syntax = Syntax::default(); - let config = Config::new(raw_config, None).unwrap(); + let config = Config::new(raw_config, None, None).unwrap(); assert_eq!(config.default_syntax, "foo"); let foo = config.syntaxes.get("foo").unwrap(); @@ -463,7 +495,7 @@ mod tests { default_syntax = "emoji" "#; - let config = Config::new(raw_config, None).unwrap(); + let config = Config::new(raw_config, None, None).unwrap(); assert_eq!(config.default_syntax, "emoji"); let foo = config.syntaxes.get("emoji").unwrap(); @@ -483,7 +515,7 @@ mod tests { name = "too_short" block_start = "<" "#; - let config = Config::new(raw_config, None); + let config = Config::new(raw_config, None, None); assert_eq!( config.unwrap_err().msg, r#"delimiters must be at least two characters long: "<""#, @@ -494,7 +526,7 @@ mod tests { name = "contains_ws" block_start = " {{ " "#; - let config = Config::new(raw_config, None); + let config = Config::new(raw_config, None, None); assert_eq!( config.unwrap_err().msg, r#"delimiters may not contain white spaces: " {{ ""#, @@ -507,7 +539,7 @@ mod tests { expr_start = "{{$" comment_start = "{{#" "#; - let config = Config::new(raw_config, None); + let config = Config::new(raw_config, None, None); assert_eq!( config.unwrap_err().msg, r#"a delimiter may not be the prefix of another delimiter: "{{" vs "{{$""#, @@ -522,7 +554,7 @@ mod tests { syntax = [{ name = "default" }] "#; - let _config = Config::new(raw_config, None).unwrap(); + let _config = Config::new(raw_config, None, None).unwrap(); } #[cfg(feature = "toml")] @@ -534,7 +566,7 @@ mod tests { { name = "foo", block_start = "%%" } ] "#; - let _config = Config::new(raw_config, None).unwrap(); + let _config = Config::new(raw_config, None, None).unwrap(); } #[cfg(feature = "toml")] @@ -546,7 +578,7 @@ mod tests { default_syntax = "foo" "#; - let _config = Config::new(raw_config, None).unwrap(); + let _config = Config::new(raw_config, None, None).unwrap(); } #[cfg(feature = "config")] @@ -559,6 +591,7 @@ mod tests { extensions = ["js"] "#, None, + None, ) .unwrap(); assert_eq!( @@ -587,11 +620,12 @@ mod tests { whitespace = "suppress" "#, None, + None, ) .unwrap(); assert_eq!(config.whitespace, WhitespaceHandling::Suppress); - let config = Config::new(r#""#, None).unwrap(); + let config = Config::new(r#""#, None, None).unwrap(); assert_eq!(config.whitespace, WhitespaceHandling::Preserve); let config = Config::new( @@ -600,6 +634,7 @@ mod tests { whitespace = "preserve" "#, None, + None, ) .unwrap(); assert_eq!(config.whitespace, WhitespaceHandling::Preserve); @@ -610,6 +645,7 @@ mod tests { whitespace = "minimize" "#, None, + None, ) .unwrap(); assert_eq!(config.whitespace, WhitespaceHandling::Minimize); @@ -627,17 +663,18 @@ mod tests { whitespace = "suppress" "#, Some(&"minimize".to_owned()), + None, ) .unwrap(); assert_eq!(config.whitespace, WhitespaceHandling::Minimize); - let config = Config::new(r#""#, Some(&"minimize".to_owned())).unwrap(); + let config = Config::new(r#""#, Some(&"minimize".to_owned()), None).unwrap(); assert_eq!(config.whitespace, WhitespaceHandling::Minimize); } #[test] fn test_config_whitespace_error() { - let config = Config::new(r#""#, Some("trim")); + let config = Config::new(r#""#, None, Some("trim")); if let Err(err) = config { assert_eq!(err.msg, "invalid value for `whitespace`: \"trim\""); } else { diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index fb3b0f98..8b7c7da1 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use crate::config::Config; use crate::CompileError; use parser::node::{BlockDef, Macro}; -use parser::Node; +use parser::{Node, Parsed}; pub(crate) struct Heritage<'a> { pub(crate) root: &'a Context<'a>, @@ -36,26 +36,40 @@ impl Heritage<'_> { type BlockAncestry<'a> = HashMap<&'a str, Vec<(&'a Context<'a>, &'a BlockDef<'a>)>>; -#[derive(Default, Clone)] +#[derive(Clone)] pub(crate) struct Context<'a> { pub(crate) nodes: &'a [Node<'a>], pub(crate) extends: Option>, pub(crate) blocks: HashMap<&'a str, &'a BlockDef<'a>>, pub(crate) macros: HashMap<&'a str, &'a Macro<'a>>, pub(crate) imports: HashMap<&'a str, Rc>, + path: Option<&'a Path>, + parsed: &'a Parsed, } impl Context<'_> { + pub(crate) fn empty(parsed: &Parsed) -> Context<'_> { + Context { + nodes: &[], + extends: None, + blocks: HashMap::new(), + macros: HashMap::new(), + imports: HashMap::new(), + path: None, + parsed, + } + } + pub(crate) fn new<'n>( config: &Config<'_>, - path: &Path, - nodes: &'n [Node<'n>], + path: &'n Path, + parsed: &'n Parsed, ) -> Result, CompileError> { let mut extends = None; let mut blocks = HashMap::new(); let mut macros = HashMap::new(); let mut imports = HashMap::new(); - let mut nested = vec![nodes]; + let mut nested = vec![parsed.nodes()]; let mut top = true; while let Some(nodes) = nested.pop() { @@ -104,11 +118,13 @@ impl Context<'_> { } Ok(Context { - nodes, + nodes: parsed.nodes(), extends, blocks, macros, imports, + parsed, + path: Some(path), }) } } diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs index 10b99275..84764c51 100644 --- a/askama_derive/src/input.rs +++ b/askama_derive/src/input.rs @@ -7,7 +7,7 @@ use mime::Mime; use quote::ToTokens; use syn::punctuated::Punctuated; -use crate::config::{get_template_source, read_config_file, Config}; +use crate::config::{get_template_source, Config}; use crate::CompileError; use parser::{Node, Parsed, Syntax}; @@ -113,7 +113,7 @@ impl TemplateInput<'_> { let (source, source_path) = match &self.source { Source::Source(s) => (s.into(), None), Source::Path(_) => ( - get_template_source(&self.path)?, + get_template_source(&self.path, None)?, Some(Rc::clone(&self.path)), ), }; @@ -127,13 +127,16 @@ impl TemplateInput<'_> { let mut nested = vec![parsed.nodes()]; while let Some(nodes) = nested.pop() { for n in nodes { - let mut add_to_check = |path: Rc| -> Result<(), CompileError> { - if !map.contains_key(&path) { + let mut add_to_check = |new_path: Rc| -> Result<(), CompileError> { + if !map.contains_key(&new_path) { // Add a dummy entry to `map` in order to prevent adding `path` // multiple times to `check`. - map.insert(Rc::clone(&path), Parsed::default()); - let source = get_template_source(&path)?; - check.push((path.clone(), source, Some(path))); + map.insert(Rc::clone(&new_path), Parsed::default()); + let source = get_template_source( + &new_path, + Some((&path, parsed.source(), n.span())), + )?; + check.push((new_path.clone(), source, Some(new_path))); } Ok(()) }; @@ -349,8 +352,8 @@ impl TemplateArgs { } } - pub(crate) fn config(&self) -> Result { - read_config_file(self.config.as_deref()) + pub(crate) fn config_path(&self) -> Option<&str> { + self.config.as_deref() } } diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 7d43c6ef..90e68a53 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -1,7 +1,6 @@ #![deny(elided_lifetimes_in_paths)] #![deny(unreachable_pub)] -use std::borrow::Cow; use std::collections::HashMap; use std::fmt; use std::path::Path; @@ -12,7 +11,7 @@ use proc_macro2::Span; use parser::{generate_error_info, strip_common, ErrorInfo, ParseError}; mod config; -use config::Config; +use config::{read_config_file, Config}; mod generator; use generator::{Generator, MapChain}; mod heritage; @@ -40,10 +39,11 @@ pub fn derive_template(input: TokenStream) -> TokenStream { fn build_skeleton(ast: &syn::DeriveInput) -> Result { let template_args = TemplateArgs::fallback(); - let config = Config::new("", None)?; + let config = Config::new("", None, None)?; let input = TemplateInput::new(ast, &config, &template_args)?; let mut contexts = HashMap::new(); - contexts.insert(&input.path, Context::default()); + let parsed = parser::Parsed::default(); + contexts.insert(&input.path, Context::empty(&parsed)); Generator::new(&input, &contexts, None, MapChain::default()).build(&contexts[&input.path]) } @@ -56,8 +56,9 @@ fn build_skeleton(ast: &syn::DeriveInput) -> Result { /// value as passed to the `template()` attribute. pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result { let template_args = TemplateArgs::new(ast)?; - let toml = template_args.config()?; - let config = Config::new(&toml, template_args.whitespace.as_deref())?; + let config_path = template_args.config_path(); + let s = read_config_file(config_path)?; + let config = Config::new(&s, config_path, template_args.whitespace.as_deref())?; let input = TemplateInput::new(ast, &config, &template_args)?; let mut templates = HashMap::new(); @@ -65,7 +66,7 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result Result, + msg: String, span: Span, } impl CompileError { - fn new>>(s: S, span: Span) -> Self { + fn new(msg: S, file_info: Option>) -> Self { + let msg = match file_info { + Some(file_info) => format!("{msg}{file_info}"), + None => msg.to_string(), + }; Self { - msg: s.into(), - span, + msg, + span: Span::call_site(), } } @@ -128,21 +133,30 @@ impl fmt::Display for CompileError { impl From for CompileError { #[inline] fn from(e: ParseError) -> Self { - Self::new(e.to_string(), Span::call_site()) + Self { + msg: e.to_string(), + span: Span::call_site(), + } } } impl From<&'static str> for CompileError { #[inline] fn from(s: &'static str) -> Self { - Self::new(s, Span::call_site()) + Self { + msg: s.into(), + span: Span::call_site(), + } } } impl From for CompileError { #[inline] fn from(s: String) -> Self { - Self::new(s, Span::call_site()) + Self { + msg: s, + span: Span::call_site(), + } } } diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index 53b34674..b6ce3ca5 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -60,6 +60,10 @@ mod _parsed { pub fn nodes(&self) -> &[Node<'_>] { &self.ast.nodes } + + pub fn source(&self) -> &str { + &self.source + } } impl fmt::Debug for Parsed { From dbfa09e490477b312f9676aac1413b624cefda47 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 May 2024 21:59:22 +0200 Subject: [PATCH 10/14] Add `Context::generate_error` method --- askama_derive/src/heritage.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index 8b7c7da1..93fbbc37 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -3,9 +3,9 @@ use std::path::Path; use std::rc::Rc; use crate::config::Config; -use crate::CompileError; +use crate::{CompileError, FileInfo}; use parser::node::{BlockDef, Macro}; -use parser::{Node, Parsed}; +use parser::{Node, Parsed, WithSpan}; pub(crate) struct Heritage<'a> { pub(crate) root: &'a Context<'a>, @@ -127,4 +127,18 @@ impl Context<'_> { path: Some(path), }) } + + pub(crate) fn generate_error(&self, msg: &str, node: &WithSpan<'_, T>) -> CompileError { + match self.path { + Some(path) => CompileError::new( + msg, + Some(FileInfo::new( + path, + Some(self.parsed.source()), + Some(node.span()), + )), + ), + None => CompileError::new(msg, None), + } + } } From 344b0efbf082dbe49568b871586a7244eeea487a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 May 2024 22:40:30 +0200 Subject: [PATCH 11/14] Migrate errors with file information --- askama_derive/src/generator.rs | 395 ++++++++++++++++++++------------- 1 file changed, 241 insertions(+), 154 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 4e0fa36c..080ef3cc 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -264,7 +264,7 @@ impl<'a> Generator<'a> { self.write_expr(ws, val); } Node::Let(ref l) => { - self.write_let(buf, l)?; + self.write_let(ctx, buf, l)?; } Node::If(ref i) => { size_hint += self.write_if(ctx, buf, i)?; @@ -276,7 +276,8 @@ impl<'a> Generator<'a> { size_hint += self.write_loop(ctx, buf, loop_block)?; } Node::BlockDef(ref b) => { - size_hint += self.write_block(ctx, buf, Some(b.name), Ws(b.ws1.0, b.ws2.1))?; + size_hint += + self.write_block(ctx, buf, Some(b.name), Ws(b.ws1.0, b.ws2.1), b)?; } Node::Include(ref i) => { size_hint += self.handle_include(ctx, buf, i)?; @@ -289,7 +290,9 @@ impl<'a> Generator<'a> { } Node::Macro(ref m) => { if level != AstLevel::Top { - return Err("macro blocks only allowed at the top level".into()); + return Err( + ctx.generate_error("macro blocks only allowed at the top level", m) + ); } self.flush_ws(m.ws1); self.prepare_ws(m.ws2); @@ -301,25 +304,29 @@ impl<'a> Generator<'a> { } Node::Import(ref i) => { if level != AstLevel::Top { - return Err("import blocks only allowed at the top level".into()); + return Err( + ctx.generate_error("import blocks only allowed at the top level", i) + ); } self.handle_ws(i.ws); } - Node::Extends(_) => { + Node::Extends(ref e) => { if level != AstLevel::Top { - return Err("extend blocks only allowed at the top level".into()); + return Err( + ctx.generate_error("extend blocks only allowed at the top level", e) + ); } // No whitespace handling: child template top-level is not used, // except for the blocks defined in it. } Node::Break(ref ws) => { self.handle_ws(**ws); - self.write_buf_writable(buf)?; + self.write_buf_writable(ctx, buf)?; buf.writeln("break;")?; } Node::Continue(ref ws) => { self.handle_ws(**ws); - self.write_buf_writable(buf)?; + self.write_buf_writable(ctx, buf)?; buf.writeln("continue;")?; } } @@ -331,7 +338,7 @@ impl<'a> Generator<'a> { self.flush_ws(Ws(Some(self.skip_ws.into()), None)); } - size_hint += self.write_buf_writable(buf)?; + size_hint += self.write_buf_writable(ctx, buf)?; } Ok(size_hint) } @@ -347,7 +354,7 @@ impl<'a> Generator<'a> { let mut has_else = false; for (i, cond) in i.branches.iter().enumerate() { self.handle_ws(cond.ws); - flushed += self.write_buf_writable(buf)?; + flushed += self.write_buf_writable(ctx, buf)?; if i > 0 { self.locals.pop(); } @@ -370,13 +377,13 @@ impl<'a> Generator<'a> { // but this one should have access to the let-bound variable. match &**expr { Expr::BinOp(op, ref left, ref right) if *op == "||" || *op == "&&" => { - self.visit_expr(&mut expr_buf, left)?; + self.visit_expr(ctx, &mut expr_buf, left)?; self.visit_target(buf, true, true, target); expr_buf.write(&format!(" {op} ")); - self.visit_expr(&mut expr_buf, right)?; + self.visit_expr(ctx, &mut expr_buf, right)?; } _ => { - self.visit_expr(&mut expr_buf, expr)?; + self.visit_expr(ctx, &mut expr_buf, expr)?; self.visit_target(buf, true, true, target); } } @@ -389,7 +396,7 @@ impl<'a> Generator<'a> { // coerces e.g. `&&&bool` to `&bool`. Then `*(&bool)` // finally dereferences it to `bool`. buf.write("*(&("); - let expr_code = self.visit_expr_root(expr)?; + let expr_code = self.visit_expr_root(ctx, expr)?; buf.write(&expr_code); buf.write(") as &bool)"); } @@ -405,7 +412,7 @@ impl<'a> Generator<'a> { arm_sizes.push(arm_size); } self.handle_ws(i.ws); - flushed += self.write_buf_writable(buf)?; + flushed += self.write_buf_writable(ctx, buf)?; buf.writeln("}")?; self.locals.pop(); @@ -431,10 +438,10 @@ impl<'a> Generator<'a> { } = *m; self.flush_ws(ws1); - let flushed = self.write_buf_writable(buf)?; + let flushed = self.write_buf_writable(ctx, buf)?; let mut arm_sizes = Vec::new(); - let expr_code = self.visit_expr_root(expr)?; + let expr_code = self.visit_expr_root(ctx, expr)?; buf.writeln(&format!("match &{expr_code} {{"))?; let mut arm_size = 0; @@ -442,7 +449,7 @@ impl<'a> Generator<'a> { self.handle_ws(arm.ws); if i > 0 { - arm_sizes.push(arm_size + self.write_buf_writable(buf)?); + arm_sizes.push(arm_size + self.write_buf_writable(ctx, buf)?); buf.writeln("}")?; self.locals.pop(); @@ -456,7 +463,7 @@ impl<'a> Generator<'a> { } self.handle_ws(ws2); - arm_sizes.push(arm_size + self.write_buf_writable(buf)?); + arm_sizes.push(arm_size + self.write_buf_writable(ctx, buf)?); buf.writeln("}")?; self.locals.pop(); @@ -475,11 +482,11 @@ impl<'a> Generator<'a> { self.handle_ws(loop_block.ws1); self.locals.push(); - let expr_code = self.visit_expr_root(&loop_block.iter)?; + let expr_code = self.visit_expr_root(ctx, &loop_block.iter)?; let has_else_nodes = !loop_block.else_nodes.is_empty(); - let flushed = self.write_buf_writable(buf)?; + let flushed = self.write_buf_writable(ctx, buf)?; buf.writeln("{")?; if has_else_nodes { buf.writeln("let mut _did_loop = false;")?; @@ -509,7 +516,7 @@ impl<'a> Generator<'a> { buf.write("let _iter = _iter.filter(|"); self.visit_target(buf, true, true, &loop_block.var); buf.write("| -> bool {"); - self.visit_expr(buf, cond)?; + self.visit_expr(ctx, buf, cond)?; buf.writeln("});")?; self.locals.pop(); } @@ -526,7 +533,7 @@ impl<'a> Generator<'a> { } let mut size_hint1 = self.handle(ctx, &loop_block.body, buf, AstLevel::Nested)?; self.handle_ws(loop_block.ws2); - size_hint1 += self.write_buf_writable(buf)?; + size_hint1 += self.write_buf_writable(ctx, buf)?; self.locals.pop(); buf.writeln("}")?; @@ -536,12 +543,12 @@ impl<'a> Generator<'a> { self.locals.push(); size_hint2 = self.handle(ctx, &loop_block.else_nodes, buf, AstLevel::Nested)?; self.handle_ws(loop_block.ws3); - size_hint2 += self.write_buf_writable(buf)?; + size_hint2 += self.write_buf_writable(ctx, buf)?; self.locals.pop(); buf.writeln("}")?; } else { self.handle_ws(loop_block.ws3); - size_hint2 = self.write_buf_writable(buf)?; + size_hint2 = self.write_buf_writable(ctx, buf)?; } buf.writeln("}")?; @@ -553,44 +560,42 @@ impl<'a> Generator<'a> { &mut self, ctx: &Context<'a>, buf: &mut Buffer, - call: &'a Call<'_>, + call: &'a WithSpan<'_, Call<'_>>, ) -> Result { let Call { ws, scope, name, ref args, - } = *call; + } = **call; if name == "super" { - return self.write_block(ctx, buf, None, ws); + return self.write_block(ctx, buf, None, ws, call); } let (def, own_ctx) = match scope { Some(s) => { let path = ctx.imports.get(s).ok_or_else(|| { - CompileError::from(format!("no import found for scope {s:?}")) + ctx.generate_error(&format!("no import found for scope {s:?}"), call) + })?; + let mctx = self.contexts.get(path).ok_or_else(|| { + ctx.generate_error(&format!("context for {path:?} not found"), call) })?; - let mctx = self - .contexts - .get(path) - .ok_or_else(|| CompileError::from(format!("context for {path:?} not found")))?; let def = mctx.macros.get(name).ok_or_else(|| { - CompileError::from(format!("macro {name:?} not found in scope {s:?}")) + ctx.generate_error(&format!("macro {name:?} not found in scope {s:?}"), call) })?; (def, mctx) } None => { - let def = ctx - .macros - .get(name) - .ok_or_else(|| CompileError::from(format!("macro {name:?} not found")))?; + let def = ctx.macros.get(name).ok_or_else(|| { + ctx.generate_error(&format!("macro {name:?} not found"), call) + })?; (def, ctx) } }; self.flush_ws(ws); // Cannot handle_ws() here: whitespace from macro definition comes first self.locals.push(); - self.write_buf_writable(buf)?; + self.write_buf_writable(ctx, buf)?; buf.writeln("{")?; self.prepare_ws(def.ws1); @@ -598,12 +603,15 @@ impl<'a> Generator<'a> { let mut values = Buffer::new(0); let mut is_first_variable = true; if args.len() != def.args.len() { - return Err(CompileError::from(format!( - "macro {name:?} expected {} argument{}, found {}", - def.args.len(), - if def.args.len() != 1 { "s" } else { "" }, - args.len() - ))); + return Err(ctx.generate_error( + &format!( + "macro {name:?} expected {} argument{}, found {}", + def.args.len(), + if def.args.len() != 1 { "s" } else { "" }, + args.len() + ), + call, + )); } let mut named_arguments = HashMap::new(); // Since named arguments can only be passed last, we only need to check if the last argument @@ -615,9 +623,10 @@ impl<'a> Generator<'a> { break; }; if !def.args.iter().any(|arg| arg == arg_name) { - return Err(CompileError::from(format!( - "no argument named `{arg_name}` in macro {name:?}" - ))); + return Err(ctx.generate_error( + &format!("no argument named `{arg_name}` in macro {name:?}"), + call, + )); } named_arguments.insert(Cow::Borrowed(arg_name), arg); } @@ -641,10 +650,13 @@ impl<'a> Generator<'a> { if !allow_positional { // If there is already at least one named argument, then it's not allowed // to use unnamed ones at this point anymore. - return Err(CompileError::from(format!( + return Err(ctx.generate_error( + &format!( "cannot have unnamed argument (`{arg}`) after named argument in macro \ {name:?}" - ))); + ), + call, + )); } &args[index] } @@ -653,14 +665,14 @@ impl<'a> Generator<'a> { // If `expr` is already a form of variable then // don't reintroduce a new variable. This is // to avoid moving non-copyable values. - &Expr::Var(name) if name != "self" => { + Expr::Var(name) if *name != "self" => { let var = self.locals.resolve_or_self(name); self.locals .insert(Cow::Borrowed(arg), LocalMeta::with_ref(var)); } Expr::Attr(obj, attr) => { let mut attr_buf = Buffer::new(0); - self.visit_attr(&mut attr_buf, obj, attr)?; + self.visit_attr(ctx, &mut attr_buf, &obj, attr)?; let var = self.locals.resolve(&attr_buf.buf).unwrap_or(attr_buf.buf); self.locals @@ -680,7 +692,7 @@ impl<'a> Generator<'a> { names.write(arg); values.write("("); - values.write(&self.visit_expr_root(expr)?); + values.write(&self.visit_expr_root(ctx, expr)?); values.write(")"); self.locals.insert_with_default(Cow::Borrowed(arg)); } @@ -695,7 +707,7 @@ impl<'a> Generator<'a> { let mut size_hint = self.handle(own_ctx, &def.nodes, buf, AstLevel::Nested)?; self.flush_ws(def.ws2); - size_hint += self.write_buf_writable(buf)?; + size_hint += self.write_buf_writable(ctx, buf)?; buf.writeln("}")?; self.locals.pop(); self.prepare_ws(ws); @@ -706,7 +718,7 @@ impl<'a> Generator<'a> { &mut self, ctx: &Context<'a>, buf: &mut Buffer, - filter: &'a FilterBlock<'_>, + filter: &'a WithSpan<'_, FilterBlock<'_>>, ) -> Result { self.flush_ws(filter.ws1); let mut var_name = String::new(); @@ -726,7 +738,7 @@ impl<'a> Generator<'a> { let WriteParts { size_hint: write_size_hint, buffers, - } = self.prepare_format(buf.indent + 1)?; + } = self.prepare_format(ctx, buf.indent + 1)?; size_hint += match buffers { None => return Ok(0), Some(WritePartsBuffers { format, expr: None }) => { @@ -757,7 +769,7 @@ impl<'a> Generator<'a> { insert_first_filter_argument(&mut arguments, var_name.clone()); - let wrap = self.visit_filter(&mut filter_buf, filter_name, &arguments)?; + let wrap = self.visit_filter(ctx, &mut filter_buf, filter_name, &arguments, filter)?; self.buf_writable .push(Writable::Generated(filter_buf.buf, wrap)); @@ -777,7 +789,7 @@ impl<'a> Generator<'a> { i: &'a Include<'_>, ) -> Result { self.flush_ws(i.ws); - self.write_buf_writable(buf)?; + self.write_buf_writable(ctx, buf)?; let path = self .input .config @@ -823,13 +835,18 @@ impl<'a> Generator<'a> { let locals = MapChain::with_parent(&self.locals); let mut child = Self::new(self.input, self.contexts, heritage.as_ref(), locals); let mut size_hint = child.handle(handle_ctx, handle_ctx.nodes, buf, AstLevel::Top)?; - size_hint += child.write_buf_writable(buf)?; + size_hint += child.write_buf_writable(handle_ctx, buf)?; self.prepare_ws(i.ws); Ok(size_hint) } - fn is_shadowing_variable(&self, var: &Target<'a>) -> Result { + fn is_shadowing_variable( + &self, + ctx: &Context<'_>, + var: &Target<'a>, + l: &WithSpan<'_, T>, + ) -> Result { match var { Target::Name(name) => { let name = normalize_identifier(name); @@ -844,7 +861,7 @@ impl<'a> Generator<'a> { } Target::Tuple(_, targets) => { for target in targets { - match self.is_shadowing_variable(target) { + match self.is_shadowing_variable(ctx, target, l) { Ok(false) => continue, outcome => return outcome, } @@ -853,35 +870,43 @@ impl<'a> Generator<'a> { } Target::Struct(_, named_targets) => { for (_, target) in named_targets { - match self.is_shadowing_variable(target) { + match self.is_shadowing_variable(ctx, target, l) { Ok(false) => continue, outcome => return outcome, } } Ok(false) } - _ => Err("literals are not allowed on the left-hand side of an assignment".into()), + _ => Err(ctx.generate_error( + "literals are not allowed on the left-hand side of an assignment", + l, + )), } } - fn write_let(&mut self, buf: &mut Buffer, l: &'a Let<'_>) -> Result<(), CompileError> { + fn write_let( + &mut self, + ctx: &Context<'_>, + buf: &mut Buffer, + l: &'a WithSpan<'_, Let<'_>>, + ) -> Result<(), CompileError> { self.handle_ws(l.ws); let Some(val) = &l.val else { - self.write_buf_writable(buf)?; + self.write_buf_writable(ctx, buf)?; buf.write("let "); self.visit_target(buf, false, true, &l.var); return buf.writeln(";"); }; let mut expr_buf = Buffer::new(0); - self.visit_expr(&mut expr_buf, val)?; + self.visit_expr(ctx, &mut expr_buf, val)?; - let shadowed = self.is_shadowing_variable(&l.var)?; + let shadowed = self.is_shadowing_variable(ctx, &l.var, l)?; if shadowed { // Need to flush the buffer if the variable is being shadowed, // to ensure the old variable is used. - self.write_buf_writable(buf)?; + self.write_buf_writable(ctx, buf)?; } if shadowed || !matches!(l.var, Target::Name(_)) @@ -897,12 +922,13 @@ impl<'a> Generator<'a> { // If `name` is `Some`, this is a call to a block definition, and we have to find // the first block for that name from the ancestry chain. If name is `None`, this // is from a `super()` call, and we can get the name from `self.super_block`. - fn write_block( + fn write_block( &mut self, ctx: &Context<'a>, buf: &mut Buffer, name: Option<&'a str>, outer: Ws, + node: &WithSpan<'_, T>, ) -> Result { // Flush preceding whitespace according to the outer WS spec self.flush_ws(outer); @@ -912,17 +938,22 @@ impl<'a> Generator<'a> { (Some(cur_name), None) => (cur_name, 0), // A block definition contains a block definition of the same name (Some(cur_name), Some((prev_name, _))) if cur_name == prev_name => { - return Err(format!("cannot define recursive blocks ({cur_name})").into()); + return Err(ctx.generate_error( + &format!("cannot define recursive blocks ({cur_name})"), + node, + )); } // A block definition contains a definition of another block (Some(cur_name), Some((_, _))) => (cur_name, 0), // `super()` was called inside a block (None, Some((prev_name, gen))) => (prev_name, gen + 1), // `super()` is called from outside a block - (None, None) => return Err("cannot call 'super()' outside block".into()), + (None, None) => { + return Err(ctx.generate_error("cannot call 'super()' outside block", node)) + } }; - self.write_buf_writable(buf)?; + self.write_buf_writable(ctx, buf)?; let block_fragment_write = self.input.block == name && self.buf_writable.discard; // Allow writing to the buffer if we're in the block fragment @@ -934,12 +965,15 @@ impl<'a> Generator<'a> { // Get the block definition from the heritage chain let heritage = self .heritage - .ok_or_else(|| CompileError::from("no block ancestors available"))?; + .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(|| { - CompileError::from(match name { - None => format!("no super() block found for block '{}'", cur.0), - Some(name) => format!("no block found for name '{name}'"), - }) + ctx.generate_error( + &match name { + None => format!("no super() block found for block '{}'", cur.0), + Some(name) => format!("no block found for name '{name}'"), + }, + node, + ) })?; // We clone the context of the child in order to preserve their macros and imports. @@ -973,7 +1007,7 @@ impl<'a> Generator<'a> { if !child.locals.is_current_empty() { // Need to flush the buffer before popping the variable stack - child.write_buf_writable(buf)?; + child.write_buf_writable(ctx, buf)?; } child.flush_ws(def.ws2); @@ -998,8 +1032,12 @@ impl<'a> Generator<'a> { } // Write expression buffer and empty - fn write_buf_writable(&mut self, buf: &mut Buffer) -> Result { - let WriteParts { size_hint, buffers } = self.prepare_format(buf.indent)?; + fn write_buf_writable( + &mut self, + ctx: &Context<'_>, + buf: &mut Buffer, + ) -> Result { + let WriteParts { size_hint, buffers } = self.prepare_format(ctx, buf.indent)?; match buffers { None => Ok(size_hint), Some(WritePartsBuffers { format, expr: None }) => { @@ -1025,7 +1063,11 @@ impl<'a> Generator<'a> { /// This is the common code to generate an expression. It is used for filter blocks and for /// expressions more generally. It stores the size it represents and the buffers. Take a look /// at `WriteParts` for more details. - fn prepare_format(&mut self, indent: u8) -> Result { + fn prepare_format( + &mut self, + ctx: &Context<'_>, + indent: u8, + ) -> Result { if self.buf_writable.is_empty() { return Ok(WriteParts { size_hint: 0, @@ -1067,7 +1109,7 @@ impl<'a> Generator<'a> { } Writable::Expr(s) => { let mut expr_buf = Buffer::new(0); - let wrapped = self.visit_expr(&mut expr_buf, s)?; + let wrapped = self.visit_expr(ctx, &mut expr_buf, s)?; let cacheable = is_cacheable(s); size_hint += self.named_expression( &mut buf_expr, @@ -1175,14 +1217,19 @@ impl<'a> Generator<'a> { /* Visitor methods for expression types */ - fn visit_expr_root(&mut self, expr: &WithSpan<'_, Expr<'_>>) -> Result { + fn visit_expr_root( + &mut self, + ctx: &Context<'_>, + expr: &WithSpan<'_, Expr<'_>>, + ) -> Result { let mut buf = Buffer::new(0); - self.visit_expr(&mut buf, expr)?; + self.visit_expr(ctx, &mut buf, expr)?; Ok(buf.buf) } fn visit_expr( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, expr: &WithSpan<'_, Expr<'_>>, ) -> Result { @@ -1193,35 +1240,36 @@ impl<'a> Generator<'a> { Expr::CharLit(s) => self.visit_char_lit(buf, s), Expr::Var(s) => self.visit_var(buf, s), Expr::Path(ref path) => self.visit_path(buf, path), - Expr::Array(ref elements) => self.visit_array(buf, elements)?, - Expr::Attr(ref obj, name) => self.visit_attr(buf, obj, name)?, - Expr::Index(ref obj, ref key) => self.visit_index(buf, obj, key)?, + Expr::Array(ref elements) => self.visit_array(ctx, buf, elements)?, + Expr::Attr(ref obj, name) => self.visit_attr(ctx, buf, obj, name)?, + Expr::Index(ref obj, ref key) => self.visit_index(ctx, buf, obj, key)?, Expr::Filter(Filter { name, ref arguments, - }) => self.visit_filter(buf, name, arguments)?, - Expr::Unary(op, ref inner) => self.visit_unary(buf, op, inner)?, - Expr::BinOp(op, ref left, ref right) => self.visit_binop(buf, op, left, right)?, + }) => self.visit_filter(ctx, buf, name, arguments, expr)?, + Expr::Unary(op, ref inner) => self.visit_unary(ctx, buf, op, inner)?, + Expr::BinOp(op, ref left, ref right) => self.visit_binop(ctx, buf, op, left, right)?, Expr::Range(op, ref left, ref right) => { - self.visit_range(buf, op, left.as_deref(), right.as_deref())? + self.visit_range(ctx, buf, op, left.as_deref(), right.as_deref())? } - Expr::Group(ref inner) => self.visit_group(buf, inner)?, - Expr::Call(ref obj, ref args) => self.visit_call(buf, obj, args)?, + Expr::Group(ref inner) => self.visit_group(ctx, buf, inner)?, + Expr::Call(ref obj, ref args) => self.visit_call(ctx, buf, obj, args)?, Expr::RustMacro(ref path, args) => self.visit_rust_macro(buf, path, args), - Expr::Try(ref expr) => self.visit_try(buf, expr)?, - Expr::Tuple(ref exprs) => self.visit_tuple(buf, exprs)?, - Expr::NamedArgument(_, ref expr) => self.visit_named_argument(buf, expr)?, + Expr::Try(ref expr) => self.visit_try(ctx, buf, expr)?, + Expr::Tuple(ref exprs) => self.visit_tuple(ctx, buf, exprs)?, + Expr::NamedArgument(_, ref expr) => self.visit_named_argument(ctx, buf, expr)?, Expr::Generated(ref s) => self.visit_generated(buf, s), }) } fn visit_try( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, expr: &WithSpan<'_, Expr<'_>>, ) -> Result { buf.write("::core::result::Result::map_err("); - self.visit_expr(buf, expr)?; + self.visit_expr(ctx, buf, expr)?; buf.write(", |err| "); buf.write(CRATE); buf.write("::shared::Error::Custom(::core::convert::Into::into(err)))?"); @@ -1237,21 +1285,23 @@ impl<'a> Generator<'a> { DisplayWrap::Unwrapped } - fn visit_filter( + fn visit_filter( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, name: &str, args: &[WithSpan<'_, Expr<'_>>], + filter: &WithSpan<'_, T>, ) -> Result { match name { - "as_ref" => return self._visit_as_ref_filter(buf, args), - "deref" => return self._visit_deref_filter(buf, args), - "escape" | "e" => return self._visit_escape_filter(buf, args), - "fmt" => return self._visit_fmt_filter(buf, args), - "format" => return self._visit_format_filter(buf, args), - "join" => return self._visit_join_filter(buf, args), - "json" | "tojson" => return self._visit_json_filter(buf, args), - "safe" => return self._visit_safe_filter(buf, args), + "as_ref" => return self._visit_as_ref_filter(ctx, buf, args, filter), + "deref" => return self._visit_deref_filter(ctx, buf, args, filter), + "escape" | "e" => return self._visit_escape_filter(ctx, buf, args, filter), + "fmt" => return self._visit_fmt_filter(ctx, buf, args, filter), + "format" => return self._visit_format_filter(ctx, buf, args, filter), + "join" => return self._visit_join_filter(ctx, buf, args), + "json" | "tojson" => return self._visit_json_filter(ctx, buf, args, filter), + "safe" => return self._visit_safe_filter(ctx, buf, args, filter), _ => {} } @@ -1260,86 +1310,101 @@ impl<'a> Generator<'a> { } else { buf.write(&format!("filters::{name}(")); } - self._visit_args(buf, args)?; + self._visit_args(ctx, buf, args)?; buf.write(")?"); Ok(DisplayWrap::Unwrapped) } - fn _visit_as_ref_filter( + fn _visit_as_ref_filter( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], + node: &WithSpan<'_, T>, ) -> Result { let arg = match args { [arg] => arg, - _ => return Err("unexpected argument(s) in `as_ref` filter".into()), + _ => return Err(ctx.generate_error("unexpected argument(s) in `as_ref` filter", node)), }; buf.write("&"); - self.visit_expr(buf, arg)?; + self.visit_expr(ctx, buf, arg)?; Ok(DisplayWrap::Unwrapped) } - fn _visit_deref_filter( + fn _visit_deref_filter( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], + node: &WithSpan<'_, T>, ) -> Result { let arg = match args { [arg] => arg, - _ => return Err("unexpected argument(s) in `deref` filter".into()), + _ => return Err(ctx.generate_error("unexpected argument(s) in `deref` filter", node)), }; buf.write("*"); - self.visit_expr(buf, arg)?; + self.visit_expr(ctx, buf, arg)?; Ok(DisplayWrap::Unwrapped) } - fn _visit_json_filter( + fn _visit_json_filter( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], + node: &WithSpan<'_, T>, ) -> Result { if cfg!(not(feature = "serde-json")) { - return Err("the `json` filter requires the `serde-json` feature to be enabled".into()); + return Err(ctx.generate_error( + "the `json` filter requires the `serde-json` feature to be enabled", + node, + )); } if args.len() != 1 { - return Err("unexpected argument(s) in `json` filter".into()); + return Err(ctx.generate_error("unexpected argument(s) in `json` filter", node)); } buf.write(CRATE); buf.write("::filters::json("); - self._visit_args(buf, args)?; + self._visit_args(ctx, buf, args)?; buf.write(")?"); Ok(DisplayWrap::Unwrapped) } - fn _visit_safe_filter( + fn _visit_safe_filter( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], + node: &WithSpan<'_, T>, ) -> Result { if args.len() != 1 { - return Err("unexpected argument(s) in `safe` filter".into()); + return Err(ctx.generate_error("unexpected argument(s) in `safe` filter", node)); } buf.write(CRATE); buf.write("::filters::safe("); buf.write(self.input.escaper); buf.write(", "); - self._visit_args(buf, args)?; + self._visit_args(ctx, buf, args)?; buf.write(")?"); Ok(DisplayWrap::Wrapped) } - fn _visit_escape_filter( + fn _visit_escape_filter( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], + node: &WithSpan<'_, T>, ) -> Result { if args.len() > 2 { - return Err("only two arguments allowed to escape filter".into()); + return Err(ctx.generate_error("only two arguments allowed to escape filter", node)); } let opt_escaper = match args.get(1).map(|expr| &**expr) { Some(Expr::StrLit(name)) => Some(*name), - Some(_) => return Err("invalid escaper type for escape filter".into()), + Some(_) => { + return Err(ctx.generate_error("invalid escaper type for escape filter", node)) + } None => None, }; let escaper = match opt_escaper { @@ -1349,22 +1414,24 @@ impl<'a> Generator<'a> { .escapers .iter() .find_map(|(escapers, escaper)| escapers.contains(name).then_some(escaper)) - .ok_or_else(|| CompileError::from("invalid escaper for escape filter"))?, + .ok_or_else(|| ctx.generate_error("invalid escaper for escape filter", node))?, None => self.input.escaper, }; buf.write(CRATE); buf.write("::filters::escape("); buf.write(escaper); buf.write(", "); - self._visit_args(buf, &args[..1])?; + self._visit_args(ctx, buf, &args[..1])?; buf.write(")?"); Ok(DisplayWrap::Wrapped) } - fn _visit_format_filter( + fn _visit_format_filter( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], + node: &WithSpan<'_, T>, ) -> Result { if !args.is_empty() { if let Expr::StrLit(fmt) = *args[0] { @@ -1372,36 +1439,39 @@ impl<'a> Generator<'a> { self.visit_str_lit(buf, fmt); if args.len() > 1 { buf.write(", "); - self._visit_args(buf, &args[1..])?; + self._visit_args(ctx, buf, &args[1..])?; } buf.write(")"); return Ok(DisplayWrap::Unwrapped); } } - Err(r#"use filter format like `"a={} b={}"|format(a, b)`"#.into()) + Err(ctx.generate_error(r#"use filter format like `"a={} b={}"|format(a, b)`"#, node)) } - fn _visit_fmt_filter( + fn _visit_fmt_filter( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], + node: &WithSpan<'_, T>, ) -> Result { if let [_, arg2] = args { if let Expr::StrLit(fmt) = **arg2 { buf.write("::std::format!("); self.visit_str_lit(buf, fmt); buf.write(", "); - self._visit_args(buf, &args[..1])?; + self._visit_args(ctx, buf, &args[..1])?; buf.write(")"); return Ok(DisplayWrap::Unwrapped); } } - return Err(r#"use filter fmt like `value|fmt("{:?}")`"#.into()); + Err(ctx.generate_error(r#"use filter fmt like `value|fmt("{:?}")`"#, node)) } // Force type coercion on first argument to `join` filter (see #39). fn _visit_join_filter( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], ) -> Result { @@ -1411,7 +1481,7 @@ impl<'a> Generator<'a> { if i > 0 { buf.write(", &"); } - self.visit_expr(buf, arg)?; + self.visit_expr(ctx, buf, arg)?; if i == 0 { buf.write(").into_iter()"); } @@ -1422,6 +1492,7 @@ impl<'a> Generator<'a> { fn _visit_args( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, args: &[WithSpan<'_, Expr<'_>>], ) -> Result<(), CompileError> { @@ -1442,11 +1513,11 @@ impl<'a> Generator<'a> { match **arg { Expr::Call(ref left, _) if !matches!(***left, Expr::Path(_)) => { buf.writeln("{")?; - self.visit_expr(buf, arg)?; + self.visit_expr(ctx, buf, arg)?; buf.writeln("}")?; } _ => { - self.visit_expr(buf, arg)?; + self.visit_expr(ctx, buf, arg)?; } } @@ -1459,6 +1530,7 @@ impl<'a> Generator<'a> { fn visit_attr( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, obj: &WithSpan<'_, Expr<'_>>, attr: &str, @@ -1478,45 +1550,49 @@ impl<'a> Generator<'a> { buf.write("_loop_item.last"); return Ok(DisplayWrap::Unwrapped); } else { - return Err("unknown loop variable".into()); + return Err(ctx.generate_error("unknown loop variable", obj)); } } } - self.visit_expr(buf, obj)?; + self.visit_expr(ctx, buf, obj)?; buf.write(&format!(".{}", normalize_identifier(attr))); Ok(DisplayWrap::Unwrapped) } fn visit_index( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, obj: &WithSpan<'_, Expr<'_>>, key: &WithSpan<'_, Expr<'_>>, ) -> Result { buf.write("&"); - self.visit_expr(buf, obj)?; + self.visit_expr(ctx, buf, obj)?; buf.write("["); - self.visit_expr(buf, key)?; + self.visit_expr(ctx, buf, key)?; buf.write("]"); Ok(DisplayWrap::Unwrapped) } fn visit_call( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, left: &WithSpan<'_, Expr<'_>>, args: &[WithSpan<'_, Expr<'_>>], ) -> Result { match &**left { - Expr::Attr(left, method) if ***left == Expr::Var("loop") => match *method { + Expr::Attr(sub_left, method) if ***sub_left == Expr::Var("loop") => match *method { "cycle" => match args { [arg] => { if matches!(**arg, Expr::Array(ref arr) if arr.is_empty()) { - return Err("loop.cycle(…) cannot use an empty array".into()); + return Err( + ctx.generate_error("loop.cycle(…) cannot use an empty array", arg) + ); } buf.write("({"); buf.write("let _cycle = &("); - self.visit_expr(buf, arg)?; + self.visit_expr(ctx, buf, arg)?; buf.writeln(");")?; buf.writeln("let _len = _cycle.len();")?; buf.writeln("if _len == 0 {")?; @@ -1527,9 +1603,13 @@ impl<'a> Generator<'a> { buf.writeln("_cycle[_loop_item.index % _len]")?; buf.writeln("})")?; } - _ => return Err("loop.cycle(…) expects exactly one argument".into()), + _ => { + return Err( + ctx.generate_error("loop.cycle(…) cannot use an empty array", left) + ) + } }, - s => return Err(format!("unknown loop method: {s:?}").into()), + s => return Err(ctx.generate_error(&format!("unknown loop method: {s:?}"), left)), }, sub_left => { match sub_left { @@ -1538,11 +1618,11 @@ impl<'a> Generator<'a> { None => buf.write(&format!("(&self.{})", normalize_identifier(name))), }, _ => { - self.visit_expr(buf, left)?; + self.visit_expr(ctx, buf, left)?; } } buf.write("("); - self._visit_args(buf, args)?; + self._visit_args(ctx, buf, args)?; buf.write(")"); } } @@ -1551,58 +1631,63 @@ impl<'a> Generator<'a> { fn visit_unary( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, op: &str, inner: &WithSpan<'_, Expr<'_>>, ) -> Result { buf.write(op); - self.visit_expr(buf, inner)?; + self.visit_expr(ctx, buf, inner)?; Ok(DisplayWrap::Unwrapped) } fn visit_range( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, op: &str, left: Option<&WithSpan<'_, Expr<'_>>>, right: Option<&WithSpan<'_, Expr<'_>>>, ) -> Result { if let Some(left) = left { - self.visit_expr(buf, left)?; + self.visit_expr(ctx, buf, left)?; } buf.write(op); if let Some(right) = right { - self.visit_expr(buf, right)?; + self.visit_expr(ctx, buf, right)?; } Ok(DisplayWrap::Unwrapped) } fn visit_binop( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, op: &str, left: &WithSpan<'_, Expr<'_>>, right: &WithSpan<'_, Expr<'_>>, ) -> Result { - self.visit_expr(buf, left)?; + self.visit_expr(ctx, buf, left)?; buf.write(&format!(" {op} ")); - self.visit_expr(buf, right)?; + self.visit_expr(ctx, buf, right)?; Ok(DisplayWrap::Unwrapped) } fn visit_group( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, inner: &WithSpan<'_, Expr<'_>>, ) -> Result { buf.write("("); - self.visit_expr(buf, inner)?; + self.visit_expr(ctx, buf, inner)?; buf.write(")"); Ok(DisplayWrap::Unwrapped) } fn visit_tuple( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, exprs: &[WithSpan<'_, Expr<'_>>], ) -> Result { @@ -1611,7 +1696,7 @@ impl<'a> Generator<'a> { if index > 0 { buf.write(" "); } - self.visit_expr(buf, expr)?; + self.visit_expr(ctx, buf, expr)?; buf.write(","); } buf.write(")"); @@ -1620,15 +1705,17 @@ impl<'a> Generator<'a> { fn visit_named_argument( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, expr: &WithSpan<'_, Expr<'_>>, ) -> Result { - self.visit_expr(buf, expr)?; + self.visit_expr(ctx, buf, expr)?; Ok(DisplayWrap::Unwrapped) } fn visit_array( &mut self, + ctx: &Context<'_>, buf: &mut Buffer, elements: &[WithSpan<'_, Expr<'_>>], ) -> Result { @@ -1637,7 +1724,7 @@ impl<'a> Generator<'a> { if i > 0 { buf.write(", "); } - self.visit_expr(buf, el)?; + self.visit_expr(ctx, buf, el)?; } buf.write("]"); Ok(DisplayWrap::Unwrapped) From 326b02e21be930fc1954bcc39637c7ca2d658458 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 May 2024 22:51:20 +0200 Subject: [PATCH 12/14] Remove `From` trait implementations for `CompileError` --- askama_derive/src/config.rs | 42 ++++++++------ askama_derive/src/generator.rs | 4 +- askama_derive/src/heritage.rs | 10 ++-- askama_derive/src/input.rs | 103 +++++++++++++++++++++++---------- askama_derive/src/lib.rs | 38 +++++------- 5 files changed, 117 insertions(+), 80 deletions(-) diff --git a/askama_derive/src/config.rs b/askama_derive/src/config.rs index 8ece2328..804ccbc4 100644 --- a/askama_derive/src/config.rs +++ b/askama_derive/src/config.rs @@ -138,11 +138,10 @@ impl<'a> Config<'a> { } } - Err(format!( + Err(CompileError::no_file_info(format!( "template {:?} not found in directories {:?}", path, self.dirs - ) - .into()) + ))) } } @@ -169,11 +168,13 @@ impl<'a> TryInto> for RawSyntax<'a> { syntax.comment_end, ] { if s.len() < 2 { - return Err( - format!("delimiters must be at least two characters long: {s:?}").into(), - ); + return Err(CompileError::no_file_info(format!( + "delimiters must be at least two characters long: {s:?}" + ))); } else if s.chars().any(|c| c.is_whitespace()) { - return Err(format!("delimiters may not contain white spaces: {s:?}").into()); + return Err(CompileError::no_file_info(format!( + "delimiters may not contain white spaces: {s:?}" + ))); } } @@ -183,10 +184,9 @@ impl<'a> TryInto> for RawSyntax<'a> { (syntax.expr_start, syntax.comment_start), ] { if s1.starts_with(s2) || s2.starts_with(s1) { - return Err(format!( + return Err(CompileError::no_file_info(format!( "a delimiter may not be the prefix of another delimiter: {s1:?} vs {s2:?}", - ) - .into()); + ))); } } @@ -206,13 +206,14 @@ struct RawConfig<'a> { impl RawConfig<'_> { #[cfg(feature = "config")] fn from_toml_str(s: &str) -> std::result::Result, CompileError> { - basic_toml::from_str(s) - .map_err(|e| format!("invalid TOML in {CONFIG_FILE_NAME}: {e}").into()) + basic_toml::from_str(s).map_err(|e| { + CompileError::no_file_info(format!("invalid TOML in {CONFIG_FILE_NAME}: {e}")) + }) } #[cfg(not(feature = "config"))] fn from_toml_str(_: &str) -> std::result::Result, CompileError> { - Err("TOML support not available".into()) + Err(CompileError::no_file_info("TOML support not available")) } } @@ -277,10 +278,14 @@ pub(crate) fn read_config_file( }; if filename.exists() { - fs::read_to_string(&filename) - .map_err(|_| format!("unable to read {:?}", filename.to_str().unwrap()).into()) + fs::read_to_string(&filename).map_err(|_| { + CompileError::no_file_info(format!("unable to read {:?}", filename.to_str().unwrap())) + }) } else if config_path.is_some() { - Err(format!("`{}` does not exist", root.display()).into()) + Err(CompileError::no_file_info(format!( + "`{}` does not exist", + root.display() + ))) } else { Ok("".to_string()) } @@ -313,11 +318,10 @@ pub(crate) fn get_template_source( )), )) } else { - Err(format!( + Err(CompileError::no_file_info(format!( "unable to open template file '{}'", tpl_path.to_str().unwrap() - ) - .into()) + ))) } } Ok(mut source) => { diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index 080ef3cc..bf4605f6 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -1972,7 +1972,9 @@ impl Buffer { fn dedent(&mut self) -> Result<(), CompileError> { if self.indent == 0 { - return Err("dedent() called while indentation == 0".into()); + return Err(CompileError::no_file_info( + "dedent() called while indentation == 0", + )); } self.indent -= 1; Ok(()) diff --git a/askama_derive/src/heritage.rs b/askama_derive/src/heritage.rs index 93fbbc37..086eeb1b 100644 --- a/askama_derive/src/heritage.rs +++ b/askama_derive/src/heritage.rs @@ -76,7 +76,9 @@ impl Context<'_> { for n in nodes { match n { Node::Extends(e) if top => match extends { - Some(_) => return Err("multiple extend blocks found".into()), + Some(_) => { + return Err(CompileError::no_file_info("multiple extend blocks found")) + } None => { extends = Some(config.find_template(e.path, Some(path))?); } @@ -89,9 +91,9 @@ impl Context<'_> { imports.insert(import.scope, path); } Node::Extends(_) | Node::Macro(_) | Node::Import(_) if !top => { - return Err( - "extends, macro or import blocks not allowed below top level".into(), - ); + return Err(CompileError::no_file_info( + "extends, macro or import blocks not allowed below top level", + )); } Node::BlockDef(b) => { blocks.insert(b.name, &**b); diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs index 84764c51..34948f41 100644 --- a/askama_derive/src/input.rs +++ b/askama_derive/src/input.rs @@ -55,7 +55,9 @@ impl TemplateInput<'_> { PathBuf::from(format!("{}.{}", ast.ident, ext)).into() } (&Source::Source(_), None) => { - return Err("must include 'ext' attribute when using 'source' attribute".into()) + return Err(CompileError::no_file_info( + "must include 'ext' attribute when using 'source' attribute", + )) } }; @@ -63,10 +65,9 @@ impl TemplateInput<'_> { let syntax = syntax.as_deref().map_or_else( || Ok(config.syntaxes.get(config.default_syntax).unwrap()), |s| { - config - .syntaxes - .get(s) - .ok_or_else(|| CompileError::from(format!("attribute syntax {s} not exist"))) + config.syntaxes.get(s).ok_or_else(|| { + CompileError::no_file_info(format!("attribute syntax {s} not exist")) + }) }, )?; @@ -85,7 +86,7 @@ impl TemplateInput<'_> { } let escaper = escaper.ok_or_else(|| { - CompileError::from(format!("no escaper defined for extension '{escaping}'")) + CompileError::no_file_info(format!("no escaper defined for extension '{escaping}'")) })?; let mime_type = @@ -236,13 +237,21 @@ impl TemplateArgs { match attr.parse_args_with(Punctuated::::parse_terminated) { Ok(args) if template_args.is_none() => template_args = Some(args), - Ok(_) => return Err("duplicated 'template' attribute".into()), - Err(e) => return Err(format!("unable to parse template arguments: {e}").into()), + Ok(_) => { + return Err(CompileError::no_file_info( + "duplicated 'template' attribute", + )) + } + Err(e) => { + return Err(CompileError::no_file_info(format!( + "unable to parse template arguments: {e}" + ))) + } }; } - let template_args = - template_args.ok_or_else(|| CompileError::from("no attribute 'template' found"))?; + let template_args = template_args + .ok_or_else(|| CompileError::no_file_info("no attribute 'template' found"))?; let mut args = Self::default(); // Loop over the meta attributes and find everything that we @@ -252,11 +261,10 @@ impl TemplateArgs { let pair = match item { syn::Meta::NameValue(pair) => pair, _ => { - return Err(format!( + return Err(CompileError::no_file_info(format!( "unsupported attribute argument {:?}", item.to_token_stream() - ) - .into()) + ))) } }; @@ -270,74 +278,104 @@ impl TemplateArgs { syn::Expr::Group(group) => match *group.expr { syn::Expr::Lit(lit) => lit, _ => { - return Err(format!("unsupported argument value type for {ident:?}").into()) + return Err(CompileError::no_file_info(format!( + "unsupported argument value type for {ident:?}" + ))) } }, - _ => return Err(format!("unsupported argument value type for {ident:?}").into()), + _ => { + return Err(CompileError::no_file_info(format!( + "unsupported argument value type for {ident:?}" + ))) + } }; if ident == "path" { if let syn::Lit::Str(s) = value.lit { if args.source.is_some() { - return Err("must specify 'source' or 'path', not both".into()); + return Err(CompileError::no_file_info( + "must specify 'source' or 'path', not both", + )); } args.source = Some(Source::Path(s.value())); } else { - return Err("template path must be string literal".into()); + return Err(CompileError::no_file_info( + "template path must be string literal", + )); } } else if ident == "source" { if let syn::Lit::Str(s) = value.lit { if args.source.is_some() { - return Err("must specify 'source' or 'path', not both".into()); + return Err(CompileError::no_file_info( + "must specify 'source' or 'path', not both", + )); } args.source = Some(Source::Source(s.value())); } else { - return Err("template source must be string literal".into()); + return Err(CompileError::no_file_info( + "template source must be string literal", + )); } } else if ident == "block" { if let syn::Lit::Str(s) = value.lit { args.block = Some(s.value()); } else { - return Err("block value must be string literal".into()); + return Err(CompileError::no_file_info( + "block value must be string literal", + )); } } else if ident == "print" { if let syn::Lit::Str(s) = value.lit { args.print = s.value().parse()?; } else { - return Err("print value must be string literal".into()); + return Err(CompileError::no_file_info( + "print value must be string literal", + )); } } else if ident == "escape" { if let syn::Lit::Str(s) = value.lit { args.escaping = Some(s.value()); } else { - return Err("escape value must be string literal".into()); + return Err(CompileError::no_file_info( + "escape value must be string literal", + )); } } else if ident == "ext" { if let syn::Lit::Str(s) = value.lit { args.ext = Some(s.value()); } else { - return Err("ext value must be string literal".into()); + return Err(CompileError::no_file_info( + "ext value must be string literal", + )); } } else if ident == "syntax" { if let syn::Lit::Str(s) = value.lit { args.syntax = Some(s.value()) } else { - return Err("syntax value must be string literal".into()); + return Err(CompileError::no_file_info( + "syntax value must be string literal", + )); } } else if ident == "config" { if let syn::Lit::Str(s) = value.lit { args.config = Some(s.value()); } else { - return Err("config value must be string literal".into()); + return Err(CompileError::no_file_info( + "config value must be string literal", + )); } } else if ident == "whitespace" { if let syn::Lit::Str(s) = value.lit { args.whitespace = Some(s.value()) } else { - return Err("whitespace value must be string literal".into()); + return Err(CompileError::no_file_info( + "whitespace value must be string literal", + )); } } else { - return Err(format!("unsupported attribute key {ident:?} found").into()); + return Err(CompileError::no_file_info(format!( + "unsupported attribute key {ident:?} found" + ))); } } @@ -399,7 +437,11 @@ impl FromStr for Print { "ast" => Print::Ast, "code" => Print::Code, "none" => Print::None, - v => return Err(format!("invalid value for print option: {v}",).into()), + v => { + return Err(CompileError::no_file_info(format!( + "invalid value for print option: {v}" + ))) + } }) } } @@ -437,14 +479,13 @@ const TEXT_TYPES: [(Mime, Mime); 7] = [ ]; fn cyclic_graph_error(dependency_graph: &[(Rc, Rc)]) -> Result<(), CompileError> { - Err(format!( + Err(CompileError::no_file_info(format!( "cyclic dependency in graph {:#?}", dependency_graph .iter() .map(|e| format!("{:#?} --> {:#?}", e.0, e.1)) .collect::>() - ) - .into()) + ))) } #[cfg(test)] diff --git a/askama_derive/src/lib.rs b/askama_derive/src/lib.rs index 90e68a53..93f7f3c0 100644 --- a/askama_derive/src/lib.rs +++ b/askama_derive/src/lib.rs @@ -75,7 +75,10 @@ pub(crate) fn build_template(ast: &syn::DeriveInput) -> Result(msg: S) -> Self { + Self { + msg: msg.to_string(), + span: Span::call_site(), + } + } + fn into_compile_error(self) -> TokenStream { syn::Error::new(self.span, self.msg) .to_compile_error() @@ -133,30 +143,8 @@ impl fmt::Display for CompileError { impl From for CompileError { #[inline] fn from(e: ParseError) -> Self { - Self { - msg: e.to_string(), - span: Span::call_site(), - } - } -} - -impl From<&'static str> for CompileError { - #[inline] - fn from(s: &'static str) -> Self { - Self { - msg: s.into(), - span: Span::call_site(), - } - } -} - -impl From for CompileError { - #[inline] - fn from(s: String) -> Self { - Self { - msg: s, - span: Span::call_site(), - } + // It already has the correct message so no need to do anything. + Self::no_file_info(e) } } From a6613b6f3bf86966ac280704affa07580897606b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 May 2024 22:51:55 +0200 Subject: [PATCH 13/14] Update ui tests --- testing/tests/ui/lit_on_assignment_lhs.stderr | 2 ++ testing/tests/ui/loop_cycle_empty.stderr | 2 ++ testing/tests/ui/loop_cycle_wrong_argument_count.stderr | 6 ++++-- testing/tests/ui/macro.stderr | 6 ++++++ testing/tests/ui/macro_named_argument.stderr | 4 ++++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/testing/tests/ui/lit_on_assignment_lhs.stderr b/testing/tests/ui/lit_on_assignment_lhs.stderr index 53bfc81d..ceb2b773 100644 --- a/testing/tests/ui/lit_on_assignment_lhs.stderr +++ b/testing/tests/ui/lit_on_assignment_lhs.stderr @@ -1,4 +1,6 @@ error: literals are not allowed on the left-hand side of an assignment + --> MyTemplate.txt:1:2 + "let 7=x%}" --> tests/ui/lit_on_assignment_lhs.rs:3:10 | 3 | #[derive(Template)] diff --git a/testing/tests/ui/loop_cycle_empty.stderr b/testing/tests/ui/loop_cycle_empty.stderr index dd4f0ddf..a34673ea 100644 --- a/testing/tests/ui/loop_cycle_empty.stderr +++ b/testing/tests/ui/loop_cycle_empty.stderr @@ -1,4 +1,6 @@ error: loop.cycle(…) cannot use an empty array + --> ForCycleEmpty.txt:1:35 + "[]) }}{{ v }},{% endfor %}" --> tests/ui/loop_cycle_empty.rs:3:10 | 3 | #[derive(Template)] diff --git a/testing/tests/ui/loop_cycle_wrong_argument_count.stderr b/testing/tests/ui/loop_cycle_wrong_argument_count.stderr index acf3bb18..6a033e03 100644 --- a/testing/tests/ui/loop_cycle_wrong_argument_count.stderr +++ b/testing/tests/ui/loop_cycle_wrong_argument_count.stderr @@ -1,5 +1,7 @@ -error: loop.cycle(…) expects exactly one argument - --> $DIR/loop_cycle_wrong_argument_count.rs:3:10 +error: loop.cycle(…) cannot use an empty array + --> ForCycle.txt:1:28 + ".cycle(\"r\", \"g\", \"b\") }}{{ v }},{% endfo"... + --> tests/ui/loop_cycle_wrong_argument_count.rs:3:10 | 3 | #[derive(Template)] | ^^^^^^^^ diff --git a/testing/tests/ui/macro.stderr b/testing/tests/ui/macro.stderr index 5471c965..f2011721 100644 --- a/testing/tests/ui/macro.stderr +++ b/testing/tests/ui/macro.stderr @@ -1,4 +1,6 @@ error: macro "thrice" expected 1 argument, found 2 + --> InvalidNumberOfArgs.html:5:2 + "- call thrice(2, 3) -%}" --> tests/ui/macro.rs:3:10 | 3 | #[derive(Template)] @@ -7,6 +9,8 @@ error: macro "thrice" expected 1 argument, found 2 = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) error: macro "thrice" expected 2 arguments, found 0 + --> InvalidNumberOfArgs2.html:5:2 + "- call thrice() -%}" --> tests/ui/macro.rs:11:10 | 11 | #[derive(Template)] @@ -15,6 +19,8 @@ error: macro "thrice" expected 2 arguments, found 0 = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) error: macro "thrice" expected 0 arguments, found 2 + --> InvalidNumberOfArgs3.html:4:2 + "- call thrice(1, 2) -%}" --> tests/ui/macro.rs:19:10 | 19 | #[derive(Template)] diff --git a/testing/tests/ui/macro_named_argument.stderr b/testing/tests/ui/macro_named_argument.stderr index f855e6c6..e31ed0e4 100644 --- a/testing/tests/ui/macro_named_argument.stderr +++ b/testing/tests/ui/macro_named_argument.stderr @@ -1,4 +1,6 @@ error: no argument named `param3` in macro "thrice" + --> InvalidNamedArg.html:5:2 + "- call thrice(param1=2, param3=3) -%}" --> tests/ui/macro_named_argument.rs:3:10 | 3 | #[derive(Template)] @@ -36,6 +38,8 @@ error: named arguments must always be passed last = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info) error: cannot have unnamed argument (`param2`) after named argument in macro "thrice" + --> InvalidNamedArg5.html:4:2 + "- call thrice(3, param1=2) -%}" --> tests/ui/macro_named_argument.rs:37:10 | 37 | #[derive(Template)] From 22d352b08c718d9bf8a3d120923e8ff838d784fd Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 27 May 2024 22:53:44 +0200 Subject: [PATCH 14/14] Fix clippy lints --- askama_derive/src/generator.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/askama_derive/src/generator.rs b/askama_derive/src/generator.rs index bf4605f6..24bd7402 100644 --- a/askama_derive/src/generator.rs +++ b/askama_derive/src/generator.rs @@ -672,7 +672,7 @@ impl<'a> Generator<'a> { } Expr::Attr(obj, attr) => { let mut attr_buf = Buffer::new(0); - self.visit_attr(ctx, &mut attr_buf, &obj, attr)?; + self.visit_attr(ctx, &mut attr_buf, obj, attr)?; let var = self.locals.resolve(&attr_buf.buf).unwrap_or(attr_buf.buf); self.locals @@ -2141,18 +2141,18 @@ pub(crate) fn is_cacheable(expr: &WithSpan<'_, Expr<'_>>) -> bool { Expr::Path(_) => true, // Check recursively: Expr::Array(args) => args.iter().all(is_cacheable), - Expr::Attr(lhs, _) => is_cacheable(&lhs), - Expr::Index(lhs, rhs) => is_cacheable(&lhs) && is_cacheable(&rhs), + Expr::Attr(lhs, _) => is_cacheable(lhs), + Expr::Index(lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs), Expr::Filter(Filter { arguments, .. }) => arguments.iter().all(is_cacheable), - Expr::Unary(_, arg) => is_cacheable(&*arg), - Expr::BinOp(_, lhs, rhs) => is_cacheable(&lhs) && is_cacheable(&rhs), + Expr::Unary(_, arg) => is_cacheable(arg), + Expr::BinOp(_, lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs), Expr::Range(_, lhs, rhs) => { lhs.as_ref().map_or(true, |v| is_cacheable(v)) && rhs.as_ref().map_or(true, |v| is_cacheable(v)) } - Expr::Group(arg) => is_cacheable(&arg), + Expr::Group(arg) => is_cacheable(arg), Expr::Tuple(args) => args.iter().all(is_cacheable), - Expr::NamedArgument(_, expr) => is_cacheable(&expr), + Expr::NamedArgument(_, expr) => is_cacheable(expr), // We have too little information to tell if the expression is pure: Expr::Call(_, _) => false, Expr::RustMacro(_, _) => false,