Skip to content

Commit 3d0cadb

Browse files
authored
Merge pull request #71 from dflook/comp-iter-namespace
2 parents a585aee + 9b15275 commit 3d0cadb

File tree

5 files changed

+121
-8
lines changed

5 files changed

+121
-8
lines changed

src/python_minifier/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
allow_rename_locals,
1919
add_namespace,
2020
)
21+
2122
from python_minifier.transforms.combine_imports import CombineImports
2223
from python_minifier.transforms.remove_annotations import RemoveAnnotations
2324
from python_minifier.transforms.remove_asserts import RemoveAsserts

src/python_minifier/expression_printer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def __init__(self):
1616
self.unicode_literals = False
1717

1818
self.precedences = {
19-
'Lambda': 2, # Lamdda
19+
'Lambda': 2, # Lambda
2020
'IfExp': 3, # IfExp
2121
'comprehension': 3.5,
2222
'Or': 4, # BoolOp

src/python_minifier/rename/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ This is usually the closest parent namespace node. The exceptions are:
4040
- Function annotations are in the same namespace as their function.
4141
- Class decorator are in the same namespace as their class.
4242
- Class bases, keywords, starargs and kwargs are in the same namespace as their class.
43+
- The first iteration expression of a comprehension is in the same namespace as it's parent ListComp/SetComp/DictComp or GeneratorExp.
4344

4445
### Bind names
4546

src/python_minifier/rename/mapper.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,26 @@ def add_parent_to_classdef(classdef):
8989
for node in classdef.decorator_list:
9090
add_parent(node, parent=classdef, namespace=classdef.namespace)
9191

92+
def add_parent_to_comprehension(node, namespace):
93+
assert is_ast_node(node, (ast.GeneratorExp, 'SetComp', 'DictComp', 'ListComp'))
94+
95+
if hasattr(node, 'elt'):
96+
add_parent(node.elt, parent=node, namespace=node)
97+
elif hasattr(node, 'key'):
98+
add_parent(node.key, parent=node, namespace=node)
99+
add_parent(node.value, parent=node, namespace=node)
100+
101+
iter_namespace = namespace
102+
for generator in node.generators:
103+
generator.parent = node
104+
generator.namespace = node
105+
106+
add_parent(generator.target, parent=generator, namespace=node)
107+
add_parent(generator.iter, parent=generator, namespace=iter_namespace)
108+
iter_namespace = node
109+
for if_ in generator.ifs:
110+
add_parent(if_, parent=generator, namespace=node)
111+
92112

93113
def add_parent(node, parent=None, namespace=None):
94114
"""
@@ -114,6 +134,8 @@ def add_parent(node, parent=None, namespace=None):
114134

115135
if is_ast_node(node, (ast.FunctionDef, 'AsyncFunctionDef')):
116136
add_parent_to_functiondef(node)
137+
elif is_ast_node(node, (ast.GeneratorExp, 'SetComp', 'DictComp', 'ListComp')):
138+
add_parent_to_comprehension(node, namespace=namespace)
117139
elif isinstance(node, ast.Lambda):
118140
add_parent_to_arguments(node.args, func=node)
119141
add_parent(node.body, parent=node, namespace=node)
@@ -125,13 +147,6 @@ def add_parent(node, parent=None, namespace=None):
125147

126148
return
127149

128-
if isinstance(node, ast.comprehension):
129-
add_parent(node.target, parent=node, namespace=namespace)
130-
add_parent(node.iter, parent=node, namespace=namespace)
131-
for if_ in node.ifs:
132-
add_parent(if_, parent=node, namespace=namespace)
133-
return
134-
135150
if isinstance(node, ast.Global):
136151
namespace.global_names.update(node.names)
137152
if is_ast_node(node, 'Nonlocal'):

test/test_comprehension_rename.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import ast
2+
import sys
3+
4+
import pytest
5+
6+
from python_minifier import minify
7+
from python_minifier.ast_compare import compare_ast
8+
9+
def test_listcomp_regression_2_7():
10+
if sys.version_info >= (3, 0):
11+
pytest.skip('ListComp doesn\'t create a new namespace in python < 3.0')
12+
13+
source = '''
14+
def f(pa):
15+
return [pa.b for pa in pa.c]
16+
'''
17+
expected = source
18+
compare_ast(ast.parse(minify(source, rename_locals=True)), ast.parse(expected))
19+
20+
def test_listcomp_regression():
21+
if sys.version_info < (3, 0):
22+
pytest.skip('ListComp creates a new namespace in python > 3.0')
23+
24+
source = '''
25+
def f(parentObject):
26+
return [parentObject.b for parentObject in parentObject.c]
27+
'''
28+
expected = '''
29+
def f(parentObject):
30+
return[A.b for A in parentObject.c]
31+
'''
32+
compare_ast(ast.parse(minify(source, rename_locals=True)), ast.parse(expected))
33+
34+
35+
def test_expression():
36+
source = '''def test():
37+
[x*y for x in range(10) for y in range(x, x+10)]
38+
'''
39+
40+
expected = '''def test():
41+
[A*B for A in range(10) for B in range(A,A+10)]
42+
'''
43+
compare_ast(ast.parse(minify(source, rename_locals=True)), ast.parse(expected))
44+
45+
def test_generator_expression():
46+
source = '''
47+
x=1
48+
def func():
49+
return (x for x in x)
50+
'''
51+
52+
expected = '''
53+
x=1
54+
def func():
55+
return (A for A in x)
56+
'''
57+
58+
compare_ast(ast.parse(minify(source, rename_locals=True)), ast.parse(expected))
59+
60+
def test_generator_expression_multiple_for():
61+
source = '''
62+
def func():
63+
return (x for x in x for x in x)
64+
65+
def func(long_name, another_long_name):
66+
return (long_name for long_name, another_long_name in long_name for long_name in (long_name, another_long_name))
67+
'''
68+
69+
expected = '''
70+
def func():
71+
return (A for A in x for A in A)
72+
73+
def func(long_name, another_long_name):
74+
return(A for A, B in long_name for A in (A,B))
75+
'''
76+
77+
compare_ast(ast.parse(minify(source, rename_locals=True)), ast.parse(expected))
78+
79+
def test_generator_expression_nested_for():
80+
source = '''
81+
def func():
82+
return (a for a in (b for b in x) for c in c)
83+
84+
def func(long_name):
85+
return (a for a in (b for b in long_name) for c in c)
86+
'''
87+
88+
expected = '''
89+
def func():
90+
return(A for A in (A for A in x) for B in B)
91+
92+
def func(long_name):
93+
return(A for A in (A for A in long_name) for B in B)
94+
'''
95+
96+
compare_ast(ast.parse(minify(source, rename_locals=True)), ast.parse(expected))

0 commit comments

Comments
 (0)