Repository: djc/askama
Pull request #986 (2024-03-11 17:01:42)

Improve error message by providing the file where the error occurred if it is not an inlined source
When you have templates included in templates, the errors become quite difficult to understand as we don't know which file is triggering it. With this change, it adds the path of the file where the error is located:

```
error: problems parsing template source in `templates/core/about/builds.html` at row 34, column 32 near:
       "=\"rust\") %}{% filter dedent(levels=3) -%"...
   --> src/web/sitemap.rs:109:10
    |
109 | #[derive(Template)]
    |          ^^^^^^^^
    |
    = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
```

Making my life and very likely everyone else's better. :)

Sadly, I can't enforce it with `trybuild` as it only allows to have inline tests (because the folder where the ui tests are run is different and I couldn't find a way to do it...).

GuillaumeGomez: Finally made tests work on CI. \o/

I added a ui test as well (took me more time than I expected...).
GuillaumeGomez: Updated!
GuillaumeGomez: Rebased on #994.
GuillaumeGomez: Thanks for the reviews! Merging then. :)

Patch:
diff --git a/askama_derive/src/input.rs b/askama_derive/src/input.rs
index f5b8e13..0c8bda2 100644
--- a/askama_derive/src/input.rs
+++ b/askama_derive/src/input.rs
@@ -107,15 +107,18 @@ impl TemplateInput<'_> {
         &self,
         map: &mut HashMap<Rc<Path>, Parsed>,
     ) -> Result<(), CompileError> {
-        let source = match &self.source {
-            Source::Source(s) => s.clone(),
-            Source::Path(_) => get_template_source(&self.path)?,
+        let (source, source_path) = match &self.source {
+            Source::Source(s) => (s.into(), None),
+            Source::Path(_) => (
+                get_template_source(&self.path)?,
+                Some(Rc::clone(&self.path)),
+            ),
         };
 
         let mut dependency_graph = Vec::new();
-        let mut check = vec![(self.path.clone(), source)];
-        while let Some((path, source)) = check.pop() {
-            let parsed = Parsed::new(source, self.syntax)?;
+        let mut check = vec![(Rc::clone(&self.path), source, source_path)];
+        while let Some((path, source, source_path)) = check.pop() {
+            let parsed = Parsed::new(source, source_path, self.syntax)?;
 
             let mut top = true;
             let mut nested = vec![parsed.nodes()];
@@ -125,9 +128,9 @@ impl TemplateInput<'_> {
                         if !map.contains_key(&path) {
                             // Add a dummy entry to `map` in order to prevent adding `path`
                             // multiple times to `check`.
-                            map.insert(path.clone(), Parsed::default());
+                            map.insert(Rc::clone(&path), Parsed::default());
                             let source = get_template_source(&path)?;
-                            check.push((path, source));
+                            check.push((path.clone(), source, Some(path)));
                         }
                         Ok(())
                     };
diff --git a/askama_parser/fuzz/fuzz_targets/fuzz_parser.rs b/askama_parser/fuzz/fuzz_targets/fuzz_parser.rs
index 1be1f94..7fbf1ad 100644
--- a/askama_parser/fuzz/fuzz_targets/fuzz_parser.rs
+++ b/askama_parser/fuzz/fuzz_targets/fuzz_parser.rs
@@ -6,6 +6,6 @@ use std::str;
 fuzz_target!(|data: &[u8]| {
     // fuzzed code goes here
     if let Ok(data) = str::from_utf8(data) {
-        if let Ok(_) = Ast::from_str(data, &Syntax::default()) {}
+        if let Ok(_) = Ast::from_str(data, None, &Syntax::default()) {}
     }
 });
diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs
index 9d6c9ac..43b604d 100644
--- a/askama_parser/src/lib.rs
+++ b/askama_parser/src/lib.rs
@@ -3,6 +3,8 @@
 
 use std::borrow::Cow;
 use std::cell::Cell;
+use std::path::Path;
+use std::rc::Rc;
 use std::{fmt, str};
 
 use nom::branch::alt;
@@ -22,6 +24,8 @@ pub use node::Node;
 mod tests;
 
 mod _parsed {
+    use std::path::Path;
+    use std::rc::Rc;
     use std::{fmt, mem};
 
     use super::node::Node;
@@ -36,12 +40,18 @@ mod _parsed {
     }
 
     impl Parsed {
-        pub fn new(source: String, syntax: &Syntax<'_>) -> Result<Self, ParseError> {
+        /// If `file_path` is `None`, it means the `source` is an inline template. Therefore, if
+        /// a parsing error occurs, we won't display the path as it wouldn't be useful.
+        pub fn new(
+            source: String,
+            file_path: Option<Rc<Path>>,
+            syntax: &Syntax<'_>,
+        ) -> Result<Self, ParseError> {
             // Self-referential borrowing: `self` will keep the source alive as `String`,
             // internally we will transmute it to `&'static str` to satisfy the compiler.
             // However, we only expose the nodes with a lifetime limited to `self`.
             let src = unsafe { mem::transmute::<&str, &'static str>(source.as_str()) };
-            let ast = Ast::from_str(src, syntax)?;
+            let ast = Ast::from_str(src, file_path, syntax)?;
             Ok(Self { ast, source })
         }
 
@@ -74,7 +84,13 @@ pub struct Ast<'a> {
 }
 
 impl<'a> Ast<'a> {
-    pub fn from_str(src: &'a str, syntax: &Syntax<'_>) -> Result<Self, ParseError> {
+    /// If `file_path` is `None`, it means the `source` is an inline template. Therefore, if
+    /// a parsing error occurs, we won't display the path as it wouldn't be useful.
+    pub fn from_str(
+        src: &'a str,
+        file_path: Option<Rc<Path>>,
+        syntax: &Syntax<'_>,
+    ) -> Result<Self, ParseError> {
         let parse = |i: &'a str| Node::many(i, &State::new(syntax));
         let (input, message) = match terminated(parse, cut(eof))(src) {
             Ok(("", nodes)) => return Ok(Self { nodes }),
@@ -97,19 +113,27 @@ impl<'a> Ast<'a> {
         let (row, last_line) = source_before.lines().enumerate().last().unwrap_or_default();
         let column = last_line.chars().count();
 
-        let msg = format!(
-            "{}problems parsing template source at row {}, column {} near:\n{}",
-            if let Some(message) = message {
-                format!("{message}\n")
-            } else {
-                String::new()
-            },
-            row + 1,
-            column,
-            source_after,
-        );
+        let file_info = file_path.and_then(|file_path| {
+            let cwd = std::env::current_dir().ok()?;
+            Some((cwd, file_path))
+        });
+        let message = message
+            .map(|message| format!("{message}\n"))
+            .unwrap_or_default();
+        let error_msg = if let Some((cwd, file_path)) = file_info {
+            format!(
+                "{message}failed to parse template source\n  --> {path}:{row}:{column}\n{source_after}",
+                path = strip_common(&cwd, &file_path),
+                row = row + 1,
+            )
+        } else {
+            format!(
+                "{message}failed to parse template source at row {}, column {column} near:\n{source_after}",
+                row + 1,
+            )
+        };
 
-        Err(ParseError(msg))
+        Err(ParseError(error_msg))
     }
 
     pub fn nodes(&self) -> &[Node<'a>] {
@@ -487,3 +511,77 @@ fn filter(i: &str, level: Level) -> ParseResult<'_, (&str, Option<Vec<Expr<'_>>>
     ))(i)?;
     Ok((i, (fname, args)))
 }
+
+/// Returns the common parts of two paths.
+///
+/// The goal of this function is to reduce the path length based on the `base` argument
+/// (generally the path where the program is running into). For example:
+///
+/// ```text
+/// current dir: /a/b/c
+/// path:        /a/b/c/d/e.txt
+/// ```
+///
+/// `strip_common` will return `d/e.txt`.
+fn strip_common(base: &Path, path: &Path) -> String {
+    let path = match path.canonicalize() {
+        Ok(path) => path,
+        Err(_) => return path.display().to_string(),
+    };
+    let mut components_iter = path.components().peekable();
+
+    for current_path_component in base.components() {
+        let Some(path_component) = components_iter.peek() else {
+            return path.display().to_string();
+        };
+        if current_path_component != *path_component {
+            break;
+        }
+        components_iter.next();
+    }
+    let path_parts = components_iter
+        .map(|c| c.as_os_str().to_string_lossy())
+        .collect::<Vec<_>>();
+    if path_parts.is_empty() {
+        path.display().to_string()
+    } else {
+        path_parts.join("/")
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::strip_common;
+    use std::path::Path;
+
+    #[test]
+    #[cfg(not(windows))]
+    fn test_strip_common() {
+        let cwd = std::env::current_dir().expect("current_dir failed");
+
+        // We need actual existing paths for `canonicalize` to work, so let's do that.
+        let entry = cwd
+            .read_dir()
+            .expect("read_dir failed")
+            .filter_map(|f| f.ok())
+            .find(|f| f.path().is_file())
+            .expect("no entry");
+
+        // Since they have the complete path in common except for the folder entry name, it should
+        // return only the folder entry name.
+        assert_eq!(
+            strip_common(&cwd, &entry.path()),
+            entry.file_name().to_string_lossy()
+        );
+
+        #[cfg(target_os = "linux")]
+        {
+            // They both start with `/home` (normally), making the path empty, so in this case,
+            // the whole path should be returned.
+            assert_eq!(strip_common(&cwd, Path::new("/home")), "/home");
+        }
+
+        // In this case it cannot canonicalize `/a/b/c` so it returns the path as is.
+        assert_eq!(strip_common(&cwd, Path::new("/a/b/c")), "/a/b/c");
+    }
+}


Test patch:
diff --git a/askama_parser/src/tests.rs b/askama_parser/src/tests.rs
index 669e376..5b96161 100644
--- a/askama_parser/src/tests.rs
+++ b/askama_parser/src/tests.rs
@@ -20,14 +20,16 @@ fn test_ws_splitter() {
 #[test]
 #[should_panic]
 fn test_invalid_block() {
-    Ast::from_str("{% extend \"blah\" %}", &Syntax::default()).unwrap();
+    Ast::from_str("{% extend \"blah\" %}", None, &Syntax::default()).unwrap();
 }
 
 #[test]
 fn test_parse_filter() {
     let syntax = Syntax::default();
     assert_eq!(
-        Ast::from_str("{{ strvar|e }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ strvar|e }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Filter(Filter {
@@ -37,7 +39,7 @@ fn test_parse_filter() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ 2|abs }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ 2|abs }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Filter(Filter {
@@ -47,7 +49,7 @@ fn test_parse_filter() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ -2|abs }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ -2|abs }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Filter(Filter {
@@ -57,7 +59,9 @@ fn test_parse_filter() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (1 - 2)|abs }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1 - 2)|abs }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Filter(Filter {
@@ -74,11 +78,11 @@ fn test_parse_filter() {
 fn test_parse_numbers() {
     let syntax = Syntax::default();
     assert_eq!(
-        Ast::from_str("{{ 2 }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ 2 }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(Ws(None, None), Expr::NumLit("2"),)],
     );
     assert_eq!(
-        Ast::from_str("{{ 2.5 }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ 2.5 }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(Ws(None, None), Expr::NumLit("2.5"),)],
     );
 }
@@ -88,16 +92,16 @@ fn test_parse_var() {
     let s = Syntax::default();
 
     assert_eq!(
-        Ast::from_str("{{ foo }}", &s).unwrap().nodes,
+        Ast::from_str("{{ foo }}", None, &s).unwrap().nodes,
         vec![Node::Expr(Ws(None, None), Expr::Var("foo"))],
     );
     assert_eq!(
-        Ast::from_str("{{ foo_bar }}", &s).unwrap().nodes,
+        Ast::from_str("{{ foo_bar }}", None, &s).unwrap().nodes,
         vec![Node::Expr(Ws(None, None), Expr::Var("foo_bar"))],
     );
 
     assert_eq!(
-        Ast::from_str("{{ none }}", &s).unwrap().nodes,
+        Ast::from_str("{{ none }}", None, &s).unwrap().nodes,
         vec![Node::Expr(Ws(None, None), Expr::Var("none"))],
     );
 }
@@ -107,16 +111,16 @@ fn test_parse_const() {
     let s = Syntax::default();
 
     assert_eq!(
-        Ast::from_str("{{ FOO }}", &s).unwrap().nodes,
+        Ast::from_str("{{ FOO }}", None, &s).unwrap().nodes,
         vec![Node::Expr(Ws(None, None), Expr::Path(vec!["FOO"]))],
     );
     assert_eq!(
-        Ast::from_str("{{ FOO_BAR }}", &s).unwrap().nodes,
+        Ast::from_str("{{ FOO_BAR }}", None, &s).unwrap().nodes,
         vec![Node::Expr(Ws(None, None), Expr::Path(vec!["FOO_BAR"]))],
     );
 
     assert_eq!(
-        Ast::from_str("{{ NONE }}", &s).unwrap().nodes,
+        Ast::from_str("{{ NONE }}", None, &s).unwrap().nodes,
         vec![Node::Expr(Ws(None, None), Expr::Path(vec!["NONE"]))],
     );
 }
@@ -126,11 +130,11 @@ fn test_parse_path() {
     let s = Syntax::default();
 
     assert_eq!(
-        Ast::from_str("{{ None }}", &s).unwrap().nodes,
+        Ast::from_str("{{ None }}", None, &s).unwrap().nodes,
         vec![Node::Expr(Ws(None, None), Expr::Path(vec!["None"]))],
     );
     assert_eq!(
-        Ast::from_str("{{ Some(123) }}", &s).unwrap().nodes,
+        Ast::from_str("{{ Some(123) }}", None, &s).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Call(
@@ -141,14 +145,14 @@ fn test_parse_path() {
     );
 
     assert_eq!(
-        Ast::from_str("{{ Ok(123) }}", &s).unwrap().nodes,
+        Ast::from_str("{{ Ok(123) }}", None, &s).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Call(Box::new(Expr::Path(vec!["Ok"])), vec![Expr::NumLit("123")]),
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ Err(123) }}", &s).unwrap().nodes,
+        Ast::from_str("{{ Err(123) }}", None, &s).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Call(Box::new(Expr::Path(vec!["Err"])), vec![Expr::NumLit("123")]),
@@ -159,7 +163,7 @@ fn test_parse_path() {
 #[test]
 fn test_parse_var_call() {
     assert_eq!(
-        Ast::from_str("{{ function(\"123\", 3) }}", &Syntax::default())
+        Ast::from_str("{{ function(\"123\", 3) }}", None, &Syntax::default())
             .unwrap()
             .nodes,
         vec![Node::Expr(
@@ -177,14 +181,16 @@ fn test_parse_path_call() {
     let s = Syntax::default();
 
     assert_eq!(
-        Ast::from_str("{{ Option::None }}", &s).unwrap().nodes,
+        Ast::from_str("{{ Option::None }}", None, &s).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Path(vec!["Option", "None"])
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ Option::Some(123) }}", &s).unwrap().nodes,
+        Ast::from_str("{{ Option::Some(123) }}", None, &s)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Call(
@@ -195,7 +201,7 @@ fn test_parse_path_call() {
     );
 
     assert_eq!(
-        Ast::from_str("{{ self::function(\"123\", 3) }}", &s)
+        Ast::from_str("{{ self::function(\"123\", 3) }}", None, &s)
             .unwrap()
             .nodes,
         vec![Node::Expr(
@@ -212,7 +218,7 @@ fn test_parse_path_call() {
 fn test_parse_root_path() {
     let syntax = Syntax::default();
     assert_eq!(
-        Ast::from_str("{{ std::string::String::new() }}", &syntax)
+        Ast::from_str("{{ std::string::String::new() }}", None, &syntax)
             .unwrap()
             .nodes,
         vec![Node::Expr(
@@ -224,7 +230,7 @@ fn test_parse_root_path() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ ::std::string::String::new() }}", &syntax)
+        Ast::from_str("{{ ::std::string::String::new() }}", None, &syntax)
             .unwrap()
             .nodes,
         vec![Node::Expr(
@@ -241,14 +247,16 @@ fn test_parse_root_path() {
 fn test_rust_macro() {
     let syntax = Syntax::default();
     assert_eq!(
-        Ast::from_str("{{ vec!(1, 2, 3) }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ vec!(1, 2, 3) }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::RustMacro(vec!["vec"], "1, 2, 3",),
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ alloc::vec!(1, 2, 3) }}", &syntax)
+        Ast::from_str("{{ alloc::vec!(1, 2, 3) }}", None, &syntax)
             .unwrap()
             .nodes,
         vec![Node::Expr(
@@ -257,30 +265,30 @@ fn test_rust_macro() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{a!()}}", &syntax).unwrap().nodes,
+        Ast::from_str("{{a!()}}", None, &syntax).unwrap().nodes,
         [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["a"], ""))],
     );
     assert_eq!(
-        Ast::from_str("{{a !()}}", &syntax).unwrap().nodes,
+        Ast::from_str("{{a !()}}", None, &syntax).unwrap().nodes,
         [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["a"], ""))],
     );
     assert_eq!(
-        Ast::from_str("{{a! ()}}", &syntax).unwrap().nodes,
+        Ast::from_str("{{a! ()}}", None, &syntax).unwrap().nodes,
         [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["a"], ""))],
     );
     assert_eq!(
-        Ast::from_str("{{a ! ()}}", &syntax).unwrap().nodes,
+        Ast::from_str("{{a ! ()}}", None, &syntax).unwrap().nodes,
         [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["a"], ""))],
     );
     assert_eq!(
-        Ast::from_str("{{A!()}}", &syntax).unwrap().nodes,
+        Ast::from_str("{{A!()}}", None, &syntax).unwrap().nodes,
         [Node::Expr(Ws(None, None), Expr::RustMacro(vec!["A"], ""),)],
     );
     assert_eq!(
-        &*Ast::from_str("{{a.b.c!( hello )}}", &syntax)
+        &*Ast::from_str("{{a.b.c!( hello )}}", None, &syntax)
             .unwrap_err()
             .to_string(),
-        "problems parsing template source at row 1, column 7 near:\n\"!( hello )}}\"",
+        "failed to parse template source at row 1, column 7 near:\n\"!( hello )}}\"",
     );
 }
 
@@ -292,14 +300,16 @@ fn change_delimiters_parse_filter() {
         ..Syntax::default()
     };
 
-    Ast::from_str("{= strvar|e =}", &syntax).unwrap();
+    Ast::from_str("{= strvar|e =}", None, &syntax).unwrap();
 }
 
 #[test]
 fn test_precedence() {
     let syntax = Syntax::default();
     assert_eq!(
-        Ast::from_str("{{ a + b == c }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ a + b == c }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::BinOp(
@@ -310,7 +320,7 @@ fn test_precedence() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ a + b * c - d / e }}", &syntax)
+        Ast::from_str("{{ a + b * c - d / e }}", None, &syntax)
             .unwrap()
             .nodes,
         vec![Node::Expr(
@@ -328,7 +338,7 @@ fn test_precedence() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ a * (b + c) / -d }}", &syntax)
+        Ast::from_str("{{ a * (b + c) / -d }}", None, &syntax)
             .unwrap()
             .nodes,
         vec![Node::Expr(
@@ -349,7 +359,7 @@ fn test_precedence() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ a || b && c || d && e }}", &syntax)
+        Ast::from_str("{{ a || b && c || d && e }}", None, &syntax)
             .unwrap()
             .nodes,
         vec![Node::Expr(
@@ -372,7 +382,9 @@ fn test_precedence() {
 fn test_associativity() {
     let syntax = Syntax::default();
     assert_eq!(
-        Ast::from_str("{{ a + b + c }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ a + b + c }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::BinOp(
@@ -383,7 +395,9 @@ fn test_associativity() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ a * b * c }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ a * b * c }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::BinOp(
@@ -394,7 +408,9 @@ fn test_associativity() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ a && b && c }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ a && b && c }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::BinOp(
@@ -405,7 +421,9 @@ fn test_associativity() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ a + b - c + d }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ a + b - c + d }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::BinOp(
@@ -421,7 +439,7 @@ fn test_associativity() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ a == b != c > d > e == f }}", &syntax)
+        Ast::from_str("{{ a == b != c > d > e == f }}", None, &syntax)
             .unwrap()
             .nodes,
         vec![Node::Expr(
@@ -454,7 +472,7 @@ fn test_associativity() {
 fn test_odd_calls() {
     let syntax = Syntax::default();
     assert_eq!(
-        Ast::from_str("{{ a[b](c) }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ a[b](c) }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Call(
@@ -467,7 +485,9 @@ fn test_odd_calls() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (a + b)(c) }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (a + b)(c) }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Call(
@@ -481,7 +501,9 @@ fn test_odd_calls() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ a + b(c) }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ a + b(c) }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::BinOp(
@@ -492,7 +514,7 @@ fn test_odd_calls() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (-a)(b) }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (-a)(b) }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Call(
@@ -505,7 +527,7 @@ fn test_odd_calls() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ -a(b) }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ -a(b) }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Unary(
@@ -515,7 +537,7 @@ fn test_odd_calls() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ a(b)|c }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ a(b)|c }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Filter(Filter {
@@ -525,7 +547,7 @@ fn test_odd_calls() {
         )]
     );
     assert_eq!(
-        Ast::from_str("{{ a(b)| c }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ a(b)| c }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Filter(Filter {
@@ -535,7 +557,7 @@ fn test_odd_calls() {
         )]
     );
     assert_eq!(
-        Ast::from_str("{{ a(b) |c }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ a(b) |c }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::BinOp(
@@ -551,7 +573,7 @@ fn test_odd_calls() {
 fn test_parse_comments() {
     fn one_comment_ws(source: &str, ws: Ws) {
         let s = &Syntax::default();
-        let mut nodes = Ast::from_str(source, s).unwrap().nodes;
+        let mut nodes = Ast::from_str(source, None, s).unwrap().nodes;
         assert_eq!(nodes.len(), 1, "expected to parse one node");
         match nodes.pop().unwrap() {
             Node::Comment(comment) => assert_eq!(comment.ws, ws),
@@ -610,60 +632,62 @@ fn test_parse_comments() {
 fn test_parse_tuple() {
     let syntax = Syntax::default();
     assert_eq!(
-        Ast::from_str("{{ () }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ () }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(Ws(None, None), Expr::Tuple(vec![]),)],
     );
     assert_eq!(
-        Ast::from_str("{{ (1) }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1) }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Group(Box::new(Expr::NumLit("1"))),
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (1,) }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1,) }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Tuple(vec![Expr::NumLit("1")]),
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (1, ) }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1, ) }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Tuple(vec![Expr::NumLit("1")]),
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (1 ,) }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1 ,) }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Tuple(vec![Expr::NumLit("1")]),
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (1 , ) }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1 , ) }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Tuple(vec![Expr::NumLit("1")]),
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (1, 2) }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1, 2) }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Tuple(vec![Expr::NumLit("1"), Expr::NumLit("2")]),
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (1, 2,) }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1, 2,) }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Tuple(vec![Expr::NumLit("1"), Expr::NumLit("2")]),
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (1, 2, 3) }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1, 2, 3) }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Tuple(vec![
@@ -674,7 +698,7 @@ fn test_parse_tuple() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ ()|abs }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ ()|abs }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Filter(Filter {
@@ -684,7 +708,9 @@ fn test_parse_tuple() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ () | abs }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ () | abs }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::BinOp(
@@ -695,7 +721,7 @@ fn test_parse_tuple() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (1)|abs }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1)|abs }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Filter(Filter {
@@ -705,7 +731,9 @@ fn test_parse_tuple() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (1) | abs }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1) | abs }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::BinOp(
@@ -716,7 +744,9 @@ fn test_parse_tuple() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (1,)|abs }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1,)|abs }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Filter(Filter {
@@ -726,7 +756,9 @@ fn test_parse_tuple() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (1,) | abs }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1,) | abs }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::BinOp(
@@ -737,7 +769,9 @@ fn test_parse_tuple() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (1, 2)|abs }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1, 2)|abs }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Filter(Filter {
@@ -747,7 +781,9 @@ fn test_parse_tuple() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ (1, 2) | abs }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ (1, 2) | abs }}", None, &syntax)
+            .unwrap()
+            .nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::BinOp(
@@ -762,10 +798,10 @@ fn test_parse_tuple() {
 #[test]
 fn test_missing_space_after_kw() {
     let syntax = Syntax::default();
-    let err = Ast::from_str("{%leta=b%}", &syntax).unwrap_err();
+    let err = Ast::from_str("{%leta=b%}", None, &syntax).unwrap_err();
     assert!(matches!(
         &*err.to_string(),
-        "problems parsing template source at row 1, column 0 near:\n\"{%leta=b%}\"",
+        "failed to parse template source at row 1, column 0 near:\n\"{%leta=b%}\"",
     ));
 }
 
@@ -773,60 +809,60 @@ fn test_missing_space_after_kw() {
 fn test_parse_array() {
     let syntax = Syntax::default();
     assert_eq!(
-        Ast::from_str("{{ [] }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ [] }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(Ws(None, None), Expr::Array(vec![]))],
     );
     assert_eq!(
-        Ast::from_str("{{ [1] }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ [1] }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Array(vec![Expr::NumLit("1")])
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ [ 1] }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ [ 1] }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Array(vec![Expr::NumLit("1")])
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ [1 ] }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ [1 ] }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Array(vec![Expr::NumLit("1")])
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ [1,2] }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ [1,2] }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Array(vec![Expr::NumLit("1"), Expr::NumLit("2")])
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ [1 ,2] }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ [1 ,2] }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Array(vec![Expr::NumLit("1"), Expr::NumLit("2")])
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ [1, 2] }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ [1, 2] }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Array(vec![Expr::NumLit("1"), Expr::NumLit("2")])
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ [1,2 ] }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ [1,2 ] }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Array(vec![Expr::NumLit("1"), Expr::NumLit("2")])
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ []|foo }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ []|foo }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Filter(Filter {
@@ -836,7 +872,7 @@ fn test_parse_array() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ []| foo }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ []| foo }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::Filter(Filter {
@@ -846,7 +882,7 @@ fn test_parse_array() {
         )],
     );
     assert_eq!(
-        Ast::from_str("{{ [] |foo }}", &syntax).unwrap().nodes,
+        Ast::from_str("{{ [] |foo }}", None, &syntax).unwrap().nodes,
         vec![Node::Expr(
             Ws(None, None),
             Expr::BinOp(
@@ -862,23 +898,23 @@ fn test_parse_array() {
 fn fuzzed_unicode_slice() {
     let d = "{eeuuu{b&{!!&{!!11{{
             0!(!1q҄א!)!!!!!!n!";
-    assert!(Ast::from_str(d, &Syntax::default()).is_err());
+    assert!(Ast::from_str(d, None, &Syntax::default()).is_err());
 }
 
 #[test]
 fn fuzzed_macro_no_end() {
     let s = "{%macro super%}{%endmacro";
-    assert!(Ast::from_str(s, &Syntax::default()).is_err());
+    assert!(Ast::from_str(s, None, &Syntax::default()).is_err());
 }
 
 #[test]
 fn fuzzed_target_recursion() {
     const TEMPLATE: &str = include_str!("../tests/target-recursion.txt");
-    assert!(Ast::from_str(TEMPLATE, &Syntax::default()).is_err());
+    assert!(Ast::from_str(TEMPLATE, None, &Syntax::default()).is_err());
 }
 
 #[test]
 fn fuzzed_unary_recursion() {
     const TEMPLATE: &str = include_str!("../tests/unary-recursion.txt");
-    assert!(Ast::from_str(TEMPLATE, &Syntax::default()).is_err());
+    assert!(Ast::from_str(TEMPLATE, None, &Syntax::default()).is_err());
 }
diff --git a/testing/templates/include_invalid_syntax.html b/testing/templates/include_invalid_syntax.html
new file mode 100644
index 0000000..a0d25c1
--- /dev/null
+++ b/testing/templates/include_invalid_syntax.html
@@ -0,0 +1,1 @@
+{% extends "invalid_syntax.html" %}
diff --git a/testing/templates/invalid_syntax.html b/testing/templates/invalid_syntax.html
new file mode 100644
index 0000000..3226bb8
--- /dev/null
+++ b/testing/templates/invalid_syntax.html
@@ -0,0 +1,1 @@
+{% let 12 = 0 }
diff --git a/testing/tests/ui.rs b/testing/tests/ui.rs
index 33848b2..5adb5c9 100644
--- a/testing/tests/ui.rs
+++ b/testing/tests/ui.rs
@@ -1,7 +1,11 @@
+#![cfg(not(windows))]
+
+use std::os::unix::fs::symlink;
+use std::path::Path;
 use trybuild::TestCases;
 use version_check as rustc;
 
-#[cfg_attr(not(windows), test)]
+#[test]
 fn ui() {
     let t = TestCases::new();
     t.compile_fail("tests/ui/*.rs");
@@ -21,4 +25,28 @@ fn ui() {
     if rustc::is_max_version("1.53").unwrap() {
         t.compile_fail("tests/ui/before_1.54/*.rs");
     }
+
+    // To be able to use existing templates, we create a link to the `templates` folder.
+    let manifest_dir = match std::env::var("CARGO_MANIFEST_DIR") {
+        Ok(manifest_dir) => manifest_dir,
+        Err(_) => panic!("you need to run tests with `cargo`"),
+    };
+    let target = Path::new(&manifest_dir).join("../target/tests/trybuild/askama_testing");
+    if !target.exists() {
+        if let Err(err) = std::fs::create_dir_all(&target) {
+            panic!("failed to create folder `{}`: {err:?}", target.display());
+        }
+    }
+    let target = target.canonicalize().unwrap().join("templates");
+    if target.exists() {
+        return;
+    }
+    let original = Path::new(&manifest_dir).join("templates");
+    if symlink(&original, &target).is_err() {
+        panic!(
+            "failed to create to create link on `{}` as `{}`",
+            original.display(),
+            target.display()
+        );
+    }
 }
diff --git a/testing/tests/ui/elif.stderr b/testing/tests/ui/elif.stderr
index 0d5de47..593e5c4 100644
--- a/testing/tests/ui/elif.stderr
+++ b/testing/tests/ui/elif.stderr
@@ -1,5 +1,5 @@
 error: unknown `elif` keyword; did you mean `else if`?
-       problems parsing template source at row 1, column 16 near:
+       failed to parse template source at row 1, column 16 near:
        "elif false %}{% endif %}"
  --> tests/ui/elif.rs:3:10
   |
diff --git a/testing/tests/ui/error_file_path.rs b/testing/tests/ui/error_file_path.rs
new file mode 100644
index 0000000..ec38053
--- /dev/null
+++ b/testing/tests/ui/error_file_path.rs
@@ -0,0 +1,16 @@
+use askama::Template;
+
+#[derive(Template)]
+#[template(path = "invalid_syntax.html")]
+struct A;
+
+#[derive(Template)]
+#[template(path = "include_invalid_syntax.html")]
+struct B;
+
+#[derive(Template)]
+#[template(source = r#"{% extends "include_invalid_syntax.html" %}"#, ext = "txt")]
+struct C;
+
+fn main() {
+}
diff --git a/testing/tests/ui/error_file_path.stderr b/testing/tests/ui/error_file_path.stderr
new file mode 100644
index 0000000..58b449a
--- /dev/null
+++ b/testing/tests/ui/error_file_path.stderr
@@ -0,0 +1,29 @@
+error: failed to parse template source
+ --> testing/templates/invalid_syntax.html:1:14
+       "}"
+ --> tests/ui/error_file_path.rs:3:10
+  |
+3 | #[derive(Template)]
+  |          ^^^^^^^^
+  |
+  = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: failed to parse template source
+ --> testing/templates/invalid_syntax.html:1:14
+       "}"
+ --> tests/ui/error_file_path.rs:7:10
+  |
+7 | #[derive(Template)]
+  |          ^^^^^^^^
+  |
+  = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: failed to parse template source
+ --> testing/templates/invalid_syntax.html:1:14
+       "}"
+  --> tests/ui/error_file_path.rs:11:10
+   |
+11 | #[derive(Template)]
+   |          ^^^^^^^^
+   |
+   = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/testing/tests/ui/excessive_nesting.stderr b/testing/tests/ui/excessive_nesting.stderr
index f8f48ec..ed01de9 100644
--- a/testing/tests/ui/excessive_nesting.stderr
+++ b/testing/tests/ui/excessive_nesting.stderr
@@ -1,4 +1,4 @@
-error: problems parsing template source at row 14, column 34 near:
+error: failed to parse template source at row 14, column 34 near:
        "%}{%if 1%}{%if 1%}{%if 1%}{%if 1%}{%if 1"...
  --> tests/ui/excessive_nesting.rs:3:10
   |
diff --git a/testing/tests/ui/extends.stderr b/testing/tests/ui/extends.stderr
index 346246d..a209811 100644
--- a/testing/tests/ui/extends.stderr
+++ b/testing/tests/ui/extends.stderr
@@ -1,5 +1,5 @@
 error: whitespace control is not allowed on `extends`
-       problems parsing template source at row 1, column 2 near:
+       failed to parse template source at row 1, column 2 near:
        "- extends \"whatever.html\" %}"
  --> tests/ui/extends.rs:3:10
   |
@@ -9,7 +9,7 @@ error: whitespace control is not allowed on `extends`
   = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: whitespace control is not allowed on `extends`
-       problems parsing template source at row 1, column 2 near:
+       failed to parse template source at row 1, column 2 near:
        " extends \"whatever.html\" -%}"
   --> tests/ui/extends.rs:10:10
    |
diff --git a/testing/tests/ui/macro_named_argument.stderr b/testing/tests/ui/macro_named_argument.stderr
index b346f2d..f855e6c 100644
--- a/testing/tests/ui/macro_named_argument.stderr
+++ b/testing/tests/ui/macro_named_argument.stderr
@@ -7,7 +7,7 @@ error: no argument named `param3` in macro "thrice"
   = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: named argument `param1` was passed more than once
-       problems parsing template source at row 5, column 15 near:
+       failed to parse template source at row 5, column 15 near:
        "(param1=2, param1=3) -%}"
   --> tests/ui/macro_named_argument.rs:11:10
    |
@@ -16,7 +16,7 @@ error: named argument `param1` was passed more than once
    |
    = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error: problems parsing template source at row 5, column 29 near:
+error: failed to parse template source at row 5, column 29 near:
        "| filter(param1=12) -%}"
   --> tests/ui/macro_named_argument.rs:20:10
    |
@@ -26,7 +26,7 @@ error: problems parsing template source at row 5, column 29 near:
    = note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: named arguments must always be passed last
-       problems parsing template source at row 4, column 15 near:
+       failed to parse template source at row 4, column 15 near:
        "(param1=2, 3) -%}"
   --> tests/ui/macro_named_argument.rs:29:10
    |
diff --git a/testing/tests/ui/match_with_extra.stderr b/testing/tests/ui/match_with_extra.stderr
index 8f515bd..841ee2e 100644
--- a/testing/tests/ui/match_with_extra.stderr
+++ b/testing/tests/ui/match_with_extra.stderr
@@ -1,4 +1,4 @@
-error: problems parsing template source at row 3, column 4 near:
+error: failed to parse template source at row 3, column 4 near:
        "// Help, I forgot how to write comments!"...
  --> tests/ui/match_with_extra.rs:3:10
   |
diff --git a/testing/tests/ui/name_mismatch_endblock.stderr b/testing/tests/ui/name_mismatch_endblock.stderr
index 958c413..8894e13 100644
--- a/testing/tests/ui/name_mismatch_endblock.stderr
+++ b/testing/tests/ui/name_mismatch_endblock.stderr
@@ -1,5 +1,5 @@
 error: expected name `foo` in `endblock` tag, found `not_foo`
-       problems parsing template source at row 1, column 27 near:
+       failed to parse template source at row 1, column 27 near:
        "not_foo %}"
  --> tests/ui/name_mismatch_endblock.rs:3:10
   |
diff --git a/testing/tests/ui/name_mismatch_endmacro.stderr b/testing/tests/ui/name_mismatch_endmacro.stderr
index 74101a4..14e7d0a 100644
--- a/testing/tests/ui/name_mismatch_endmacro.stderr
+++ b/testing/tests/ui/name_mismatch_endmacro.stderr
@@ -1,5 +1,5 @@
 error: expected name `foo` in `endmacro` tag, found `not_foo`
-       problems parsing template source at row 1, column 41 near:
+       failed to parse template source at row 1, column 41 near:
        "not_foo %}"
  --> tests/ui/name_mismatch_endmacro.rs:3:10
   |
diff --git a/testing/tests/ui/since_1.58/break_outside_of_loop.stderr b/testing/tests/ui/since_1.58/break_outside_of_loop.stderr
index 8f6ba68..b7a74d4 100644
--- a/testing/tests/ui/since_1.58/break_outside_of_loop.stderr
+++ b/testing/tests/ui/since_1.58/break_outside_of_loop.stderr
@@ -1,4 +1,4 @@
-error: problems parsing template source at row 1, column 9 near:
+error: failed to parse template source at row 1, column 9 near:
        "break%}, have a parsing error!"
  --> tests/ui/since_1.58/break_outside_of_loop.rs:3:10
   |
diff --git a/testing/tests/ui/since_1.58/typo_in_keyword.stderr b/testing/tests/ui/since_1.58/typo_in_keyword.stderr
index 92c638d..a0cb99b 100644
--- a/testing/tests/ui/since_1.58/typo_in_keyword.stderr
+++ b/testing/tests/ui/since_1.58/typo_in_keyword.stderr
@@ -1,4 +1,4 @@
-error: problems parsing template source at row 1, column 26 near:
+error: failed to parse template source at row 1, column 26 near:
        "endfo%}\n12345678901234567890123456789012"...
  --> tests/ui/since_1.58/typo_in_keyword.rs:3:10
   |