diff --git a/Makefile b/Makefile index e39087dd..87a215f4 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ run-tests: @echo "CARGO TEST SPEEDUPS" @cd minijinja; cargo test --no-default-features --features=speedups,$(FEATURES) @echo "CARGO CHECK NO_DEFAULT_FEATURES" - @cd minijinja; cargo check --no-default-features + @cd minijinja; cargo check --no-default-features --features=debug check: @echo "check no default features:" diff --git a/examples/error/src/main.rs b/examples/error/src/main.rs index 192ba4a9..31f08006 100644 --- a/examples/error/src/main.rs +++ b/examples/error/src/main.rs @@ -26,7 +26,7 @@ fn main() { let template = env.get_template("hello.txt").unwrap(); let ctx = context! { seq => vec![2, 4, 8], - other_seq => (0..20).collect::>(), + other_seq => (0..5).collect::>(), bar => "test" }; match template.render(&ctx) { diff --git a/minijinja/src/compiler/codegen.rs b/minijinja/src/compiler/codegen.rs index f157f430..545ea536 100644 --- a/minijinja/src/compiler/codegen.rs +++ b/minijinja/src/compiler/codegen.rs @@ -27,6 +27,7 @@ pub struct CodeGenerator<'source> { blocks: BTreeMap<&'source str, Instructions<'source>>, pending_block: Vec, current_line: usize, + span_stack: Vec, } impl<'source> CodeGenerator<'source> { @@ -37,6 +38,7 @@ impl<'source> CodeGenerator<'source> { blocks: BTreeMap::new(), pending_block: Vec::new(), current_line: 0, + span_stack: Vec::new(), } } @@ -50,8 +52,24 @@ impl<'source> CodeGenerator<'source> { self.set_line(span.start_line); } + /// Pushes a span to the stack + pub fn push_span(&mut self, span: Span) { + self.span_stack.push(span); + self.set_line_from_span(span); + } + + /// Pops a span from the stack. + pub fn pop_span(&mut self) { + self.span_stack.pop(); + } + /// Add a simple instruction. pub fn add(&mut self, instr: Instruction<'source>) -> usize { + if let Some(span) = self.span_stack.last() { + if span.start_line == self.current_line { + return self.instructions.add_with_span(instr, *span); + } + } self.instructions.add_with_line(instr, self.current_line) } @@ -353,15 +371,15 @@ impl<'source> CodeGenerator<'source> { pub fn compile_assignment(&mut self, expr: &ast::Expr<'source>) -> Result<(), Error> { match expr { ast::Expr::Var(var) => { - self.set_line_from_span(var.span()); self.add(Instruction::StoreLocal(var.id)); } ast::Expr::List(list) => { - self.set_line_from_span(list.span()); + self.push_span(list.span()); self.add(Instruction::UnpackList(list.items.len())); for expr in &list.items { self.compile_assignment(expr)?; } + self.pop_span(); } _ => panic!("bad assignment target"), } @@ -404,7 +422,7 @@ impl<'source> CodeGenerator<'source> { self.end_if(); } ast::Expr::Filter(f) => { - self.set_line_from_span(f.span()); + self.push_span(f.span()); if let Some(ref expr) = f.expr { self.compile_expr(expr)?; } @@ -413,26 +431,30 @@ impl<'source> CodeGenerator<'source> { } self.add(Instruction::BuildList(f.args.len() + 1)); self.add(Instruction::ApplyFilter(f.name)); + self.pop_span(); } ast::Expr::Test(f) => { - self.set_line_from_span(f.span()); + self.push_span(f.span()); self.compile_expr(&f.expr)?; for arg in &f.args { self.compile_expr(arg)?; } self.add(Instruction::BuildList(f.args.len() + 1)); self.add(Instruction::PerformTest(f.name)); + self.pop_span(); } ast::Expr::GetAttr(g) => { - self.set_line_from_span(g.span()); + self.push_span(g.span()); self.compile_expr(&g.expr)?; self.add(Instruction::GetAttr(g.name)); + self.pop_span(); } ast::Expr::GetItem(g) => { - self.set_line_from_span(g.span()); + self.push_span(g.span()); self.compile_expr(&g.expr)?; self.compile_expr(&g.subscript_expr)?; self.add(Instruction::GetItem); + self.pop_span(); } ast::Expr::Call(c) => { self.compile_call(c)?; @@ -458,7 +480,7 @@ impl<'source> CodeGenerator<'source> { } fn compile_call(&mut self, c: &ast::Spanned>) -> Result<(), Error> { - self.set_line_from_span(c.span()); + self.push_span(c.span()); match c.identify_call() { ast::CallType::Function(name) => { for arg in &c.args { @@ -485,11 +507,12 @@ impl<'source> CodeGenerator<'source> { self.add(Instruction::CallObject); } }; + self.pop_span(); Ok(()) } fn compile_bin_op(&mut self, c: &ast::Spanned>) -> Result<(), Error> { - self.set_line_from_span(c.span()); + self.push_span(c.span()); let instr = match c.op { ast::BinOpKind::Eq => Instruction::Eq, ast::BinOpKind::Ne => Instruction::Ne, @@ -518,6 +541,7 @@ impl<'source> CodeGenerator<'source> { self.compile_expr(&c.left)?; self.compile_expr(&c.right)?; self.add(instr); + self.pop_span(); Ok(()) } diff --git a/minijinja/src/compiler/instructions.rs b/minijinja/src/compiler/instructions.rs index 85a8e5e6..462c8a3b 100644 --- a/minijinja/src/compiler/instructions.rs +++ b/minijinja/src/compiler/instructions.rs @@ -4,6 +4,7 @@ use std::fmt; #[cfg(test)] use similar_asserts::assert_eq; +use crate::compiler::tokens::Span; use crate::value::Value; /// This loop has the loop var. @@ -182,15 +183,23 @@ pub enum Instruction<'source> { } #[derive(Copy, Clone)] -struct Loc { +struct LineInfo { first_instruction: u32, line: u32, } +#[derive(Copy, Clone)] +struct SpanInfo { + first_instruction: u32, + span: Option, +} + /// Wrapper around instructions to help with location management. pub struct Instructions<'source> { pub(crate) instructions: Vec>, - locations: Vec, + line_infos: Vec, + #[cfg(feature = "debug")] + span_infos: Vec, name: &'source str, source: &'source str, } @@ -200,7 +209,9 @@ impl<'source> Instructions<'source> { pub fn new(name: &'source str, source: &'source str) -> Instructions<'source> { Instructions { instructions: Vec::new(), - locations: Vec::new(), + line_infos: Vec::new(), + #[cfg(feature = "debug")] + span_infos: Vec::new(), name, source, } @@ -234,35 +245,79 @@ impl<'source> Instructions<'source> { rv } - /// Adds a new instruction with location info. - pub fn add_with_line(&mut self, instr: Instruction<'source>, line: usize) -> usize { - let rv = self.add(instr); + fn add_line_record(&mut self, instr: usize, line: usize) { let same_loc = self - .locations + .line_infos .last() .map_or(false, |last_loc| last_loc.line as usize == line); if !same_loc { - self.locations.push(Loc { - first_instruction: rv as u32, + self.line_infos.push(LineInfo { + first_instruction: instr as u32, line: line as u32, }); } + } + + /// Adds a new instruction with line number. + pub fn add_with_line(&mut self, instr: Instruction<'source>, line: usize) -> usize { + let rv = self.add(instr); + self.add_line_record(rv, line); + rv + } + + /// Adds a new instruction with span. + pub fn add_with_span(&mut self, instr: Instruction<'source>, span: Span) -> usize { + let rv = self.add(instr); + #[cfg(feature = "debug")] + { + let same_loc = self + .span_infos + .last() + .map_or(false, |last_loc| last_loc.span == Some(span)); + if !same_loc { + self.span_infos.push(SpanInfo { + first_instruction: rv as u32, + span: Some(span), + }); + } + } + self.add_line_record(rv, span.start_line); rv } /// Looks up the line for an instruction pub fn get_line(&self, idx: usize) -> Option { let loc = match self - .locations + .line_infos .binary_search_by_key(&idx, |x| x.first_instruction as usize) { - Ok(idx) => &self.locations[idx as usize], + Ok(idx) => &self.line_infos[idx as usize], Err(0) => return None, - Err(idx) => &self.locations[idx as usize - 1], + Err(idx) => &self.line_infos[idx as usize - 1], }; Some(loc.line as usize) } + /// Looks up a span for an instruction. + pub fn get_span(&self, idx: usize) -> Option { + #[cfg(feature = "debug")] + { + let loc = match self + .span_infos + .binary_search_by_key(&idx, |x| x.first_instruction as usize) + { + Ok(idx) => &self.span_infos[idx as usize], + Err(0) => return None, + Err(idx) => &self.span_infos[idx as usize - 1], + }; + loc.span + } + #[cfg(not(feature = "debug"))] + { + None + } + } + /// Returns a list of all names referenced in the current block backwards /// from the given pc. #[cfg(feature = "debug")] diff --git a/minijinja/src/compiler/parser.rs b/minijinja/src/compiler/parser.rs index 4c7c06aa..87e4bdcd 100644 --- a/minijinja/src/compiler/parser.rs +++ b/minijinja/src/compiler/parser.rs @@ -65,7 +65,7 @@ enum SetParseResult<'a> { struct TokenStream<'a> { iter: Box, Span), Error>> + 'a>, current: Option, Span), Error>>, - current_span: Span, + last_span: Span, } impl<'a> TokenStream<'a> { @@ -74,7 +74,7 @@ impl<'a> TokenStream<'a> { TokenStream { iter: (Box::new(tokenize(source, in_expr)) as Box>), current: None, - current_span: Span::default(), + last_span: Span::default(), } } @@ -84,7 +84,7 @@ impl<'a> TokenStream<'a> { let rv = self.current.take(); self.current = self.iter.next(); if let Some(Ok((_, span))) = rv { - self.current_span = span; + self.last_span = span; } rv.transpose() } @@ -105,15 +105,25 @@ impl<'a> TokenStream<'a> { /// Expands the span #[inline(always)] pub fn expand_span(&self, mut span: Span) -> Span { - span.end_line = self.current_span.end_line; - span.end_col = self.current_span.end_col; + span.end_line = self.last_span.end_line; + span.end_col = self.last_span.end_col; span } - /// Returns the last seen span. + /// Returns the current span. #[inline(always)] pub fn current_span(&self) -> Span { - self.current_span + if let Some(Ok((_, span))) = self.current { + span + } else { + self.last_span + } + } + + /// Returns the last seen span. + #[inline(always)] + pub fn last_span(&self) -> Span { + self.last_span } } @@ -175,7 +185,7 @@ impl<'a> Parser<'a> { } fn parse_ifexpr(&mut self) -> Result, Error> { - let mut span = self.stream.current_span(); + let mut span = self.stream.last_span(); let mut expr = self.parse_or()?; loop { if matches!(self.stream.current()?, Some((Token::Ident("if"), _))) { @@ -195,7 +205,7 @@ impl<'a> Parser<'a> { }, self.stream.expand_span(span), )); - span = self.stream.current_span(); + span = self.stream.last_span(); } else { break; } @@ -214,7 +224,7 @@ impl<'a> Parser<'a> { }); fn parse_compare(&mut self) -> Result, Error> { - let mut span = self.stream.current_span(); + let mut span = self.stream.last_span(); let mut expr = self.parse_math1()?; loop { let mut negated = false; @@ -254,7 +264,7 @@ impl<'a> Parser<'a> { self.stream.expand_span(span), )); } - span = self.stream.current_span(); + span = self.stream.last_span(); } Ok(expr) } @@ -280,16 +290,22 @@ impl<'a> Parser<'a> { }); fn parse_unary(&mut self) -> Result, Error> { + let span = self.stream.current_span(); let mut expr = self.parse_unary_only()?; - expr = self.parse_postfix(expr)?; + expr = self.parse_postfix(expr, span)?; self.parse_filter_expr(expr) } - fn parse_postfix(&mut self, expr: ast::Expr<'a>) -> Result, Error> { + fn parse_postfix( + &mut self, + expr: ast::Expr<'a>, + mut span: Span, + ) -> Result, Error> { let mut expr = expr; loop { + let next_span = self.stream.current_span(); match self.stream.current()? { - Some((Token::Dot, span)) => { + Some((Token::Dot, _)) => { self.stream.next()?; let (name, _) = expect_token!(self, Token::Ident(name) => name, "identifier")?; expr = ast::Expr::GetAttr(Spanned::new( @@ -297,7 +313,7 @@ impl<'a> Parser<'a> { self.stream.expand_span(span), )); } - Some((Token::BracketOpen, span)) => { + Some((Token::BracketOpen, _)) => { self.stream.next()?; let subscript_expr = self.parse_expr()?; expect_token!(self, Token::BracketClose, "`]`")?; @@ -309,7 +325,7 @@ impl<'a> Parser<'a> { self.stream.expand_span(span), )); } - Some((Token::ParenOpen, span)) => { + Some((Token::ParenOpen, _)) => { let args = self.parse_args()?; expr = ast::Expr::Call(Spanned::new( ast::Call { expr, args }, @@ -318,6 +334,7 @@ impl<'a> Parser<'a> { } _ => break, } + span = next_span; } Ok(expr) } @@ -894,7 +911,7 @@ impl<'a> Parser<'a> { pub fn parse(&mut self) -> Result, Error> { // start the stream self.stream.next()?; - let span = self.stream.current_span(); + let span = self.stream.last_span(); Ok(ast::Stmt::Template(Spanned::new( ast::Template { children: self.subparse(&|_| false)?, @@ -924,7 +941,7 @@ pub fn parse<'source, 'name>( let mut parser = Parser::new(source, false); parser.parse().map_err(|mut err| { if err.line().is_none() { - err.set_location(filename, parser.stream.current_span().start_line) + err.set_filename_and_span(filename, parser.stream.last_span()) } err }) @@ -935,7 +952,7 @@ pub fn parse_expr(source: &str) -> Result, Error> { let mut parser = Parser::new(source, true); parser.parse_expr().map_err(|mut err| { if err.line().is_none() { - err.set_location("", parser.stream.current_span().start_line) + err.set_filename_and_span("", parser.stream.last_span()) } err }) diff --git a/minijinja/src/compiler/tokens.rs b/minijinja/src/compiler/tokens.rs index 997f3c9f..cdaa96d1 100644 --- a/minijinja/src/compiler/tokens.rs +++ b/minijinja/src/compiler/tokens.rs @@ -119,7 +119,7 @@ impl<'a> fmt::Display for Token<'a> { } /// Token span information -#[derive(Clone, Copy, Default)] +#[derive(Clone, Copy, Default, PartialEq, Eq)] pub struct Span { pub start_line: usize, pub start_col: usize, diff --git a/minijinja/src/error.rs b/minijinja/src/error.rs index f782b58b..c826bc84 100644 --- a/minijinja/src/error.rs +++ b/minijinja/src/error.rs @@ -28,6 +28,7 @@ pub struct Error { detail: Option>, name: Option, lineno: usize, + span: Option, source: Option>, #[cfg(feature = "debug")] pub(crate) debug_info: Option, @@ -58,7 +59,7 @@ impl fmt::Debug for Error { { if let Some(info) = self.debug_info() { writeln!(f)?; - render_debug_info(f, self.line(), info)?; + render_debug_info(f, self.kind, self.line(), self.span, info)?; writeln!(f)?; } } @@ -88,6 +89,8 @@ pub enum ErrorKind { UnknownFilter, /// A test is unknown UnknownTest, + /// A function is unknown + UnknownFunction, /// A bad escape sequence in a string was encountered. BadEscape, /// An operation on an undefined value was attempted. @@ -109,9 +112,10 @@ impl ErrorKind { ErrorKind::TooManyArguments => "too many arguments", ErrorKind::MissingArgument => "missing argument", ErrorKind::UnknownFilter => "unknown filter", + ErrorKind::UnknownFunction => "unknown function", ErrorKind::UnknownTest => "unknown test", ErrorKind::BadEscape => "bad string escape", - ErrorKind::UndefinedError => "variable or attribute undefined", + ErrorKind::UndefinedError => "undefined value", ErrorKind::BadSerialization => "could not serialize to internal format", ErrorKind::WriteFailure => "failed to write output", } @@ -138,7 +142,7 @@ impl fmt::Display for Error { { if f.alternate() { if let Some(info) = self.debug_info() { - render_debug_info(f, self.line(), info)?; + render_debug_info(f, self.kind, self.line(), self.span, info)?; } } } @@ -154,17 +158,24 @@ impl Error { detail: Some(detail.into()), name: None, lineno: 0, + span: None, source: None, #[cfg(feature = "debug")] debug_info: None, } } - pub(crate) fn set_location(&mut self, filename: &str, lineno: usize) { + pub(crate) fn set_filename_and_line(&mut self, filename: &str, lineno: usize) { self.name = Some(filename.into()); self.lineno = lineno; } + pub(crate) fn set_filename_and_span(&mut self, filename: &str, span: Span) { + self.name = Some(filename.into()); + self.span = Some(span); + self.lineno = span.start_line; + } + pub(crate) fn new_not_found(name: &str) -> Error { Error::new( ErrorKind::TemplateNotFound, @@ -219,6 +230,7 @@ impl From for Error { detail: None, name: None, lineno: 0, + span: None, source: None, #[cfg(feature = "debug")] debug_info: None, @@ -297,7 +309,9 @@ mod debug_info { pub(super) fn render_debug_info( f: &mut fmt::Formatter, + kind: ErrorKind, line: Option, + span: Option, info: &DebugInfo, ) -> fmt::Result { if let Some(source) = info.source() { @@ -311,7 +325,20 @@ mod debug_info { for (idx, line) in pre { writeln!(f, "{:>4} | {}", idx + 1, line).unwrap(); } + writeln!(f, "{:>4} > {}", idx + 1, lines[idx].1).unwrap(); + if let Some(span) = span { + if span.start_line == span.end_line { + writeln!( + f, + " i {}{} {}", + " ".repeat(span.start_col), + "^".repeat(span.end_col - span.start_col), + kind, + )?; + } + } + for (idx, line) in post { writeln!(f, "{:>4} | {}", idx + 1, line).unwrap(); } @@ -349,5 +376,7 @@ pub fn attach_basic_debug_info(rv: Result, source: &str) -> Result< } } +use crate::compiler::tokens::Span; + #[cfg(feature = "debug")] pub(crate) use self::debug_info::*; diff --git a/minijinja/src/vm/mod.rs b/minijinja/src/vm/mod.rs index c384cad8..1660ec4c 100644 --- a/minijinja/src/vm/mod.rs +++ b/minijinja/src/vm/mod.rs @@ -355,8 +355,8 @@ impl<'env> Vm<'env> { stack.push(try_ctx!(func.call(state, args))); } else { bail!(Error::new( - ErrorKind::ImpossibleOperation, - format!("unknown function {}", function_name), + ErrorKind::UnknownFunction, + format!("{} is unknown", function_name), )); } } @@ -632,8 +632,10 @@ impl<'env> Vm<'env> { } fn process_err(mut err: Error, pc: usize, state: &State) -> Error { - if let Some(lineno) = state.instructions.get_line(pc) { - err.set_location(state.instructions.name(), lineno); + if let Some(span) = state.instructions.get_span(pc) { + err.set_filename_and_span(state.instructions.name(), span); + } else if let Some(lineno) = state.instructions.get_line(pc) { + err.set_filename_and_line(state.instructions.name(), lineno); } #[cfg(feature = "debug")] { diff --git a/minijinja/tests/inputs/err_bad_addition.txt b/minijinja/tests/inputs/err_bad_addition.txt new file mode 100644 index 00000000..566f2ea1 --- /dev/null +++ b/minijinja/tests/inputs/err_bad_addition.txt @@ -0,0 +1,3 @@ +{} +--- +{{ [1, 2] + 23 }} diff --git a/minijinja/tests/inputs/err_bad_call.txt b/minijinja/tests/inputs/err_bad_call.txt new file mode 100644 index 00000000..a777efcc --- /dev/null +++ b/minijinja/tests/inputs/err_bad_call.txt @@ -0,0 +1,3 @@ +{} +--- +This is {{ an_unknown_function() }}! diff --git a/minijinja/tests/inputs/err_bad_filter.txt b/minijinja/tests/inputs/err_bad_filter.txt new file mode 100644 index 00000000..7e56f2a8 --- /dev/null +++ b/minijinja/tests/inputs/err_bad_filter.txt @@ -0,0 +1,5 @@ +{} +--- +{% for item in 42|slice(4) %} + - {{ item }} +{% endfor %} diff --git a/minijinja/tests/inputs/err_bad_nested_subtraction.txt b/minijinja/tests/inputs/err_bad_nested_subtraction.txt new file mode 100644 index 00000000..58e1f550 --- /dev/null +++ b/minijinja/tests/inputs/err_bad_nested_subtraction.txt @@ -0,0 +1,5 @@ +{"seq": [1, 2, 3]} +--- +{% for item in seq %} + {{ ((item + 4) * (3 - [])) + 4 - 2 }} +{% endfor %} \ No newline at end of file diff --git a/minijinja/tests/inputs/err_bad_test.txt b/minijinja/tests/inputs/err_bad_test.txt new file mode 100644 index 00000000..841f8a07 --- /dev/null +++ b/minijinja/tests/inputs/err_bad_test.txt @@ -0,0 +1,7 @@ +{"seq": [1, 2, 3]} +--- +{% for item in seq %} + {% if item is reallyEven %} + {{ item }} + {% endif %} +{% endfor %} diff --git a/minijinja/tests/inputs/err_bad_test_arguments.txt b/minijinja/tests/inputs/err_bad_test_arguments.txt new file mode 100644 index 00000000..73844df3 --- /dev/null +++ b/minijinja/tests/inputs/err_bad_test_arguments.txt @@ -0,0 +1,7 @@ +{"seq": [1, 2, 3]} +--- +{% for item in seq %} + {% if item is even(42) %} + {{ item }} + {% endif %} +{% endfor %} diff --git a/minijinja/tests/inputs/err_undefined_item.txt b/minijinja/tests/inputs/err_undefined_item.txt new file mode 100644 index 00000000..cf3b3b5e --- /dev/null +++ b/minijinja/tests/inputs/err_undefined_item.txt @@ -0,0 +1,3 @@ +{"seq": [1, 2, 3]} +--- +{{ seq[42][23] }} \ No newline at end of file diff --git a/minijinja/tests/inputs/err_undefined_nested_attr.txt b/minijinja/tests/inputs/err_undefined_nested_attr.txt new file mode 100644 index 00000000..edb2266f --- /dev/null +++ b/minijinja/tests/inputs/err_undefined_nested_attr.txt @@ -0,0 +1,4 @@ +{"seq": [1, 2, 3]} +--- +{{ seq.whatever }} +{{ seq.whatever.else }} \ No newline at end of file diff --git a/minijinja/tests/snapshots/test_compiler__bool_ops.snap b/minijinja/tests/snapshots/test_compiler__bool_ops.snap index 3641cf91..c2676ab8 100644 --- a/minijinja/tests/snapshots/test_compiler__bool_ops.snap +++ b/minijinja/tests/snapshots/test_compiler__bool_ops.snap @@ -13,4 +13,5 @@ CodeGenerator { blocks: {}, pending_block: [], current_line: 0, + span_stack: [], } diff --git a/minijinja/tests/snapshots/test_compiler__const.snap b/minijinja/tests/snapshots/test_compiler__const.snap index 1e8ec7a1..774c2189 100644 --- a/minijinja/tests/snapshots/test_compiler__const.snap +++ b/minijinja/tests/snapshots/test_compiler__const.snap @@ -11,4 +11,5 @@ CodeGenerator { blocks: {}, pending_block: [], current_line: 0, + span_stack: [], } diff --git a/minijinja/tests/snapshots/test_compiler__for_loop.snap b/minijinja/tests/snapshots/test_compiler__for_loop.snap index e73deeba..102f1a5d 100644 --- a/minijinja/tests/snapshots/test_compiler__for_loop.snap +++ b/minijinja/tests/snapshots/test_compiler__for_loop.snap @@ -15,4 +15,5 @@ CodeGenerator { blocks: {}, pending_block: [], current_line: 0, + span_stack: [], } diff --git a/minijinja/tests/snapshots/test_compiler__if_branches.snap b/minijinja/tests/snapshots/test_compiler__if_branches.snap index 654e16c5..aa7458d7 100644 --- a/minijinja/tests/snapshots/test_compiler__if_branches.snap +++ b/minijinja/tests/snapshots/test_compiler__if_branches.snap @@ -17,4 +17,5 @@ CodeGenerator { blocks: {}, pending_block: [], current_line: 0, + span_stack: [], } diff --git a/minijinja/tests/snapshots/test_parser__parser@call.txt.snap b/minijinja/tests/snapshots/test_parser__parser@call.txt.snap index 518f91f1..b0a25f1c 100644 --- a/minijinja/tests/snapshots/test_parser__parser@call.txt.snap +++ b/minijinja/tests/snapshots/test_parser__parser@call.txt.snap @@ -12,7 +12,7 @@ Ok( id: "super", } @ 1:3-1:8, args: [], - } @ 1:8-1:10, + } @ 1:3-1:10, } @ 1:0-1:10, EmitRaw { raw: "\n", @@ -24,7 +24,7 @@ Ok( id: "loop", } @ 2:3-2:7, name: "cycle", - } @ 2:7-2:13, + } @ 2:3-2:13, args: [ Const { value: 1, @@ -33,7 +33,7 @@ Ok( value: 2, } @ 2:17-2:18, ], - } @ 2:13-2:19, + } @ 2:7-2:19, } @ 2:0-2:19, EmitRaw { raw: "\n", @@ -45,9 +45,9 @@ Ok( id: "self", } @ 3:3-3:7, name: "foo", - } @ 3:7-3:11, + } @ 3:3-3:11, args: [], - } @ 3:11-3:13, + } @ 3:7-3:13, } @ 3:0-3:13, EmitRaw { raw: "\n", @@ -83,7 +83,7 @@ Ok( ], } @ 4:13-4:21, ], - } @ 4:6-4:22, + } @ 4:3-4:22, } @ 4:0-4:22, EmitRaw { raw: "\n", @@ -101,7 +101,7 @@ Ok( value: 2, } @ 5:15-5:16, ], - } @ 5:11-5:18, + } @ 5:3-5:18, } @ 5:0-5:18, EmitRaw { raw: "\n", @@ -131,7 +131,7 @@ Ok( ], } @ 6:24-6:28, ], - } @ 6:17-6:29, + } @ 6:3-6:29, } @ 6:0-6:29, ], } @ 0:0-6:32, diff --git a/minijinja/tests/snapshots/test_parser__parser@escape.txt.snap b/minijinja/tests/snapshots/test_parser__parser@escape.txt.snap index 9247b7c9..81314e95 100644 --- a/minijinja/tests/snapshots/test_parser__parser@escape.txt.snap +++ b/minijinja/tests/snapshots/test_parser__parser@escape.txt.snap @@ -15,7 +15,7 @@ Ok( right: Const { value: "foo", } @ 1:9-1:14, - } @ 1:0-1:14, + } @ 1:2-1:14, } @ 1:0-1:14, EmitRaw { raw: "\n", @@ -29,7 +29,7 @@ Ok( right: Const { value: "foo", } @ 2:9-2:14, - } @ 2:0-2:14, + } @ 2:2-2:14, } @ 2:0-2:14, EmitRaw { raw: "\n", diff --git a/minijinja/tests/snapshots/test_parser__parser@for_loop_unpack.txt.snap b/minijinja/tests/snapshots/test_parser__parser@for_loop_unpack.txt.snap index 4209f4cd..44c501ad 100644 --- a/minijinja/tests/snapshots/test_parser__parser@for_loop_unpack.txt.snap +++ b/minijinja/tests/snapshots/test_parser__parser@for_loop_unpack.txt.snap @@ -18,12 +18,12 @@ Ok( id: "b", } @ 1:11-1:12, ], - } @ 1:7-1:12, + } @ 1:8-1:12, Var { id: "c", } @ 1:15-1:16, ], - } @ 1:3-1:16, + } @ 1:7-1:16, iter: Var { id: "seq", } @ 1:20-1:23, diff --git a/minijinja/tests/snapshots/test_parser__parser@getattr.txt.snap b/minijinja/tests/snapshots/test_parser__parser@getattr.txt.snap index 99f6dd9f..a89c0da8 100644 --- a/minijinja/tests/snapshots/test_parser__parser@getattr.txt.snap +++ b/minijinja/tests/snapshots/test_parser__parser@getattr.txt.snap @@ -13,9 +13,9 @@ Ok( id: "foo", } @ 1:3-1:6, name: "bar", - } @ 1:6-1:10, + } @ 1:3-1:10, name: "baz", - } @ 1:10-1:14, + } @ 1:6-1:14, } @ 1:0-1:14, ], } @ 0:0-1:17, diff --git a/minijinja/tests/snapshots/test_parser__parser@getitem.txt.snap b/minijinja/tests/snapshots/test_parser__parser@getitem.txt.snap index 1422956d..4556d5d3 100644 --- a/minijinja/tests/snapshots/test_parser__parser@getitem.txt.snap +++ b/minijinja/tests/snapshots/test_parser__parser@getitem.txt.snap @@ -15,11 +15,11 @@ Ok( subscript_expr: Const { value: "bar", } @ 1:7-1:12, - } @ 1:6-1:13, + } @ 1:3-1:13, subscript_expr: Const { value: 42, } @ 1:14-1:16, - } @ 1:13-1:17, + } @ 1:6-1:17, } @ 1:0-1:17, ], } @ 0:0-1:20, diff --git a/minijinja/tests/snapshots/test_parser__parser@in.txt.snap b/minijinja/tests/snapshots/test_parser__parser@in.txt.snap index 1fe7ff40..5c8120f2 100644 --- a/minijinja/tests/snapshots/test_parser__parser@in.txt.snap +++ b/minijinja/tests/snapshots/test_parser__parser@in.txt.snap @@ -49,7 +49,7 @@ Ok( id: "sequence", } @ 3:16-3:24, } @ 3:3-3:24, - } @ 3:0-3:24, + } @ 3:3-3:24, } @ 3:0-3:24, ], } @ 0:0-3:27, diff --git a/minijinja/tests/snapshots/test_parser__parser@ops.txt.snap b/minijinja/tests/snapshots/test_parser__parser@ops.txt.snap index e0cc1396..c5c9fb62 100644 --- a/minijinja/tests/snapshots/test_parser__parser@ops.txt.snap +++ b/minijinja/tests/snapshots/test_parser__parser@ops.txt.snap @@ -17,7 +17,7 @@ Ok( right: Var { id: "bar", } @ 1:11-1:14, - } @ 1:0-1:14, + } @ 1:3-1:14, right: BinOp { op: Eq, left: Var { @@ -27,7 +27,7 @@ Ok( value: true, } @ 1:25-1:29, } @ 1:15-1:29, - } @ 1:0-1:29, + } @ 1:3-1:29, } @ 1:0-1:29, ], } @ 0:0-1:32, diff --git a/minijinja/tests/snapshots/test_parser__parser@set.txt.snap b/minijinja/tests/snapshots/test_parser__parser@set.txt.snap index c203af57..0650b465 100644 --- a/minijinja/tests/snapshots/test_parser__parser@set.txt.snap +++ b/minijinja/tests/snapshots/test_parser__parser@set.txt.snap @@ -27,7 +27,7 @@ Ok( id: "b", } @ 2:11-2:12, ], - } @ 2:7-2:12, + } @ 2:8-2:12, expr: List { items: [ Const { diff --git a/minijinja/tests/snapshots/test_parser__parser@test.txt.snap b/minijinja/tests/snapshots/test_parser__parser@test.txt.snap index 6ad781ae..21dc30e5 100644 --- a/minijinja/tests/snapshots/test_parser__parser@test.txt.snap +++ b/minijinja/tests/snapshots/test_parser__parser@test.txt.snap @@ -43,7 +43,7 @@ Ok( } @ 3:7-3:10, args: [], } @ 3:14-3:18, - } @ 3:0-3:18, + } @ 3:3-3:18, } @ 3:0-3:18, ], } @ 0:0-3:21, diff --git a/minijinja/tests/snapshots/test_templates__vm@block_super_err.txt.snap b/minijinja/tests/snapshots/test_templates__vm@block_super_err.txt.snap index 771ad56c..e6714d2b 100644 --- a/minijinja/tests/snapshots/test_templates__vm@block_super_err.txt.snap +++ b/minijinja/tests/snapshots/test_templates__vm@block_super_err.txt.snap @@ -13,4 +13,11 @@ Error { line: 1, } +---------------------------- Template Source ----------------------------- + 1 > {{ super() }} +-------------------------------------------------------------------------- +Referenced variables: +-------------------------------------------------------------------------- + + diff --git a/minijinja/tests/snapshots/test_templates__vm@err_bad_addition.txt.snap b/minijinja/tests/snapshots/test_templates__vm@err_bad_addition.txt.snap new file mode 100644 index 00000000..d55f41c7 --- /dev/null +++ b/minijinja/tests/snapshots/test_templates__vm@err_bad_addition.txt.snap @@ -0,0 +1,24 @@ +--- +source: minijinja/tests/test_templates.rs +description: "{{ [1, 2] + 23 }}" +info: {} +input_file: minijinja/tests/inputs/err_bad_addition.txt +--- +!!!ERROR!!! + +Error { + kind: ImpossibleOperation, + detail: "tried to use + operator on unsupported types sequence and number", + name: "err_bad_addition.txt", + line: 1, +} + +---------------------------- Template Source ----------------------------- + 1 > {{ [1, 2] + 23 }} + i ^^^^^^^^^^^ impossible operation +-------------------------------------------------------------------------- +Referenced variables: +-------------------------------------------------------------------------- + + + diff --git a/minijinja/tests/snapshots/test_templates__vm@err_bad_call.txt.snap b/minijinja/tests/snapshots/test_templates__vm@err_bad_call.txt.snap new file mode 100644 index 00000000..e8b4cd35 --- /dev/null +++ b/minijinja/tests/snapshots/test_templates__vm@err_bad_call.txt.snap @@ -0,0 +1,26 @@ +--- +source: minijinja/tests/test_templates.rs +description: "This is {{ an_unknown_function() }}!" +info: {} +input_file: minijinja/tests/inputs/err_bad_call.txt +--- +!!!ERROR!!! + +Error { + kind: UnknownFunction, + detail: "an_unknown_function is unknown", + name: "err_bad_call.txt", + line: 1, +} + +---------------------------- Template Source ----------------------------- + 1 > This is {{ an_unknown_function() }}! + i ^^^^^^^^^^^^^^^^^^^^^ unknown function +-------------------------------------------------------------------------- +Referenced variables: { + an_unknown_function: Undefined, +} +-------------------------------------------------------------------------- + + + diff --git a/minijinja/tests/snapshots/test_templates__vm@err_bad_filter.txt.snap b/minijinja/tests/snapshots/test_templates__vm@err_bad_filter.txt.snap new file mode 100644 index 00000000..10a0351e --- /dev/null +++ b/minijinja/tests/snapshots/test_templates__vm@err_bad_filter.txt.snap @@ -0,0 +1,26 @@ +--- +source: minijinja/tests/test_templates.rs +description: "{% for item in 42|slice(4) %}\n - {{ item }}\n{% endfor %}" +info: {} +input_file: minijinja/tests/inputs/err_bad_filter.txt +--- +!!!ERROR!!! + +Error { + kind: ImpossibleOperation, + detail: "object is not iterable", + name: "err_bad_filter.txt", + line: 1, +} + +---------------------------- Template Source ----------------------------- + 1 > {% for item in 42|slice(4) %} + i ^^^^^^^^ impossible operation + 2 | - {{ item }} + 3 | {% endfor %} +-------------------------------------------------------------------------- +Referenced variables: +-------------------------------------------------------------------------- + + + diff --git a/minijinja/tests/snapshots/test_templates__vm@err_bad_nested_subtraction.txt.snap b/minijinja/tests/snapshots/test_templates__vm@err_bad_nested_subtraction.txt.snap new file mode 100644 index 00000000..cbd66194 --- /dev/null +++ b/minijinja/tests/snapshots/test_templates__vm@err_bad_nested_subtraction.txt.snap @@ -0,0 +1,48 @@ +--- +source: minijinja/tests/test_templates.rs +description: "{% for item in seq %}\n {{ ((item + 4) * (3 - [])) + 4 - 2 }}\n{% endfor %}" +info: + seq: + - 1 + - 2 + - 3 +input_file: minijinja/tests/inputs/err_bad_nested_subtraction.txt +--- +!!!ERROR!!! + +Error { + kind: ImpossibleOperation, + detail: "tried to use - operator on unsupported types number and sequence", + name: "err_bad_nested_subtraction.txt", + line: 2, +} + +---------------------------- Template Source ----------------------------- + 1 | {% for item in seq %} + 2 > {{ ((item + 4) * (3 - [])) + 4 - 2 }} + i ^^^^^^ impossible operation + 3 | {% endfor %} +-------------------------------------------------------------------------- +Referenced variables: { + item: 1, + loop: LoopState { + index0: 0, + index: 1, + length: 3, + revindex: 3, + revindex0: 2, + first: true, + last: false, + depth: 1, + depth0: 0, + }, + seq: [ + 1, + 2, + 3, + ], +} +-------------------------------------------------------------------------- + + + diff --git a/minijinja/tests/snapshots/test_templates__vm@err_bad_test.txt.snap b/minijinja/tests/snapshots/test_templates__vm@err_bad_test.txt.snap new file mode 100644 index 00000000..53cc3955 --- /dev/null +++ b/minijinja/tests/snapshots/test_templates__vm@err_bad_test.txt.snap @@ -0,0 +1,50 @@ +--- +source: minijinja/tests/test_templates.rs +description: "{% for item in seq %}\n {% if item is reallyEven %}\n {{ item }}\n {% endif %}\n{% endfor %}" +info: + seq: + - 1 + - 2 + - 3 +input_file: minijinja/tests/inputs/err_bad_test.txt +--- +!!!ERROR!!! + +Error { + kind: UnknownTest, + detail: "test reallyEven is unknown", + name: "err_bad_test.txt", + line: 2, +} + +---------------------------- Template Source ----------------------------- + 1 | {% for item in seq %} + 2 > {% if item is reallyEven %} + i ^^^^^^^^^^ unknown test + 3 | {{ item }} + 4 | {% endif %} + 5 | {% endfor %} +-------------------------------------------------------------------------- +Referenced variables: { + item: 1, + loop: LoopState { + index0: 0, + index: 1, + length: 3, + revindex: 3, + revindex0: 2, + first: true, + last: false, + depth: 1, + depth0: 0, + }, + seq: [ + 1, + 2, + 3, + ], +} +-------------------------------------------------------------------------- + + + diff --git a/minijinja/tests/snapshots/test_templates__vm@err_bad_test_arguments.txt.snap b/minijinja/tests/snapshots/test_templates__vm@err_bad_test_arguments.txt.snap new file mode 100644 index 00000000..c8faa577 --- /dev/null +++ b/minijinja/tests/snapshots/test_templates__vm@err_bad_test_arguments.txt.snap @@ -0,0 +1,49 @@ +--- +source: minijinja/tests/test_templates.rs +description: "{% for item in seq %}\n {% if item is even(42) %}\n {{ item }}\n {% endif %}\n{% endfor %}" +info: + seq: + - 1 + - 2 + - 3 +input_file: minijinja/tests/inputs/err_bad_test_arguments.txt +--- +!!!ERROR!!! + +Error { + kind: TooManyArguments, + name: "err_bad_test_arguments.txt", + line: 2, +} + +---------------------------- Template Source ----------------------------- + 1 | {% for item in seq %} + 2 > {% if item is even(42) %} + i ^^^^^^^^ too many arguments + 3 | {{ item }} + 4 | {% endif %} + 5 | {% endfor %} +-------------------------------------------------------------------------- +Referenced variables: { + item: 1, + loop: LoopState { + index0: 0, + index: 1, + length: 3, + revindex: 3, + revindex0: 2, + first: true, + last: false, + depth: 1, + depth0: 0, + }, + seq: [ + 1, + 2, + 3, + ], +} +-------------------------------------------------------------------------- + + + diff --git a/minijinja/tests/snapshots/test_templates__vm@err_undefined_attr.txt.snap b/minijinja/tests/snapshots/test_templates__vm@err_undefined_attr.txt.snap index c3e8e3d9..09abe68e 100644 --- a/minijinja/tests/snapshots/test_templates__vm@err_undefined_attr.txt.snap +++ b/minijinja/tests/snapshots/test_templates__vm@err_undefined_attr.txt.snap @@ -12,4 +12,14 @@ Error { line: 1, } +---------------------------- Template Source ----------------------------- + 1 > {{ undefined_value.attr }} + i ^^^^^^^^^^^^^^^^^^^^ undefined value +-------------------------------------------------------------------------- +Referenced variables: { + undefined_value: Undefined, +} +-------------------------------------------------------------------------- + + diff --git a/minijinja/tests/snapshots/test_templates__vm@err_undefined_item.txt.snap b/minijinja/tests/snapshots/test_templates__vm@err_undefined_item.txt.snap new file mode 100644 index 00000000..2b4b6842 --- /dev/null +++ b/minijinja/tests/snapshots/test_templates__vm@err_undefined_item.txt.snap @@ -0,0 +1,33 @@ +--- +source: minijinja/tests/test_templates.rs +description: "{{ seq[42][23] }}" +info: + seq: + - 1 + - 2 + - 3 +input_file: minijinja/tests/inputs/err_undefined_item.txt +--- +!!!ERROR!!! + +Error { + kind: UndefinedError, + name: "err_undefined_item.txt", + line: 1, +} + +---------------------------- Template Source ----------------------------- + 1 > {{ seq[42][23] }} + i ^^^^^^^^ undefined value +-------------------------------------------------------------------------- +Referenced variables: { + seq: [ + 1, + 2, + 3, + ], +} +-------------------------------------------------------------------------- + + + diff --git a/minijinja/tests/snapshots/test_templates__vm@err_undefined_nested_attr.txt.snap b/minijinja/tests/snapshots/test_templates__vm@err_undefined_nested_attr.txt.snap new file mode 100644 index 00000000..ece3e7c2 --- /dev/null +++ b/minijinja/tests/snapshots/test_templates__vm@err_undefined_nested_attr.txt.snap @@ -0,0 +1,34 @@ +--- +source: minijinja/tests/test_templates.rs +description: "{{ seq.whatever }}\n{{ seq.whatever.else }}" +info: + seq: + - 1 + - 2 + - 3 +input_file: minijinja/tests/inputs/err_undefined_nested_attr.txt +--- +!!!ERROR!!! + +Error { + kind: UndefinedError, + name: "err_undefined_nested_attr.txt", + line: 2, +} + +---------------------------- Template Source ----------------------------- + 1 | {{ seq.whatever }} + 2 > {{ seq.whatever.else }} + i ^^^^^^^^^^^^^^ undefined value +-------------------------------------------------------------------------- +Referenced variables: { + seq: [ + 1, + 2, + 3, + ], +} +-------------------------------------------------------------------------- + + + diff --git a/minijinja/tests/snapshots/test_templates__vm@include_choice_none.txt.snap b/minijinja/tests/snapshots/test_templates__vm@include_choice_none.txt.snap index 2c60ba4f..002e923c 100644 --- a/minijinja/tests/snapshots/test_templates__vm@include_choice_none.txt.snap +++ b/minijinja/tests/snapshots/test_templates__vm@include_choice_none.txt.snap @@ -13,4 +13,13 @@ Error { line: 2, } +---------------------------- Template Source ----------------------------- + 1 | Before + 2 > {% include ["missing_template1.txt", "missing_template2.txt"] %} + 3 | After +-------------------------------------------------------------------------- +Referenced variables: +-------------------------------------------------------------------------- + + diff --git a/minijinja/tests/snapshots/test_templates__vm@include_missing.txt.snap b/minijinja/tests/snapshots/test_templates__vm@include_missing.txt.snap index da8dea89..20f518dd 100644 --- a/minijinja/tests/snapshots/test_templates__vm@include_missing.txt.snap +++ b/minijinja/tests/snapshots/test_templates__vm@include_missing.txt.snap @@ -14,4 +14,15 @@ Error { line: 2, } +---------------------------- Template Source ----------------------------- + 1 | Before + 2 > {% include template %} + 3 | After +-------------------------------------------------------------------------- +Referenced variables: { + template: "missing_template.txt", +} +-------------------------------------------------------------------------- + + diff --git a/minijinja/tests/snapshots/test_templates__vm@loop_bad_unpacking.txt.snap b/minijinja/tests/snapshots/test_templates__vm@loop_bad_unpacking.txt.snap index ae7add36..4825ee61 100644 --- a/minijinja/tests/snapshots/test_templates__vm@loop_bad_unpacking.txt.snap +++ b/minijinja/tests/snapshots/test_templates__vm@loop_bad_unpacking.txt.snap @@ -21,4 +21,33 @@ Error { }, } +---------------------------- Template Source ----------------------------- + 1 |
    + 2 > {% for a, b in seq %} + i ^^^^ impossible operation + 3 |
  • {{ a }}: {{ b }} + 4 | {% endfor %} + 5 |
+-------------------------------------------------------------------------- +Referenced variables: { + loop: LoopState { + index0: 0, + index: 1, + length: 3, + revindex: 3, + revindex0: 2, + first: true, + last: false, + depth: 1, + depth0: 0, + }, + seq: [ + 1, + 2, + 3, + ], +} +-------------------------------------------------------------------------- + + diff --git a/minijinja/tests/snapshots/test_templates__vm@loop_bad_unpacking_wrong_len.txt.snap b/minijinja/tests/snapshots/test_templates__vm@loop_bad_unpacking_wrong_len.txt.snap index 39662a09..07605730 100644 --- a/minijinja/tests/snapshots/test_templates__vm@loop_bad_unpacking_wrong_len.txt.snap +++ b/minijinja/tests/snapshots/test_templates__vm@loop_bad_unpacking_wrong_len.txt.snap @@ -23,4 +23,45 @@ Error { line: 2, } +---------------------------- Template Source ----------------------------- + 1 |
    + 2 > {% for a, b in seq %} + i ^^^^ impossible operation + 3 |
  • {{ a }}: {{ b }} + 4 | {% endfor %} + 5 |
+-------------------------------------------------------------------------- +Referenced variables: { + loop: LoopState { + index0: 0, + index: 1, + length: 3, + revindex: 3, + revindex0: 2, + first: true, + last: false, + depth: 1, + depth0: 0, + }, + seq: [ + [ + 1, + 2, + 3, + ], + [ + 2, + 3, + 4, + ], + [ + 3, + 4, + 5, + ], + ], +} +-------------------------------------------------------------------------- + + diff --git a/minijinja/tests/snapshots/test_templates__vm@loop_over_non_iterable.txt.snap b/minijinja/tests/snapshots/test_templates__vm@loop_over_non_iterable.txt.snap index 448d5f2a..7dcacb12 100644 --- a/minijinja/tests/snapshots/test_templates__vm@loop_over_non_iterable.txt.snap +++ b/minijinja/tests/snapshots/test_templates__vm@loop_over_non_iterable.txt.snap @@ -14,4 +14,14 @@ Error { line: 1, } +---------------------------- Template Source ----------------------------- + 1 > [{% for item in seq %}{% endfor %}] +-------------------------------------------------------------------------- +Referenced variables: { + loop: Undefined, + seq: 42, +} +-------------------------------------------------------------------------- + + diff --git a/minijinja/tests/test_templates.rs b/minijinja/tests/test_templates.rs index fa6789e9..e2cf9947 100644 --- a/minijinja/tests/test_templates.rs +++ b/minijinja/tests/test_templates.rs @@ -27,10 +27,6 @@ fn test_vm() { let contents = std::fs::read_to_string(path).unwrap(); let mut iter = contents.splitn(2, "\n---\n"); let mut env = Environment::new(); - #[cfg(feature = "debug")] - { - env.set_debug(false); - } let ctx: serde_json::Value = serde_json::from_str(iter.next().unwrap()).unwrap(); for (path, source) in &refs {