Skip to content

Commit bfe655b

Browse files
authored
Merge pull request #120 from dflook/fix-namedexpr-scope
Fix NamedExpr scope
2 parents 2714b91 + 99616a9 commit bfe655b

File tree

4 files changed

+283
-3
lines changed

4 files changed

+283
-3
lines changed

.github/workflows/xtest.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ jobs:
3232
with:
3333
image: danielflook/python-minifier-build:${{ matrix.python }}-2024-09-15
3434
run: |
35-
exit 0
3635
3736
if [[ "${{ matrix.python }}" == "python3.4" ]]; then
3837
(cd /usr/lib64/python3.4/test && python3.4 make_ssl_certs.py)

src/python_minifier/rename/mapper.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,27 @@ def add_parent_to_comprehension(node, namespace):
112112

113113
add_parent(generator.target, namespace=node)
114114
add_parent(generator.iter, namespace=iter_namespace)
115-
iter_namespace = node
115+
116116
for if_ in generator.ifs:
117117
add_parent(if_, namespace=node)
118118

119+
iter_namespace = node
120+
121+
def namedexpr_namespace(node):
122+
"""
123+
Get the namespace for a NamedExpr target
124+
"""
125+
126+
if not isinstance(node, (ast.ListComp, ast.DictComp, ast.SetComp, ast.GeneratorExp)):
127+
return node
128+
129+
return namedexpr_namespace(node.namespace)
130+
131+
def add_parent_to_namedexpr(node):
132+
assert isinstance(node, ast.NamedExpr)
133+
134+
add_parent(node.target, namespace=namedexpr_namespace(node.namespace))
135+
add_parent(node.value, namespace=node.namespace)
119136

120137
def add_parent(node, namespace=None):
121138
"""
@@ -161,6 +178,11 @@ def add_parent(node, namespace=None):
161178
elif isinstance(node.ctx, ast.Store) and isinstance(get_parent(node), ast.AugAssign):
162179
namespace.nonlocal_names.add(node.id)
163180

181+
if isinstance(node, ast.NamedExpr):
182+
# NamedExpr is 'special'
183+
add_parent_to_namedexpr(node)
184+
return
185+
164186
for child in ast.iter_child_nodes(node):
165187
add_parent(child, namespace=namespace)
166188

test/helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def namespace_name(node):
4343
for name in sorted(namespace.nonlocal_names):
4444
s += indent + ' - nonlocal ' + name + '\n'
4545

46-
for binding in sorted(namespace.bindings, key=lambda b: b.name):
46+
for binding in sorted(namespace.bindings, key=lambda b: b.name or str(b.value)):
4747
s += indent + ' - ' + repr(binding) + '\n'
4848

4949
for child in iter_child_namespaces(namespace):

test/test_bind_names_namedexpr.py

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
import sys
2+
3+
import pytest
4+
5+
from helpers import assert_namespace_tree
6+
7+
def test_namedexpr_in_module():
8+
if sys.version_info < (3, 8):
9+
pytest.skip('Test is for >= python3.8 only')
10+
11+
source = '''
12+
(a := 1)
13+
'''
14+
15+
expected_namespaces = '''
16+
+ Module
17+
- NameBinding(name='a', allow_rename=True) <references=1>
18+
'''
19+
20+
assert_namespace_tree(source, expected_namespaces)
21+
22+
def test_namedexpr_in_function():
23+
if sys.version_info < (3, 8):
24+
pytest.skip('Test is for >= python3.8 only')
25+
26+
source = '''
27+
def test():
28+
(a := 1)
29+
lambda x: (x := 1)
30+
'''
31+
32+
expected_namespaces = '''
33+
+ Module
34+
- NameBinding(name='test', allow_rename=True) <references=1>
35+
+ Function test
36+
- NameBinding(name='a', allow_rename=True) <references=1>
37+
+ Lambda
38+
- NameBinding(name='x', allow_rename=False) <references=2>
39+
'''
40+
41+
assert_namespace_tree(source, expected_namespaces)
42+
43+
def test_namedexpr_in_listcomp_if_nonlocal():
44+
if sys.version_info < (3, 8):
45+
pytest.skip('Test is for >= python3.8 only')
46+
47+
source = '''
48+
def f(arg, /):
49+
nonlocal x
50+
print([x for y in range(10) if (x := y // 2) & 1])
51+
print(arg, arg)
52+
'''
53+
54+
expected_namespaces = '''
55+
+ Module
56+
- NameBinding(name='f', allow_rename=True) <references=1>
57+
- BuiltinBinding(name='print', allow_rename=True) <references=2>
58+
- BuiltinBinding(name='range', allow_rename=True) <references=1>
59+
- NameBinding(name='x', allow_rename=False) <references=3>
60+
+ Function f
61+
- nonlocal x
62+
- NameBinding(name='arg', allow_rename=True) <references=3>
63+
+ ListComp
64+
- NameBinding(name='y', allow_rename=True) <references=2>
65+
'''
66+
67+
assert_namespace_tree(source, expected_namespaces)
68+
69+
70+
def test_namedexpr_in_listcomp_if_global():
71+
if sys.version_info < (3, 8):
72+
pytest.skip('Test is for >= python3.8 only')
73+
74+
source = '''
75+
def f2():
76+
def f(arg, /):
77+
global x
78+
print([x for y in range(10) if (x := y // 2) & 1])
79+
print(arg, arg)
80+
'''
81+
82+
expected_namespaces = '''
83+
+ Module
84+
- NameBinding(name='f2', allow_rename=True) <references=1>
85+
- BuiltinBinding(name='print', allow_rename=True) <references=2>
86+
- BuiltinBinding(name='range', allow_rename=True) <references=1>
87+
- NameBinding(name='x', allow_rename=True) <references=3>
88+
+ Function f2
89+
- NameBinding(name='f', allow_rename=True) <references=1>
90+
+ Function f
91+
- global x
92+
- NameBinding(name='arg', allow_rename=True) <references=3>
93+
+ ListComp
94+
- NameBinding(name='y', allow_rename=True) <references=2>
95+
'''
96+
97+
assert_namespace_tree(source, expected_namespaces)
98+
99+
100+
def test_namedexpr_in_listcomp_if():
101+
if sys.version_info < (3, 8):
102+
pytest.skip('Test is for >= python3.8 only')
103+
104+
source = '''
105+
def f(arg, /):
106+
print([x for y in range(10) if (x := y // 2) & 1])
107+
print(arg, arg)
108+
'''
109+
110+
expected_namespaces = '''
111+
+ Module
112+
- NameBinding(name='f', allow_rename=True) <references=1>
113+
- BuiltinBinding(name='print', allow_rename=True) <references=2>
114+
- BuiltinBinding(name='range', allow_rename=True) <references=1>
115+
+ Function f
116+
- NameBinding(name='arg', allow_rename=True) <references=3>
117+
- NameBinding(name='x', allow_rename=True) <references=2>
118+
+ ListComp
119+
- NameBinding(name='y', allow_rename=True) <references=2>
120+
'''
121+
122+
assert_namespace_tree(source, expected_namespaces)
123+
124+
125+
def test_namedexpr_in_listcomp_body():
126+
if sys.version_info < (3, 8):
127+
pytest.skip('Test is for >= python3.8 only')
128+
129+
source = '''
130+
def f(arg, /):
131+
print([(x := y // 2) for _ in range(x)])
132+
print(arg, arg)
133+
'''
134+
135+
expected_namespaces = '''
136+
+ Module
137+
- NameBinding(name='f', allow_rename=True) <references=1>
138+
- BuiltinBinding(name='print', allow_rename=True) <references=2>
139+
- BuiltinBinding(name='range', allow_rename=True) <references=1>
140+
- NameBinding(name='y', allow_rename=False) <references=1>
141+
+ Function f
142+
- NameBinding(name='arg', allow_rename=True) <references=3>
143+
- NameBinding(name='x', allow_rename=True) <references=2>
144+
+ ListComp
145+
- NameBinding(name='_', allow_rename=True) <references=1>
146+
'''
147+
148+
assert_namespace_tree(source, expected_namespaces)
149+
150+
def test_namedexpr_in_dictcomp_body():
151+
if sys.version_info < (3, 8):
152+
pytest.skip('Test is for >= python3.8 only')
153+
154+
source = '''
155+
{i: (x := i // 2) for i in range(1)}
156+
'''
157+
158+
expected_namespaces = '''
159+
+ Module
160+
- BuiltinBinding(name='range', allow_rename=True) <references=1>
161+
- NameBinding(name='x', allow_rename=True) <references=1>
162+
+ DictComp
163+
- NameBinding(name='i', allow_rename=True) <references=3>
164+
'''
165+
166+
assert_namespace_tree(source, expected_namespaces)
167+
168+
169+
def test_namedexpr_in_dictcomp_if():
170+
if sys.version_info < (3, 8):
171+
pytest.skip('Test is for >= python3.8 only')
172+
173+
source = '''
174+
{x: y for y in range(1) if (x := y // 2)}
175+
'''
176+
177+
expected_namespaces = '''
178+
+ Module
179+
- BuiltinBinding(name='range', allow_rename=True) <references=1>
180+
- NameBinding(name='x', allow_rename=True) <references=2>
181+
+ DictComp
182+
- NameBinding(name='y', allow_rename=True) <references=3>
183+
'''
184+
185+
assert_namespace_tree(source, expected_namespaces)
186+
187+
def test_namedexpr_in_setcomp_body():
188+
if sys.version_info < (3, 8):
189+
pytest.skip('Test is for >= python3.8 only')
190+
191+
source = '''
192+
{(x := y // 2) for y in range(1)}
193+
'''
194+
195+
expected_namespaces = '''
196+
+ Module
197+
- BuiltinBinding(name='range', allow_rename=True) <references=1>
198+
- NameBinding(name='x', allow_rename=True) <references=1>
199+
+ SetComp
200+
- NameBinding(name='y', allow_rename=True) <references=2>
201+
'''
202+
203+
assert_namespace_tree(source, expected_namespaces)
204+
205+
206+
def test_namedexpr_in_setcomp_if():
207+
if sys.version_info < (3, 8):
208+
pytest.skip('Test is for >= python3.8 only')
209+
210+
source = '''
211+
{x for y in range(1) if (x := y // 2)}
212+
'''
213+
214+
expected_namespaces = '''
215+
+ Module
216+
- BuiltinBinding(name='range', allow_rename=True) <references=1>
217+
- NameBinding(name='x', allow_rename=True) <references=2>
218+
+ SetComp
219+
- NameBinding(name='y', allow_rename=True) <references=2>
220+
'''
221+
222+
assert_namespace_tree(source, expected_namespaces)
223+
224+
def test_namedexpr_in_generatorexp_body():
225+
if sys.version_info < (3, 8):
226+
pytest.skip('Test is for >= python3.8 only')
227+
228+
source = '''
229+
((x := y // 2) for y in range(1))
230+
'''
231+
232+
expected_namespaces = '''
233+
+ Module
234+
- BuiltinBinding(name='range', allow_rename=True) <references=1>
235+
- NameBinding(name='x', allow_rename=True) <references=1>
236+
+ GeneratorExp
237+
- NameBinding(name='y', allow_rename=True) <references=2>
238+
'''
239+
240+
assert_namespace_tree(source, expected_namespaces)
241+
242+
243+
def test_namedexpr_in_generatorexp_if():
244+
if sys.version_info < (3, 8):
245+
pytest.skip('Test is for >= python3.8 only')
246+
247+
source = '''
248+
(x for y in range(1) if (x := y // 2))
249+
'''
250+
251+
expected_namespaces = '''
252+
+ Module
253+
- BuiltinBinding(name='range', allow_rename=True) <references=1>
254+
- NameBinding(name='x', allow_rename=True) <references=2>
255+
+ GeneratorExp
256+
- NameBinding(name='y', allow_rename=True) <references=2>
257+
'''
258+
259+
assert_namespace_tree(source, expected_namespaces)

0 commit comments

Comments
 (0)