]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
Compile `elif` tag to `elif` instead of `else: if` 760/head
authorAdrian Moennich <adrian@planetcoding.net>
Tue, 22 Aug 2017 20:59:57 +0000 (22:59 +0200)
committerAdrian Moennich <adrian@planetcoding.net>
Tue, 22 Aug 2017 21:12:09 +0000 (23:12 +0200)
This avoids deep nesting in case of many `{% elif .. %}` blocks (which
would fail during execution) and also deep recursion (which may fail
during compilation)

fixes #759

CHANGES
jinja2/compiler.py
jinja2/idtracking.py
jinja2/nodes.py
jinja2/parser.py
tests/test_core_tags.py
tests/test_idtracking.py

diff --git a/CHANGES b/CHANGES
index 38f4508cc8509667af4f4f3125b1e2dc55a031c2..7e5df3dd55b582e75fc1c040ca8d689d2e80182e 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -10,6 +10,9 @@ Version 2.9.7
   (`#718`_)
 - Resolved a bug where getting debug locals for tracebacks could
   modify template context.
+- Fixed a bug where having many `{% elif ... %}` blocks resulted in a
+  "too many levels of indentation" error.  These blocks now compile to
+  native `elif ..:` instead of `else: if ..:`  (#759)
 
 .. _#718: https://github.com/pallets/jinja/pull/718
 
index b2ab6fe6a899e2f50aae468ac3aacab08d017adf..30826f34085db06ee63b73838ae59ca73810c406 100644 (file)
@@ -1129,6 +1129,13 @@ class CodeGenerator(NodeVisitor):
         self.indent()
         self.blockvisit(node.body, if_frame)
         self.outdent()
+        for elif_ in node.elif_:
+            self.writeline('elif ', elif_)
+            self.visit(elif_.test, if_frame)
+            self.write(':')
+            self.indent()
+            self.blockvisit(elif_.body, if_frame)
+            self.outdent()
         if node.else_:
             self.writeline('else:')
             self.indent()
index 8479b72c2309c6bf8db7cee4c160f10c9a824fdf..30e348de44c6c1b88649e5ce17f5995d02db875d 100644 (file)
@@ -222,9 +222,10 @@ class FrameSymbolVisitor(NodeVisitor):
             return rv
 
         body_symbols = inner_visit(node.body)
+        elif_symbols = inner_visit(node.elif_)
         else_symbols = inner_visit(node.else_ or ())
 
-        self.symbols.branch_update([body_symbols, else_symbols])
+        self.symbols.branch_update([body_symbols, elif_symbols, else_symbols])
 
     def visit_Macro(self, node, **kwargs):
         self.symbols.store(node.name)
index aa4df72380aae59c04df28bfa907a2a2bf7c482e..59e07651257df0a7bd1b0afd2ccf69d9810855c8 100644 (file)
@@ -314,7 +314,7 @@ class For(Stmt):
 
 class If(Stmt):
     """If `test` is true, `body` is rendered, else `else_`."""
-    fields = ('test', 'body', 'else_')
+    fields = ('test', 'body', 'elif_', 'else_')
 
 
 class Macro(Stmt):
index 0bf74c9459be00c325b12be338f4925bb6bfcfe2..70ab7ca779a810dd3329c64be6129ec67bdbaaa6 100644 (file)
@@ -210,17 +210,16 @@ class Parser(object):
             node.test = self.parse_tuple(with_condexpr=False)
             node.body = self.parse_statements(('name:elif', 'name:else',
                                                'name:endif'))
+            node.elif_ = []
+            node.else_ = []
             token = next(self.stream)
             if token.test('name:elif'):
-                new_node = nodes.If(lineno=self.stream.current.lineno)
-                node.else_ = [new_node]
-                node = new_node
+                node = nodes.If(lineno=self.stream.current.lineno)
+                result.elif_.append(node)
                 continue
             elif token.test('name:else'):
-                node.else_ = self.parse_statements(('name:endif',),
-                                                   drop_needle=True)
-            else:
-                node.else_ = []
+                result.else_ = self.parse_statements(('name:endif',),
+                                                     drop_needle=True)
             break
         return result
 
index f48d8b4483b947ad5269373725ee042b1a581d83..19b849e2f3fdb9c6be5f5f7ad472456e5ed5be1a 100644 (file)
@@ -222,6 +222,15 @@ class TestIfCondition(object):
             %}...{% else %}XXX{% endif %}''')
         assert tmpl.render() == '...'
 
+    def test_elif_deep(self, env):
+        elifs = '\n'.join('{{% elif a == {0} %}}{0}'.format(i)
+                          for i in range(1, 1000))
+        tmpl = env.from_string('{{% if a == 0 %}}0{0}{{% else %}}x{{% endif %}}'
+                               .format(elifs))
+        for x in (0, 10, 999):
+            assert tmpl.render(a=x).strip() == str(x)
+        assert tmpl.render(a=1000).strip() == 'x'
+
     def test_else(self, env):
         tmpl = env.from_string('{% if false %}XXX{% else %}...{% endif %}')
         assert tmpl.render() == '...'
index ea01b17efb079c40d74591e48c41997e9d073780..29312d3e3d35dd391e3df78d410d0ec4fc3535db 100644 (file)
@@ -61,7 +61,7 @@ def test_complex():
                     nodes.Output([
                         nodes.Name('title_upper', 'load'),
                         nodes.Call(nodes.Name('render_title', 'load'), [
-                            nodes.Const('Aha')], [], None, None)])], [])])])
+                            nodes.Const('Aha')], [], None, None)])], [], [])])])
 
     for_loop = nodes.For(
         nodes.Name('item', 'store'),
@@ -155,7 +155,7 @@ def test_if_branching_stores():
     tmpl = nodes.Template([
         nodes.If(nodes.Name('expression', 'load'), [
             nodes.Assign(nodes.Name('variable', 'store'),
-                         nodes.Const(42))], [])])
+                         nodes.Const(42))], [], [])])
 
     sym = symbols_for_node(tmpl)
     assert sym.refs == {
@@ -177,7 +177,7 @@ def test_if_branching_stores_undefined():
         nodes.Assign(nodes.Name('variable', 'store'), nodes.Const(23)),
         nodes.If(nodes.Name('expression', 'load'), [
             nodes.Assign(nodes.Name('variable', 'store'),
-                         nodes.Const(42))], [])])
+                         nodes.Const(42))], [], [])])
 
     sym = symbols_for_node(tmpl)
     assert sym.refs == {
@@ -197,7 +197,7 @@ def test_if_branching_stores_undefined():
 def test_if_branching_multi_scope():
     for_loop = nodes.For(nodes.Name('item', 'store'), nodes.Name('seq', 'load'), [
         nodes.If(nodes.Name('expression', 'load'), [
-            nodes.Assign(nodes.Name('x', 'store'), nodes.Const(42))], []),
+            nodes.Assign(nodes.Name('x', 'store'), nodes.Const(42))], [], []),
         nodes.Include(nodes.Const('helper.html'), True, False)
     ], [], None, False)