fixed a bug with nested loops and the special loop variable.
authorArmin Ronacher <armin.ronacher@active-4.com>
Wed, 13 Aug 2008 16:55:50 +0000 (18:55 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Wed, 13 Aug 2008 16:55:50 +0000 (18:55 +0200)
H: Enter commit message.  Lines beginning with 'HG:' are removed.

--HG--
branch : trunk

CHANGES
jinja2/compiler.py
tests/test_forloop.py

diff --git a/CHANGES b/CHANGES
index 02f12fd85e18c89eb3cc4ab3f98e7acd03322772..8f957411e9d01a3be71991d756e78d8c189c5407 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -5,6 +5,10 @@ Version 2.1
 -----------
 (codename to be selected, release date yet unknown)
 
+- fixed a bug with nested loops and the special loop variable.  Before the
+  change an inner loop overwrote the loop variable from the outer one after
+  iteration.
+
 Version 2.0
 -----------
 (codename jinjavitus, released on July 17th 2008)
index fab6ea0c09f7dd76c0b1a74e89fbcfec7e537f3f..ad7857d07e3076928f507dbfee06ce5b0b22eb86 100644 (file)
@@ -115,10 +115,13 @@ class Identifiers(object):
             return False
         return name in self.declared
 
-    def find_shadowed(self):
-        """Find all the shadowed names."""
+    def find_shadowed(self, extra=()):
+        """Find all the shadowed names.  extra is an iterable of variables
+        that may be defined with `add_special` which may occour scoped.
+        """
         return (self.declared | self.outer_undeclared) & \
-               (self.declared_locally | self.declared_parameter)
+               (self.declared_locally | self.declared_parameter) | \
+               set(x for x in extra if self.is_declared(x))
 
 
 class Frame(object):
@@ -511,13 +514,15 @@ class CodeGenerator(NodeVisitor):
                 self.writeline('%s = environment.%s[%r]' %
                                (mapping[name], dependency, name))
 
-    def collect_shadowed(self, frame):
+    def collect_shadowed(self, frame, extra_vars=()):
         """This function returns all the shadowed variables in a dict
         in the form name: alias and will write the required assignments
         into the current scope.  No indentation takes place.
+
+        `extra_vars` is passed to `Identifiers.find_shadowed`.
         """
         aliases = {}
-        for name in frame.identifiers.find_shadowed():
+        for name in frame.identifiers.find_shadowed(extra_vars):
             aliases[name] = ident = self.temporary_identifier()
             self.writeline('%s = l_%s' % (ident, name))
         return aliases
@@ -889,18 +894,11 @@ class CodeGenerator(NodeVisitor):
                         find_undeclared(node.iter_child_nodes(
                             only=('body',)), ('loop',))
 
-        # make sure the loop variable is a special one and raise a template
-        # assertion error if a loop tries to write to loop
-        loop_frame.identifiers.add_special('loop')
-        for name in node.find_all(nodes.Name):
-            if name.ctx == 'store' and name.name == 'loop':
-                self.fail('Can\'t assign to special loop variable '
-                          'in for-loop target', name.lineno)
-
         # if we don't have an recursive loop we have to find the shadowed
-        # variables at that point
+        # variables at that point.  Because loops can be nested but the loop
+        # variable is a special one we have to enforce aliasing for it.
         if not node.recursive:
-            aliases = self.collect_shadowed(loop_frame)
+            aliases = self.collect_shadowed(loop_frame, ('loop',))
 
         # otherwise we set up a buffer and add a function def
         else:
@@ -909,6 +907,14 @@ class CodeGenerator(NodeVisitor):
             self.buffer(loop_frame)
             aliases = {}
 
+        # make sure the loop variable is a special one and raise a template
+        # assertion error if a loop tries to write to loop
+        loop_frame.identifiers.add_special('loop')
+        for name in node.find_all(nodes.Name):
+            if name.ctx == 'store' and name.name == 'loop':
+                self.fail('Can\'t assign to special loop variable '
+                          'in for-loop target', name.lineno)
+
         self.pull_locals(loop_frame)
         if node.else_:
             iteration_indicator = self.temporary_identifier()
index f7e4d68307094e2e9096ff0d0d2c55d320833e4f..a4f057c2f132e7b29043dde37f88acc8e4261b88 100644 (file)
@@ -138,3 +138,9 @@ def test_loop_filter(env):
 
 def test_loop_unassignable(env):
     raises(TemplateSyntaxError, env.from_string, LOOPUNASSIGNABLE)
+
+
+def test_scoped_special_var(env):
+    t = env.from_string('{% for s in seq %}[{{ loop.first }}{% for c in s %}'
+                        '|{{ loop.first }}{% endfor %}]{% endfor %}')
+    assert t.render(seq=('ab', 'cd')) == '[True|True|False][False|True|False]'