Permalink
Browse files

fixed a bug with nested loops and the special loop variable.

H: Enter commit message.  Lines beginning with 'HG:' are removed.

--HG--
branch : trunk
  • Loading branch information...
1 parent 9a0078d commit ff53c785742f9221d0e71286f1a8f663505cda41 @mitsuhiko mitsuhiko committed Aug 13, 2008
Showing with 31 additions and 15 deletions.
  1. +4 −0 CHANGES
  2. +21 −15 jinja2/compiler.py
  3. +6 −0 tests/test_forloop.py
View
@@ -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)
View
@@ -115,10 +115,13 @@ def is_declared(self, name, local_only=False):
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 @@ def pull_dependencies(self, nodes):
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 @@ def visit_For(self, node, frame):
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 @@ def visit_For(self, node, frame):
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()
View
@@ -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]'

0 comments on commit ff53c78

Please sign in to comment.