Repository: mitsuhiko/minijinja
Pull request #118 (2022-09-16 22:28:01)

Implement basic span error reporting
Some operations now report the spans of where the error happened.

Example error in debug format:

```
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 %}
--------------------------------------------------------------------------
```


Patch:
diff --git a/Makefile b/Makefile
index e39087d..87a215f 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 192ba4a..31f0800 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::<Vec<_>>(),
+        other_seq => (0..5).collect::<Vec<_>>(),
         bar => "test"
     };
     match template.render(&ctx) {
diff --git a/minijinja/src/compiler/codegen.rs b/minijinja/src/compiler/codegen.rs
index f157f43..545ea53 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<PendingBlock>,
     current_line: usize,
+    span_stack: Vec<Span>,
 }
 
 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<ast::Call<'source>>) -> 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<ast::BinOp<'source>>) -> 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 85a8e5e..462c8a3 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<Span>,
+}
+
 /// Wrapper around instructions to help with location management.
 pub struct Instructions<'source> {
     pub(crate) instructions: Vec<Instruction<'source>>,
-    locations: Vec<Loc>,
+    line_infos: Vec<LineInfo>,
+    #[cfg(feature = "debug")]
+    span_infos: Vec<SpanInfo>,
     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<usize> {
         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<Span> {
+        #[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 4c7c06a..87e4bdc 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<dyn Iterator<Item = Result<(Token<'a>, Span), Error>> + 'a>,
     current: Option<Result<(Token<'a>, 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<dyn Iterator<Item = _>>),
             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<ast::Expr<'a>, 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<ast::Expr<'a>, 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<ast::Expr<'a>, 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<ast::Expr<'a>, Error> {
+    fn parse_postfix(
+        &mut self,
+        expr: ast::Expr<'a>,
+        mut span: Span,
+    ) -> Result<ast::Expr<'a>, 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<ast::Stmt<'a>, 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<ast::Expr<'_>, Error> {
     let mut parser = Parser::new(source, true);
     parser.parse_expr().map_err(|mut err| {
         if err.line().is_none() {
-            err.set_location("<expression>", parser.stream.current_span().start_line)
+            err.set_filename_and_span("<expression>", parser.stream.last_span())
         }
         err
     })
diff --git a/minijinja/src/compiler/tokens.rs b/minijinja/src/compiler/tokens.rs
index 997f3c9..cdaa96d 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 f782b58..c826bc8 100644
--- a/minijinja/src/error.rs
+++ b/minijinja/src/error.rs
@@ -28,6 +28,7 @@ pub struct Error {
     detail: Option<Cow<'static, str>>,
     name: Option<String>,
     lineno: usize,
+    span: Option<Span>,
     source: Option<Box<dyn std::error::Error + Send + Sync>>,
     #[cfg(feature = "debug")]
     pub(crate) debug_info: Option<DebugInfo>,
@@ -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<ErrorKind> 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<usize>,
+        span: Option<Span>,
         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<T>(rv: Result<T, Error>, 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 c384cad..1660ec4 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")]
     {


Test patch:
diff --git a/minijinja/tests/inputs/err_bad_addition.txt b/minijinja/tests/inputs/err_bad_addition.txt
new file mode 100644
index 0000000..566f2ea
--- /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 0000000..a777efc
--- /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 0000000..7e56f2a
--- /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 0000000..58e1f55
--- /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 0000000..841f8a0
--- /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 0000000..73844df
--- /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 0000000..cf3b3b5
--- /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 0000000..edb2266
--- /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 3641cf9..c2676ab 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 1e8ec7a..774c218 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 e73deeb..102f1a5 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 654e16c..aa7458d 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 518f91f..b0a25f1 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 9247b7c..81314e9 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 4209f4c..44c501a 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 99f6dd9..a89c0da 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 1422956..4556d5d 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 1fe7ff4..5c8120f 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 e0cc139..c5c9fb6 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 c203af5..0650b46 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 6ad781a..21dc30e 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 771ad56..e6714d2 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 0000000..d55f41c
--- /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 0000000..e8b4cd3
--- /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 0000000..10a0351
--- /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 0000000..cbd6619
--- /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 0000000..53cc395
--- /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 0000000..c8faa57
--- /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 c3e8e3d..09abe68 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 0000000..2b4b684
--- /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 0000000..ece3e7c
--- /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 2c60ba4..002e923 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 da8dea8..20f518d 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 ae7add3..4825ee6 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 | <ul>
+   2 > {% for a, b in seq %}
+     i        ^^^^ impossible operation
+   3 |   <li>{{ a }}: {{ b }}
+   4 | {% endfor %}
+   5 | </ul>
+--------------------------------------------------------------------------
+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 39662a0..0760573 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 | <ul>
+   2 > {% for a, b in seq %}
+     i        ^^^^ impossible operation
+   3 |   <li>{{ a }}: {{ b }}
+   4 | {% endfor %}
+   5 | </ul>
+--------------------------------------------------------------------------
+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 448d5f2..7dcacb1 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 fa6789e..e2cf994 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 {