there is now a workaround in the compiler that makes sure it's possible to call things with python keywords.  {{ foo(class=42) }} works again

--HG--
branch : trunk
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index bc5163b..8541cba 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -10,7 +10,9 @@
 """
 from copy import copy
 from random import randrange
+from keyword import iskeyword
 from cStringIO import StringIO
+from itertools import chain
 from jinja2 import nodes
 from jinja2.visitor import NodeVisitor, NodeTransformer
 from jinja2.exceptions import TemplateAssertionError
@@ -163,14 +165,19 @@
         rv.name_overrides = self.name_overrides.copy()
         return rv
 
-    def inspect(self, nodes, hard_scope=False):
-        """Walk the node and check for identifiers.  If the scope
-        is hard (eg: enforce on a python level) overrides from outer
-        scopes are tracked differently.
+    def inspect(self, nodes, with_depenencies=False, hard_scope=False):
+        """Walk the node and check for identifiers.  If the scope is hard (eg:
+        enforce on a python level) overrides from outer scopes are tracked
+        differently.
+
+        Per default filters and tests (dependencies) are not tracked.  That's
+        the case because filters and tests are absolutely immutable and so we
+        can savely use them in closures too.  The `Template` and `Block`
+        visitor visits the frame with dependencies to collect them.
         """
         visitor = FrameIdentifierVisitor(self.identifiers, hard_scope)
         for node in nodes:
-            visitor.visit(node)
+            visitor.visit(node, True, with_depenencies)
 
     def inner(self):
         """Return an inner frame."""
@@ -193,41 +200,63 @@
         self.identifiers = identifiers
         self.hard_scope = hard_scope
 
-    def visit_Name(self, node):
+    def visit_Name(self, node, visit_ident, visit_deps):
         """All assignments to names go through this function."""
-        if node.ctx in ('store', 'param'):
-            self.identifiers.declared_locally.add(node.name)
-        elif node.ctx == 'load':
-            if not self.identifiers.is_declared(node.name, self.hard_scope):
+        if visit_ident:
+            if node.ctx in ('store', 'param'):
+                self.identifiers.declared_locally.add(node.name)
+            elif node.ctx == 'load' and not \
+                 self.identifiers.is_declared(node.name, self.hard_scope):
                 self.identifiers.undeclared.add(node.name)
 
-    def visit_Filter(self, node):
-        self.generic_visit(node)
-        self.identifiers.filters.add(node.name)
+    def visit_Filter(self, node, visit_ident, visit_deps):
+        if visit_deps:
+            self.generic_visit(node, visit_ident, True)
+            self.identifiers.filters.add(node.name)
 
-    def visit_Test(self, node):
-        self.generic_visit(node)
-        self.identifiers.tests.add(node.name)
+    def visit_Test(self, node, visit_ident, visit_deps):
+        if visit_deps:
+            self.generic_visit(node, visit_ident, True)
+            self.identifiers.tests.add(node.name)
 
-    def visit_Macro(self, node):
-        self.identifiers.declared_locally.add(node.name)
+    def visit_Macro(self, node, visit_ident, visit_deps):
+        if visit_ident:
+            self.identifiers.declared_locally.add(node.name)
 
-    def visit_Import(self, node):
-        self.generic_visit(node)
-        self.identifiers.declared_locally.add(node.target)
+    def visit_Import(self, node, visit_ident, visit_deps):
+        if visit_ident:
+            self.generic_visit(node, True, visit_deps)
+            self.identifiers.declared_locally.add(node.target)
 
-    def visit_FromImport(self, node):
-        self.generic_visit(node)
-        self.identifiers.declared_locally.update(node.names)
+    def visit_FromImport(self, node, visit_ident, visit_deps):
+        if visit_ident:
+            self.generic_visit(node, True, visit_deps)
+            for name in node.names:
+                if isinstance(name, tuple):
+                    self.identifiers.declared_locally.add(name[1])
+                else:
+                    self.identifiers.declared_locally.add(name)
 
-    def visit_Assign(self, node):
+    def visit_Assign(self, node, visit_ident, visit_deps):
         """Visit assignments in the correct order."""
-        self.visit(node.node)
-        self.visit(node.target)
+        self.visit(node.node, visit_ident, visit_deps)
+        self.visit(node.target, visit_ident, visit_deps)
 
-    # stop traversing at instructions that have their own scope.
-    visit_Block = visit_CallBlock = visit_FilterBlock = \
-        visit_For = lambda s, n: None
+    def visit_For(self, node, visit_ident, visit_deps):
+        """Visiting stops at for blocks.  However the block sequence
+        is visited as part of the outer scope.
+        """
+        if visit_ident:
+            self.visit(node.iter, True, visit_deps)
+            if visit_deps:
+                for child in node.iter_child_nodes(exclude=('iter',)):
+                    self.visit(child, False, True)
+
+    def ident_stop(self, node, visit_ident, visit_deps):
+        if visit_deps:
+            self.generic_visit(node, False, True)
+    visit_CallBlock = visit_FilterBlock = ident_stop
+    visit_Block = lambda s, n, a, b: None
 
 
 class CompilerExit(Exception):
@@ -344,10 +373,10 @@
     def signature(self, node, frame, have_comma=True, extra_kwargs=None):
         """Writes a function call to the stream for the current node.
         Per default it will write a leading comma but this can be
-        disabled by setting have_comma to False.  If extra_kwargs is
-        given it must be a string that represents a single keyword
-        argument call that is inserted at the end of the regular
-        keyword argument calls.
+        disabled by setting have_comma to False.  The extra keyword
+        arguments may not include python keywords otherwise a syntax
+        error could occour.  The extra keyword arguments should be given
+        as python dict.
         """
         have_comma = have_comma and [True] or []
         def touch_comma():
@@ -356,20 +385,53 @@
             else:
                 have_comma.append(True)
 
+        # if any of the given keyword arguments is a python keyword
+        # we have to make sure that no invalid call is created.
+        kwarg_workaround = False
+        for kwarg in chain((x.key for x in node.kwargs), extra_kwargs or ()):
+            if iskeyword(kwarg):
+                kwarg_workaround = True
+                break
+
         for arg in node.args:
             touch_comma()
             self.visit(arg, frame)
-        for kwarg in node.kwargs:
-            touch_comma()
-            self.visit(kwarg, frame)
-        if extra_kwargs is not None:
-            touch_comma()
-            self.write(extra_kwargs)
+
+        if not kwarg_workaround:
+            for kwarg in node.kwargs:
+                touch_comma()
+                self.visit(kwarg, frame)
+            if extra_kwargs is not None:
+                for key, value in extra_kwargs.iteritems():
+                    touch_comma()
+                    self.write('%s=%s' % (key, value))
         if node.dyn_args:
             touch_comma()
             self.write('*')
             self.visit(node.dyn_args, frame)
-        if node.dyn_kwargs:
+
+        if kwarg_workaround:
+            touch_comma()
+            if node.dyn_kwargs is not None:
+                self.write('**dict({')
+            else:
+                self.write('**{')
+            for kwarg in node.kwargs:
+                self.write('%r: ' % kwarg.key)
+                self.visit(kwarg.value, frame)
+                self.write(', ')
+            if extra_kwargs is not None:
+                for key, value in extra_kwargs.iteritems():
+                    touch_comma()
+                    self.write('%r: %s, ' % (key, value))
+            if node.dyn_kwargs is not None:
+                self.write('}, **')
+                self.visit(node.dyn_kwargs, frame)
+                self.write(')')
+            else:
+                self.write('}')
+
+        elif node.dyn_kwargs is not None:
             touch_comma()
             self.write('**')
             self.visit(node.dyn_kwargs, frame)
@@ -448,6 +510,10 @@
         func_frame.accesses_caller = False
         func_frame.arguments = args = ['l_' + x.name for x in node.args]
 
+        if 'caller' in func_frame.identifiers.undeclared:
+            func_frame.accesses_caller = True
+            func_frame.identifiers.add_special('caller')
+            args.append('l_caller')
         if 'kwargs' in func_frame.identifiers.undeclared:
             func_frame.accesses_kwargs = True
             func_frame.identifiers.add_special('kwargs')
@@ -456,17 +522,14 @@
             func_frame.accesses_varargs = True
             func_frame.identifiers.add_special('varargs')
             args.append('l_varargs')
-        if 'caller' in func_frame.identifiers.undeclared:
-            func_frame.accesses_caller = True
-            func_frame.identifiers.add_special('caller')
-            args.append('l_caller')
         return func_frame
 
     # -- Visitors
 
     def visit_Template(self, node, frame=None):
         assert frame is None, 'no root frame allowed'
-        self.writeline('from jinja2.runtime import *')
+        from jinja2.runtime import __all__ as exported
+        self.writeline('from jinja2.runtime import ' + ', '.join(exported))
         self.writeline('name = %r' % self.name)
 
         # do we have an extends tag at all?  If not, we can save some
@@ -491,7 +554,7 @@
 
         # process the root
         frame = Frame()
-        frame.inspect(node.body)
+        frame.inspect(node.body, with_depenencies=True)
         frame.toplevel = frame.rootlevel = True
         self.indent()
         self.pull_locals(frame, indent=False)
@@ -513,7 +576,7 @@
         # at this point we now have the blocks collected and can visit them too.
         for name, block in self.blocks.iteritems():
             block_frame = Frame()
-            block_frame.inspect(block.body)
+            block_frame.inspect(block.body, with_depenencies=True)
             block_frame.block = name
             block_frame.identifiers.add_special('super')
             block_frame.name_overrides['super'] = 'context.super(%r, ' \
@@ -627,21 +690,25 @@
         self.visit(node.template, frame)
         self.write(', %r).include(context)' % self.name)
         for name in node.names:
+            if isinstance(name, tuple):
+                name, alias = name
+            else:
+                alias = name
             self.writeline('l_%s = getattr(included_template, '
-                           '%r, missing)' % (name, name))
-            self.writeline('if l_%s is missing:' % name)
+                           '%r, missing)' % (alias, name))
+            self.writeline('if l_%s is missing:' % alias)
             self.indent()
             self.writeline('l_%s = environment.undefined(%r %% '
                            'included_template.name)' %
-                           (name, 'the template %r does not export '
+                           (alias, 'the template %r does not export '
                             'the requested name ' + repr(name)))
             self.outdent()
             if frame.toplevel:
-                self.writeline('context[%r] = l_%s' % (name, name))
+                self.writeline('context[%r] = l_%s' % (alias, alias))
 
     def visit_For(self, node, frame):
         loop_frame = frame.inner()
-        loop_frame.inspect(node.iter_child_nodes())
+        loop_frame.inspect(node.iter_child_nodes(exclude=('iter',)))
         extended_loop = bool(node.else_) or \
                         'loop' in loop_frame.identifiers.undeclared
         if extended_loop:
@@ -774,7 +841,8 @@
             self.writeline('yield ', node)
         else:
             self.writeline('%s.append(' % frame.buffer, node)
-        self.visit_Call(node.call, call_frame, extra_kwargs='caller=caller')
+        self.visit_Call(node.call, call_frame,
+                        extra_kwargs={'caller': 'caller'})
         if frame.buffer is not None:
             self.write(')')