]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
Implement consistent scoping for sets in loops
authorArmin Ronacher <armin.ronacher@active-4.com>
Sun, 8 Jan 2017 02:17:30 +0000 (03:17 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sun, 8 Jan 2017 02:17:30 +0000 (03:17 +0100)
While technically this applies to any scope and not just for loops
it comes up most commonly in the context of for loops.  This now
defines the behavior for scoping in a way that is consistent but
different than it was in the past.  There is an ongoing conversation
if we should keep it that way or not.

References #641

jinja2/idtracking.py
tests/test_core_tags.py

index 87ef5107ffdbc72cada152c86b72ad59fb75103c..b00dab8c1c604ce1568ffb9eca37724e96e7e2f7 100644 (file)
@@ -73,10 +73,22 @@ class Symbols(object):
         return rv
 
     def store(self, name):
-        # We already have that name locally, so we can just bail
+        self.stores.add(name)
+
+        # If we have not see the name referenced yet, we need to figure
+        # out what to set it to.
         if name not in self.refs:
+            # If there is a parent scope we check if the name has a
+            # reference there.  If it does it means we might have to alias
+            # to a variable there.
+            if self.parent is not None:
+                outer_ref = self.parent.find_ref(name)
+                if outer_ref is not None:
+                    self._define_ref(name, load=(VAR_LOAD_ALIAS, outer_ref))
+                    return
+
+            # Otherwise we can just set it to undefined.
             self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None))
-        self.stores.add(name)
 
     def declare_parameter(self, name):
         self.stores.add(name)
index 04380082b31f220fb3e1a96d01f0808fe44c195d..9f0cabfb2bbfa5ce1be9e4670ff6eab472b369a9 100644 (file)
@@ -199,6 +199,15 @@ class TestForLoop(object):
                                '{{ a }}|{{ b }}|{{ c }}{% endfor %}')
         assert tmpl.render() == '1|2|3'
 
+    def test_intended_scoping_with_set(self, env):
+        tmpl = env.from_string('{% for item in seq %}{{ x }}'
+                               '{% set x = item %}{{ x }}{% endfor %}')
+        assert tmpl.render(x=0, seq=[1, 2, 3]) == '010203'
+
+        tmpl = env.from_string('{% set x = 9 %}{% for item in seq %}{{ x }}'
+                               '{% set x = item %}{{ x }}{% endfor %}')
+        assert tmpl.render(x=0, seq=[1, 2, 3]) == '919293'
+
 
 @pytest.mark.core_tags
 @pytest.mark.if_condition