# HG changeset patch # User cmlenz # Date 1186611681 0 # Node ID 5413c9d95db13cc88b58a0e25d5fa86df2a14868 # Parent e0d57ab9b0bed0395abcd093f29c71a49b8181d5 Fixes for nonlocal variable access in code blocks, as well as nested function and class definitions. diff --git a/genshi/template/eval.py b/genshi/template/eval.py --- a/genshi/template/eval.py +++ b/genshi/template/eval.py @@ -638,7 +638,7 @@ """ def __init__(self): - self.locals = [CONSTANTS, set()] + self.locals = [CONSTANTS] def visitConst(self, node): if isinstance(node.value, str): @@ -649,7 +649,7 @@ return node def visitAssName(self, node): - if self.locals: + if len(self.locals) > 1: self.locals[-1].add(node.name) return node @@ -670,6 +670,8 @@ return ASTTransformer.visitAugAssign(self, node) def visitClass(self, node): + if len(self.locals) > 1: + self.locals[-1].add(node.name) self.locals.append(set()) try: return ASTTransformer.visitClass(self, node) @@ -684,6 +686,8 @@ self.locals.pop() def visitFunction(self, node): + if len(self.locals) > 1: + self.locals[-1].add(node.name) self.locals.append(set(node.argnames)) try: return ASTTransformer.visitFunction(self, node) @@ -714,12 +718,11 @@ def visitName(self, node): # If the name refers to a local inside a lambda, list comprehension, or # generator expression, leave it alone - for frame in self.locals: - if node.name in frame: - return node - # Otherwise, translate the name ref into a context lookup - func_args = [ast.Name('data'), ast.Const(node.name)] - return ast.CallFunc(ast.Name('_lookup_name'), func_args) + if node.name not in flatten(self.locals): + # Otherwise, translate the name ref into a context lookup + func_args = [ast.Name('data'), ast.Const(node.name)] + node = ast.CallFunc(ast.Name('_lookup_name'), func_args) + return node class ExpressionASTTransformer(TemplateASTTransformer): diff --git a/genshi/template/tests/eval.py b/genshi/template/tests/eval.py --- a/genshi/template/tests/eval.py +++ b/genshi/template/tests/eval.py @@ -449,7 +449,8 @@ self.assertEqual(None, data['donothing']()) def test_def_with_multiple_statements(self): - suite = Suite("""def donothing(): + suite = Suite(""" +def donothing(): if True: return foo """) @@ -458,6 +459,35 @@ assert 'donothing' in data self.assertEqual('bar', data['donothing']()) + def test_def_using_nonlocal(self): + suite = Suite(""" +values = [] +def add(value): + if value not in values: + values.append(value) +add('foo') +add('bar') +""") + data = {} + suite.execute(data) + self.assertEqual(['foo', 'bar'], data['values']) + + def test_def_nested(self): + suite = Suite(""" +def doit(): + values = [] + def add(value): + if value not in values: + values.append(value) + add('foo') + add('bar') + return values +x = doit() +""") + data = {} + suite.execute(data) + self.assertEqual(['foo', 'bar'], data['x']) + def test_delete(self): suite = Suite("""foo = 42 del foo @@ -472,6 +502,19 @@ suite.execute(data) assert 'plain' in data + def test_class_in_def(self): + suite = Suite(""" +def create(): + class Foobar(object): + def __str__(self): + return 'foobar' + return Foobar() +x = create() +""") + data = {} + suite.execute(data) + self.assertEqual('foobar', str(data['x'])) + def test_class_with_methods(self): suite = Suite("""class plain(object): def donothing():