Made it possible to refer to names from outer scopes in included templates
authorArmin Ronacher <armin.ronacher@active-4.com>
Sun, 8 Feb 2009 18:11:44 +0000 (19:11 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sun, 8 Feb 2009 18:11:44 +0000 (19:11 +0100)
that were unused in the callers frame (#327).

--HG--
branch : trunk

CHANGES
jinja2/compiler.py
tests/test_imports.py

diff --git a/CHANGES b/CHANGES
index edbdb0e26ad0ae2ea4a57fc36d8609dfc3ffb7c8..0f657309ca69a8252d2e9d458d4bcaa8f3f71403 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -13,6 +13,8 @@ Version 2.2
 - Fixed a bug that caused syntax errors when defining macros or using the
   `{% call %}` tag inside loops.
 - Fixed a bug in the parser that made ``{{ foo[1, 2] }}`` impossible.
+- Made it possible to refer to names from outer scopes in included templates
+  that were unused in the callers frame (#327)
 
 Version 2.1.1
 -------------
index 8d605c725b675307ac85e93b885b9e2d056aaab1..b6bf2ebade24b78ab835abc61a0b2756e0bf1cab 100644 (file)
@@ -520,6 +520,16 @@ class CodeGenerator(NodeVisitor):
                 self.writeline('%s = environment.%s[%r]' %
                                (mapping[name], dependency, name))
 
+    def unoptimize_scope(self, frame):
+        """Disable Python optimizations for the frame."""
+        # XXX: this is not that nice but it has no real overhead.  It
+        # mainly works because python finds the locals before dead code
+        # is removed.  If that breaks we have to add a dummy function
+        # that just accepts the arguments and does nothing.
+        if frame.identifiers.declared:
+            self.writeline('if 0: dummy(%s)' % ', '.join(
+                'l_' + name for name in frame.identifiers.declared))
+
     def push_scope(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
@@ -821,6 +831,8 @@ class CodeGenerator(NodeVisitor):
 
     def visit_Include(self, node, frame):
         """Handles includes."""
+        if node.with_context:
+            self.unoptimize_scope(frame)
         if node.ignore_missing:
             self.writeline('try:')
             self.indent()
@@ -852,6 +864,8 @@ class CodeGenerator(NodeVisitor):
 
     def visit_Import(self, node, frame):
         """Visit regular imports."""
+        if node.with_context:
+            self.unoptimize_scope(frame)
         self.writeline('l_%s = ' % node.target, node)
         if frame.toplevel:
             self.write('context.vars[%r] = ' % node.target)
index a0bb0322fdbb5417201478cde907cc468fc841f0..f5b227f23fcec5d28614068f63711a0708735678 100644 (file)
@@ -13,7 +13,8 @@ from jinja2.exceptions import TemplateNotFound
 
 test_env = Environment(loader=DictLoader(dict(
     module='{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}',
-    header='[{{ foo }}|{{ 23 }}]'
+    header='[{{ foo }}|{{ 23 }}]',
+    o_printer='({{ o }})'
 )))
 test_env.globals['bar'] = 23
 
@@ -80,3 +81,16 @@ def test_exports():
     assert not hasattr(m, '__missing')
     assert m.variable == 42
     assert not hasattr(m, 'notthere')
+
+
+def test_unoptimized_scopes():
+    t = test_env.from_string("""
+        {% macro outer(o) %}
+        {% macro inner() %}
+        {% include "o_printer" %}
+        {% endmacro %}
+        {{ inner() }}
+        {% endmacro %}
+        {{ outer("FOO") }}
+    """)
+    assert t.render().strip() == '(FOO)'