From: Armin Ronacher Date: Wed, 13 Aug 2008 16:55:50 +0000 (+0200) Subject: fixed a bug with nested loops and the special loop variable. X-Git-Tag: 2.1~44 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=ff53c785742f9221d0e71286f1a8f663505cda41;p=jinja2.git 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 @@ 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) 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 @@ 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() 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_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]'