Repository: mitsuhiko/minijinja (language: rust)

Issue:
`undeclared_variables()` stuck with  `for loop` ({% for ... in ... %})
## Description
minijinja [undeclared_variables()](https://docs.rs/minijinja/latest/minijinja/struct.Template.html#method.undeclared_variables) stucks when there is a variable in `for loop`

[What happened]
The running code stuck and never exited

## Reproduction steps
```rust
// main.rs
// Running the code below
fn main() {
    let mut env = minijinja::Environment::new();
    env.add_template("my_test", {
        r#"
            {% for item in content.products.items() %}
              {{ foo.bar }}                          
              {{ item.name }}  // remove this line and it will run to exit
            {% endfor %}
        "#
    })
    .unwrap();
    let tpl = env.get_template("my_test").unwrap();
    println!("check point 1");
    let ret = tpl.undeclared_variables(true);     <-- stuck here
    println!("check point 2, ret={:?}", ret);    <-- never see this
}
```
```
# Cargo.toml
[package]
name = "undeclared_variables_stuck_issue"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
minijinja = "1.0.17"

```
Additional helpful information:

- Version of minijinja: 
```
1.0.17
``` 
- Version of rustc:
```
rustc 1.76.0 (07dca489a 2024-02-04)
```
- Operating system and version:
```
ProductName:            macOS
ProductVersion:         14.2.1
BuildVersion:           23C71
```
- If set `nested` param to `false`, it's able to run to exit

## What did you expect
When running the code above, I expect to see 
```bash
check point 1
check point 2, ret={"foo.bar", "content.products.items"}
# Note that the output didn't have `item.name` 
# cause it's a declared variable in for loop
```

[What you think should have happened]
The `cpu` is high  (20 % v.s  5%) all time when running the code above, I guess it might enter an infinite cyclic loop



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 84a7af1c..5ae24db5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 All notable changes to MiniJinja are documented here.
 
+## 1.0.18
+
+- Fixed an endless loop in `undeclared_variables`.  #486
+
 ## 1.0.17
 
 - Added support for `Option<Into<Value>>` as return value from
diff --git a/minijinja/src/compiler/meta.rs b/minijinja/src/compiler/meta.rs
index 4aa82963..edd6aa06 100644
--- a/minijinja/src/compiler/meta.rs
+++ b/minijinja/src/compiler/meta.rs
@@ -131,6 +131,8 @@ fn tracker_visit_expr<'a>(expr: &ast::Expr<'a>, state: &mut AssignmentTracker<'a
                                 }
                                 state.assign_nested(rv);
                                 return;
+                            } else {
+                                break;
                             }
                         }
                         ast::Expr::GetAttr(expr) => {


Test patch:
diff --git a/minijinja/tests/test_templates.rs b/minijinja/tests/test_templates.rs
index 6a48e661..fbe1c825 100644
--- a/minijinja/tests/test_templates.rs
+++ b/minijinja/tests/test_templates.rs
@@ -446,6 +446,31 @@ fn test_undeclared_variables() {
     );
 }
 
+#[test]
+fn test_undeclared_variables_bug() {
+    // see https://github.com/mitsuhiko/minijinja/issues/484
+    let mut env = Environment::new();
+    env.add_template(
+        "demo",
+        r#"
+        {% for item in content.products.items() %}
+          {{ foo.bar }}
+          {{ item.name }}  // this used to cause a loop
+        {% endfor %}
+    "#,
+    )
+    .unwrap();
+    let tmpl = env.get_template("demo").unwrap();
+    let undeclared = tmpl.undeclared_variables(true);
+    assert_eq!(
+        undeclared,
+        ["foo.bar", "content.products.items"]
+            .into_iter()
+            .map(|x| x.to_string())
+            .collect()
+    );
+}
+
 #[test]
 fn test_block_fragments() {
     let mut env = Environment::new();