Repository: mitsuhiko/minijinja (language: rust)

Issue:
"TemplateError: unknown function" when called from inside a macro
## Reproduction steps

Running the (Python) code

```python
import minijinja

env = minijinja.Environment(undefined_behavior="strict")
print(env.render_str("""
{% macro collapsible(title) -%}
{{ caller() }}
{%- endmacro %}

{% macro autograder_ui_hint() -%}
{% call collapsible("Tips for using the autograder") %}
ABC
{% endcall %}
{%- endmacro %}

{{ autograder_ui_hint() }}"""))
```

gives me
```
Traceback (most recent call last):
  File "/home/andreas/tmp/a.py", line 4, in <module>
    print(env.render_str("""
          ^^^^^^^^^^^^^^^^^^
minijinja.TemplateError: unknown function: collapsible is unknown (in <string>:9)
---------------------------------- <string> -----------------------------------
   6 | {%- endmacro %}
   7 | 
   8 | {% macro autograder_ui_hint() -%}
   9 > {% call collapsible("Tips for using the autograder") %}
     i         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unknown function
  10 | ABC
  11 | {% endcall %}
  12 | {%- endmacro %}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
No referenced variables
-------------------------------------------------------------------------------
```
Additional helpful information:

- Version of minijinja (Python): 1.0.13 (binary from PyPI)
- Version of rustc: ?
- Operating system and version: Linux/Debian 12

## What did you expect

Since `collapsible` is defined above, it should also be callable from within the `autograder_ui_hint` macro. Jinja2 (Python) allows this.


Discussion / hints:
Thanks. It looks like the closure tracker does not properly recognize the need to build closures within macros. Will have a look at this.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a41af88f..65ec7e6e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
 
 All notable changes to MiniJinja are documented here.
 
+## 1.0.14
+
+- Fixed a bug with broken closure handling when working with nested
+  `{% macro %}` or `{% call %}` blocks.  #435
+
 ## 1.0.13
 
 - `minijinja-cli` now supports an `-o` or `--output` parameter to write
diff --git a/minijinja/src/compiler/meta.rs b/minijinja/src/compiler/meta.rs
index 2a0b7420..4aa82963 100644
--- a/minijinja/src/compiler/meta.rs
+++ b/minijinja/src/compiler/meta.rs
@@ -43,8 +43,7 @@ pub fn find_macro_closure<'a>(m: &ast::Macro<'a>) -> HashSet<&'a str> {
         nested_out: None,
         assigned: vec![Default::default()],
     };
-    m.args.iter().for_each(|arg| track_assign(arg, &mut state));
-    m.body.iter().for_each(|node| track_walk(node, &mut state));
+    tracker_visit_macro(m, &mut state);
     state.out
 }
 
@@ -73,6 +72,15 @@ 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>) {
+    m.args.iter().for_each(|arg| track_assign(arg, state));
+    m.defaults
+        .iter()
+        .for_each(|expr| tracker_visit_expr(expr, state));
+    m.body.iter().for_each(|node| track_walk(node, state));
+}
+
 fn tracker_visit_expr<'a>(expr: &ast::Expr<'a>, state: &mut AssignmentTracker<'a>) {
     match expr {
         ast::Expr::Var(var) => {
@@ -247,9 +255,17 @@ fn track_walk<'a>(node: &ast::Stmt<'a>, state: &mut AssignmentTracker<'a>) {
         #[cfg(feature = "macros")]
         ast::Stmt::Macro(stmt) => {
             state.assign(stmt.name);
+            tracker_visit_macro(stmt, state);
         }
         #[cfg(feature = "macros")]
-        ast::Stmt::CallBlock(_) => {}
+        ast::Stmt::CallBlock(stmt) => {
+            tracker_visit_expr(&stmt.call.expr, state);
+            stmt.call
+                .args
+                .iter()
+                .for_each(|x| tracker_visit_expr(x, state));
+            tracker_visit_macro(&stmt.macro_decl, state);
+        }
         ast::Stmt::Do(stmt) => {
             tracker_visit_expr(&stmt.call.expr, state);
             stmt.call


Test patch:
diff --git a/minijinja/tests/test_macros.rs b/minijinja/tests/test_macros.rs
index 2836bcbf..42755942 100644
--- a/minijinja/tests/test_macros.rs
+++ b/minijinja/tests/test_macros.rs
@@ -2,6 +2,7 @@
 use std::sync::atomic::AtomicBool;
 use std::sync::Arc;
 
+use insta::assert_snapshot;
 use similar_asserts::assert_eq;
 
 use minijinja::value::{Kwargs, StructObject, Value};
@@ -168,3 +169,45 @@ fn test_no_leak() {
         "{}<macro meh><macro foo>{}<macro foo>{}<macro foo>{}<macro foo>{}"
     );
 }
+
+/// https://github.com/mitsuhiko/minijinja/issues/434
+#[test]
+fn test_nested_macro_bug() {
+    let rv = render!(
+        r#"
+    {% set a = 42 %}
+    {% macro m1(var) -%}
+      {{ var }}
+    {%- endmacro %}
+    
+    {% macro m2(x=a) -%}
+      {{ m1(x) }}
+    {%- endmacro %}
+    
+    {{ m2() }}
+    "#
+    );
+    assert_snapshot!(rv.trim(), @"42");
+}
+
+/// https://github.com/mitsuhiko/minijinja/issues/434
+#[test]
+fn test_caller_bug() {
+    let rv = render!(
+        r#"
+    {% set a = 42 %}
+    {% set b = 23 %}
+
+    {% macro m1(var) -%}
+      {{ caller(var) }}
+    {%- endmacro %}
+    
+    {% macro m2(x=a) -%}
+      {% call(var) m1(x) %}{{ var }}|{{ b }}{% endcall %}
+    {%- endmacro %}
+    
+    {{ m2() }}
+    "#
+    );
+    assert_snapshot!(rv.trim(), @"42|23");
+}