fixed a bug with nested loops and the special loop variable.
H: Enter commit message. Lines beginning with 'HG:' are removed.
--HG--
branch : trunk
diff --git a/CHANGES b/CHANGES
index 02f12fd..8f95741 100644
--- a/CHANGES
+++ b/CHANGES
@@ -5,6 +5,10 @@
-----------
(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)
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index fab6ea0..ad7857d 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -115,10 +115,13 @@
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 @@
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 @@
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 @@
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()
diff --git a/tests/test_forloop.py b/tests/test_forloop.py
index f7e4d68..a4f057c 100644
--- a/tests/test_forloop.py
+++ b/tests/test_forloop.py
@@ -138,3 +138,9 @@
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]'