- Solved some warnings for string literals. (#646)
- Increment the bytecode cache version which was not done due to an
oversight before.
+- Corrected bad code generation and scoping for filtered loops. (#649)
Version 2.9.3
-------------
def visit_For(self, node, frame):
loop_frame = frame.inner()
+ test_frame = frame.inner()
else_frame = frame.inner()
# try to figure out if we have an extended loop. An extended loop
loop_ref = None
if extended_loop:
loop_ref = loop_frame.symbols.declare_parameter('loop')
- loop_frame.symbols.analyze_node(node)
+ loop_frame.symbols.analyze_node(node, for_branch='body')
if node.else_:
else_frame.symbols.analyze_node(node, for_branch='else')
+ if node.test:
+ loop_filter_func = self.temporary_identifier()
+ test_frame.symbols.analyze_node(node, for_branch='test')
+ self.writeline('%s(fiter):' % self.func(loop_filter_func), node.test)
+ self.indent()
+ self.enter_frame(test_frame)
+ self.writeline(self.environment.is_async and 'async for ' or 'for ')
+ self.visit(node.target, loop_frame)
+ self.write(' in ')
+ self.write(self.environment.is_async and 'auto_aiter(fiter)' or 'fiter')
+ self.write(':')
+ self.indent()
+ self.writeline('if ', node.test)
+ self.visit(node.test, test_frame)
+ self.write(':')
+ self.indent()
+ self.writeline('yield ')
+ self.visit(node.target, loop_frame)
+ self.outdent(3)
+ self.leave_frame(test_frame, with_python_scope=True)
+
# if we don't have an recursive loop we have to find the shadowed
# variables at that point. Because loops can be nested but the loop
# variable is a special one we have to enforce aliasing for it.
else:
self.write(' in ')
- # if we have an extened loop and a node test, we filter in the
- # "outer frame".
- if extended_loop and node.test is not None:
- self.write('(')
- self.visit(node.target, loop_frame)
- self.write(self.environment.is_async and ' async for ' or ' for ')
- self.visit(node.target, loop_frame)
- self.write(' in ')
- if node.recursive:
- self.write('reciter')
- else:
- if self.environment.is_async:
- self.write('auto_aiter(')
- self.visit(node.iter, frame)
- if self.environment.is_async:
- self.write(')')
- self.write(' if (')
- self.visit(node.test, loop_frame)
- self.write('))')
-
- elif node.recursive:
+ if node.test:
+ self.write('%s(' % loop_filter_func)
+ if node.recursive:
self.write('reciter')
else:
if self.environment.is_async and not extended_loop:
self.visit(node.iter, frame)
if self.environment.is_async and not extended_loop:
self.write(')')
+ if node.test:
+ self.write(')')
if node.recursive:
self.write(', loop_render_func, depth):')
self.indent()
self.enter_frame(loop_frame)
- # tests in not extended loops become a continue
- if not extended_loop and node.test is not None:
- self.writeline('if not ')
- self.visit(node.test, loop_frame)
- self.write(':')
- self.indent()
- self.writeline('continue')
- self.outdent()
-
self.blockvisit(node.body, loop_frame)
if node.else_:
self.writeline('%s = 0' % iteration_indicator)
self.sym_visitor.visit(child)
def visit_For(self, node, for_branch='body', **kwargs):
- if node.test is not None:
- self.sym_visitor.visit(node.test)
if for_branch == 'body':
self.sym_visitor.visit(node.target, store_as_param=True)
branch = node.body
elif for_branch == 'else':
branch = node.else_
+ elif for_branch == 'test':
+ self.sym_visitor.visit(node.target, store_as_param=True)
+ if node.test is not None:
+ self.sym_visitor.visit(node.test)
+ return
else:
raise RuntimeError('Unknown for branch')
for item in branch or ():
tmpl = test_env_async.from_string('{% for a, b, c in [[1, 2, 3]] %}'
'{{ a }}|{{ b }}|{{ c }}{% endfor %}')
assert tmpl.render() == '1|2|3'
+
+ def test_recursive_loop_filter(self, test_env_async):
+ t = test_env_async.from_string('''
+ <?xml version="1.0" encoding="UTF-8"?>
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+ {%- for page in [site.root] if page.url != this recursive %}
+ <url><loc>{{ page.url }}</loc></url>
+ {{- loop(page.children) }}
+ {%- endfor %}
+ </urlset>
+ ''')
+ sm =t.render(this='/foo', site={'root': {
+ 'url': '/',
+ 'children': [
+ {'url': '/foo'},
+ {'url': '/bar'},
+ ]
+ }})
+ lines = [x.strip() for x in sm.splitlines() if x.strip()]
+ assert lines == [
+ '<?xml version="1.0" encoding="UTF-8"?>',
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
+ '<url><loc>/</loc></url>',
+ '<url><loc>/bar</loc></url>',
+ '</urlset>',
+ ]
+
+ def test_nonrecursive_loop_filter(self, test_env_async):
+ t = test_env_async.from_string('''
+ <?xml version="1.0" encoding="UTF-8"?>
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+ {%- for page in items if page.url != this %}
+ <url><loc>{{ page.url }}</loc></url>
+ {%- endfor %}
+ </urlset>
+ ''')
+ sm =t.render(this='/foo', items=[
+ {'url': '/'},
+ {'url': '/foo'},
+ {'url': '/bar'},
+ ])
+ lines = [x.strip() for x in sm.splitlines() if x.strip()]
+ assert lines == [
+ '<?xml version="1.0" encoding="UTF-8"?>',
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
+ '<url><loc>/</loc></url>',
+ '<url><loc>/bar</loc></url>',
+ '</urlset>',
+ ]
t = env.from_string('{% set x = 1 %}{% with x = 2 %}{% block y scoped %}'
'{{ x }}{% endblock %}{% endwith %}')
assert t.render() == '2'
+
+ def test_recursive_loop_filter(self, env):
+ t = env.from_string('''
+ <?xml version="1.0" encoding="UTF-8"?>
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+ {%- for page in [site.root] if page.url != this recursive %}
+ <url><loc>{{ page.url }}</loc></url>
+ {{- loop(page.children) }}
+ {%- endfor %}
+ </urlset>
+ ''')
+ sm =t.render(this='/foo', site={'root': {
+ 'url': '/',
+ 'children': [
+ {'url': '/foo'},
+ {'url': '/bar'},
+ ]
+ }})
+ lines = [x.strip() for x in sm.splitlines() if x.strip()]
+ print(lines)
+ assert lines == [
+ '<?xml version="1.0" encoding="UTF-8"?>',
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
+ '<url><loc>/</loc></url>',
+ '<url><loc>/bar</loc></url>',
+ '</urlset>',
+ ]