Repository: mitsuhiko/minijinja
Pull request #725 (2025-02-13 00:54:56)

Fix caller being marked as undeclared
`caller` was not handled correctly for undeclared variable finding.

Refs #715


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ffa4a9b..acb2e20 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@ All notable changes to MiniJinja are documented here.
   open parentheses, braces or brackets.  #723
 - Added `State::known_variables` to return a list of known variables
   and `Environment::globals`.  #724
+- Fixed an issue with undeclared variables not handling `caller`.  #725
 
 ## 2.8.0
 
diff --git a/minijinja/src/compiler/meta.rs b/minijinja/src/compiler/meta.rs
index 7a97686..984bcf0 100644
--- a/minijinja/src/compiler/meta.rs
+++ b/minijinja/src/compiler/meta.rs
@@ -43,7 +43,7 @@ pub fn find_macro_closure<'a>(m: &ast::Macro<'a>) -> HashSet<&'a str> {
         nested_out: None,
         assigned: vec![Default::default()],
     };
-    tracker_visit_macro(m, &mut state);
+    tracker_visit_macro(m, &mut state, false);
     state.out
 }
 
@@ -73,7 +73,18 @@ fn tracker_visit_expr_opt<'a>(expr: &Option<ast::Expr<'a>>, state: &mut Assignme
 }
 
 #[cfg(feature = "macros")]
-fn tracker_visit_macro<'a>(m: &ast::Macro<'a>, state: &mut AssignmentTracker<'a>) {
+fn tracker_visit_macro<'a>(
+    m: &ast::Macro<'a>,
+    state: &mut AssignmentTracker<'a>,
+    declare_caller: bool,
+) {
+    if declare_caller {
+        // this is not completely correct as caller is actually only defined
+        // if the macro was used in the context of a call block.  However it
+        // is impossible to determine this at compile time so we err on the
+        // side of assuming caller is there.
+        state.assign("caller");
+    }
     m.args.iter().for_each(|arg| track_assign(arg, state));
     m.defaults
         .iter()
@@ -269,7 +280,7 @@ fn track_walk<'a>(node: &ast::Stmt<'a>, state: &mut AssignmentTracker<'a>) {
         ast::Stmt::Macro(stmt) => {
             state.assign(stmt.name);
             state.push();
-            tracker_visit_macro(stmt, state);
+            tracker_visit_macro(stmt, state, true);
             state.pop();
         }
         #[cfg(feature = "macros")]
@@ -280,7 +291,7 @@ fn track_walk<'a>(node: &ast::Stmt<'a>, state: &mut AssignmentTracker<'a>) {
                 .iter()
                 .for_each(|x| tracker_visit_callarg(x, state));
             state.push();
-            tracker_visit_macro(&stmt.macro_decl, state);
+            tracker_visit_macro(&stmt.macro_decl, state, true);
             state.pop();
         }
         #[cfg(feature = "loop_controls")]


Test patch:
diff --git a/minijinja/tests/test_templates.rs b/minijinja/tests/test_templates.rs
index 9de80c0..278e50e 100644
--- a/minijinja/tests/test_templates.rs
+++ b/minijinja/tests/test_templates.rs
@@ -491,19 +491,23 @@ fn test_undeclared_variables() {
     let mut env = Environment::new();
     env.add_template(
         "demo",
-        "{% set x = foo %}{{ x }}{{ bar.baz }}{{ bar.blub }}",
+        "{% set x = foo %}{{ x }}{{ bar.baz }}{{ bar.blub }}
+        {% macro blah() %}{{ macro_x }}{{ caller() }}{% endmacro %}",
     )
     .unwrap();
     let tmpl = env.get_template("demo").unwrap();
     let undeclared = tmpl.undeclared_variables(false);
     assert_eq!(
         undeclared,
-        ["foo", "bar"].into_iter().map(|x| x.to_string()).collect()
+        ["foo", "bar", "macro_x"]
+            .into_iter()
+            .map(|x| x.to_string())
+            .collect()
     );
     let undeclared = tmpl.undeclared_variables(true);
     assert_eq!(
         undeclared,
-        ["foo", "bar.baz", "bar.blub"]
+        ["foo", "bar.baz", "bar.blub", "macro_x"]
             .into_iter()
             .map(|x| x.to_string())
             .collect()