Repository: pallets/jinja (python)

Issue:
What's wrong with this Jinja template when using {% elif %}
I have the following **Jinja** template:

```jinja
{% if new_data_set_created is defined %}
    {% if new_data_set_created == True %}
        The new image data set <strong>{{ form_data["data_set_name"] }}</strong> has been created.
        {% set form_data = {"data_set_name": "", "client": "", "project": ""} %}
    {% else %}
        The new image data set <strong>{{ form_data["data_set_name"] }}</strong> could not be created.<br />
    {% endif %}
{% elif deleted_data_set is defined %}
    The image data set <strong>{{ deleted_data_set }}</strong> has been deleted from DB and from disk.
    {% set form_data = {"data_set_name": "", "client": "", "project": ""} %}
{% else %}
    {% set form_data = {"data_set_name": "", "client": "", "project": ""} %}
{% endif %}
```

However, when rendering this template via **Flask** I get the error message that `form_data` in line 3 of this code section is undefined (which is not the case, I double-checked that):

    jinja2.exceptions.UndefinedError: 'form_data' is undefined

Now when I remove the `elif` block from that code section above (i.e. lines 8-10) then the very same template does work again and line 3 does not lead to an error any more.

I am calling this template like so:

```python
return render_template(
    "image-data-sets.html",
    new_data_set_created=True,
    form_data=request.form,
    data_sets=ImageDataSet.query.all(),
    clients=clients,
)
```

I double-checked that `request.form` (and thus `form_data` within the template) is not `None`. And as said, I don't make any changes to this call, I just remove those lines from the template and then it's working.

So my question: Why does removing the `elif` block fix this issue, and why is this error raised in the first place? What's wrong with my `if-elif-else-endif` block? Is this a bug or am I missing something?

Pull request:
Fix bug involving calling set on a template parameter within all branches of an if block
This fixes a bug where calling `{% set %}` in all 3 branches of an `{% if %}` block could result in the parameter being undefined sometimes. If the `{% set %}` call was not done in all branches or it was done outside of an `{% if %}` block then the bug would not appear. This was determined to be a bad assumption being made by the ID tracking for variables within the template loader where if the `{% set %}` occurred in all branches then it was not going to be a previously defined variable.

Fixes #1253

Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [x] Add or update relevant docs, in the docs folder and in code.
- [ ] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [ ] Add `.. versionchanged::` entries in any relevant code docs.
- [x] Run `pre-commit` hooks and fix any issues.
- [x] Run `pytest` and `tox`, no tests failed.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 2b817985..b6a5a1af 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -45,6 +45,9 @@ Unreleased
     filter. :issue:`1624`
 -   Using ``set`` for multiple assignment (``a, b = 1, 2``) does not fail when the
     target is a namespace attribute. :issue:`1413`
+-   Using ``set`` in all branches of ``{% if %}{% elif %}{% else %}`` blocks
+    does not cause the variable to be considered initially undefined.
+    :issue:`1253`
 
 
 Version 3.1.4
diff --git a/src/jinja2/idtracking.py b/src/jinja2/idtracking.py
index cb4bccb0..e6dd8cd1 100644
--- a/src/jinja2/idtracking.py
+++ b/src/jinja2/idtracking.py
@@ -121,23 +121,20 @@ class Symbols:
             self._define_ref(name, load=(VAR_LOAD_RESOLVE, name))
 
     def branch_update(self, branch_symbols: t.Sequence["Symbols"]) -> None:
-        stores: t.Dict[str, int] = {}
+        stores: t.Set[str] = set()
+
         for branch in branch_symbols:
-            for target in branch.stores:
-                if target in self.stores:
-                    continue
-                stores[target] = stores.get(target, 0) + 1
+            stores.update(branch.stores)
+
+        stores.difference_update(self.stores)
 
         for sym in branch_symbols:
             self.refs.update(sym.refs)
             self.loads.update(sym.loads)
             self.stores.update(sym.stores)
 
-        for name, branch_count in stores.items():
-            if branch_count == len(branch_symbols):
-                continue
-
-            target = self.find_ref(name)  # type: ignore
+        for name in stores:
+            target = self.find_ref(name)
             assert target is not None, "should not happen"
 
             if self.parent is not None:


Test patch:
diff --git a/tests/test_regression.py b/tests/test_regression.py
index 10df2d1b..93d72c5e 100644
--- a/tests/test_regression.py
+++ b/tests/test_regression.py
@@ -750,6 +750,16 @@ End"""
         assert tmpl.render() == "foo"
 
 
+def test_load_parameter_when_set_in_all_if_branches(env):
+    tmpl = env.from_string(
+        "{% if True %}{{ a.b }}{% set a = 1 %}"
+        "{% elif False %}{% set a = 2 %}"
+        "{% else %}{% set a = 3 %}{% endif %}"
+        "{{ a }}"
+    )
+    assert tmpl.render(a={"b": 0}) == "01"
+
+
 @pytest.mark.parametrize("unicode_char", ["\N{FORM FEED}", "\x85"])
 def test_unicode_whitespace(env, unicode_char):
     content = "Lorem ipsum\n" + unicode_char + "\nMore text"