Implement consistent scoping for sets in loops

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
This commit is contained in:
Armin Ronacher 2017-01-08 03:17:30 +01:00
parent b557eca315
commit 8172db059d
2 changed files with 24 additions and 3 deletions

View File

@ -73,11 +73,23 @@ class Symbols(object):
return rv
def store(self, name):
# We already have that name locally, so we can just bail
if name not in self.refs:
self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None))
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))
def declare_parameter(self, name):
self.stores.add(name)
return self._define_ref(name, load=(VAR_LOAD_PARAMETER, None))

View 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