Skip to content

Commit 4a9c194

Browse files
committed
pythongh-87447: Fix walrus comprehension rebind checking
1 parent 2d52406 commit 4a9c194

File tree

3 files changed

+86
-3
lines changed

3 files changed

+86
-3
lines changed

Lib/test/test_named_expressions.py

+82-2
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,53 @@ def test_named_expression_invalid_rebinding_list_comprehension_iteration_variabl
124124
("Unreachable reuse", 'i', "[False or (i:=0) for i in range(5)]"),
125125
("Unreachable nested reuse", 'i',
126126
"[(i, j) for i in range(5) for j in range(5) if True or (i:=10)]"),
127+
# Regression tests from https://github.com/python/cpython/issues/87447
128+
("Complex expression: a", "a",
129+
"[(a := 1) for a, (*b, c[d+e::f(g)], h.i) in j]"),
130+
("Complex expression: b", "b",
131+
"[(b := 1) for a, (*b, c[d+e::f(g)], h.i) in j]"),
127132
]
128133
for case, target, code in cases:
129134
msg = f"assignment expression cannot rebind comprehension iteration variable '{target}'"
130135
with self.subTest(case=case):
131136
with self.assertRaisesRegex(SyntaxError, msg):
132-
exec(code, {}, {})
137+
exec(code, {}) # Module scope
138+
with self.assertRaisesRegex(SyntaxError, msg):
139+
exec(code, {}, {}) # Class scope
140+
with self.assertRaisesRegex(SyntaxError, msg):
141+
exec(f"lambda: {code}", {}) # Function scope
142+
143+
def test_named_expression_valid_rebinding_list_comprehension_iteration_variable(self):
144+
cases = [
145+
# Regression tests from https://github.com/python/cpython/issues/87447
146+
("Complex expression: c",
147+
"[(c := 1) for a, (*b, c[d+e::f(g)], h.i) in j]"),
148+
("Complex expression: d",
149+
"[(d := 1) for a, (*b, c[d+e::f(g)], h.i) in j]"),
150+
("Complex expression: e",
151+
"[(e := 1) for a, (*b, c[d+e::f(g)], h.i) in j]"),
152+
("Complex expression: f",
153+
"[(f := 1) for a, (*b, c[d+e::f(g)], h.i) in j]"),
154+
("Complex expression: g",
155+
"[(g := 1) for a, (*b, c[d+e::f(g)], h.i) in j]"),
156+
("Complex expression: h",
157+
"[(h := 1) for a, (*b, c[d+e::f(g)], h.i) in j]"),
158+
("Complex expression: i",
159+
"[(i := 1) for a, (*b, c[d+e::f(g)], h.i) in j]"),
160+
("Complex expression: j",
161+
"[(j := 1) for a, (*b, c[d+e::f(g)], h.i) in j]"),
162+
]
163+
for case, code in cases:
164+
with self.subTest(case=case):
165+
# Names used in snippets are not defined,
166+
# but we are fine with it: just must not be a SyntaxError.
167+
# Names used in snippets are not defined,
168+
# but we are fine with it: just must not be a SyntaxError.
169+
with self.assertRaises(NameError):
170+
exec(code, {}) # Module scope
171+
with self.assertRaises(NameError):
172+
exec(code, {}, {}) # Class scope
173+
exec(f"lambda: {code}", {}) # Function scope
133174

134175
def test_named_expression_invalid_rebinding_list_comprehension_inner_loop(self):
135176
cases = [
@@ -178,12 +219,51 @@ def test_named_expression_invalid_rebinding_set_comprehension_iteration_variable
178219
("Unreachable reuse", 'i', "{False or (i:=0) for i in range(5)}"),
179220
("Unreachable nested reuse", 'i',
180221
"{(i, j) for i in range(5) for j in range(5) if True or (i:=10)}"),
222+
# Regression tests from https://github.com/python/cpython/issues/87447
223+
("Complex expression: a", "a",
224+
"{(a := 1) for a, (*b, c[d+e::f(g)], h.i) in j}"),
225+
("Complex expression: b", "b",
226+
"{(b := 1) for a, (*b, c[d+e::f(g)], h.i) in j}"),
181227
]
182228
for case, target, code in cases:
183229
msg = f"assignment expression cannot rebind comprehension iteration variable '{target}'"
184230
with self.subTest(case=case):
185231
with self.assertRaisesRegex(SyntaxError, msg):
186-
exec(code, {}, {})
232+
exec(code, {}) # Module scope
233+
with self.assertRaisesRegex(SyntaxError, msg):
234+
exec(code, {}, {}) # Class scope
235+
with self.assertRaisesRegex(SyntaxError, msg):
236+
exec(f"lambda: {code}", {}) # Function scope
237+
238+
def test_named_expression_valid_rebinding_set_comprehension_iteration_variable(self):
239+
cases = [
240+
# Regression tests from https://github.com/python/cpython/issues/87447
241+
("Complex expression: c",
242+
"{(c := 1) for a, (*b, c[d+e::f(g)], h.i) in j}"),
243+
("Complex expression: d",
244+
"{(d := 1) for a, (*b, c[d+e::f(g)], h.i) in j}"),
245+
("Complex expression: e",
246+
"{(e := 1) for a, (*b, c[d+e::f(g)], h.i) in j}"),
247+
("Complex expression: f",
248+
"{(f := 1) for a, (*b, c[d+e::f(g)], h.i) in j}"),
249+
("Complex expression: g",
250+
"{(g := 1) for a, (*b, c[d+e::f(g)], h.i) in j}"),
251+
("Complex expression: h",
252+
"{(h := 1) for a, (*b, c[d+e::f(g)], h.i) in j}"),
253+
("Complex expression: i",
254+
"{(i := 1) for a, (*b, c[d+e::f(g)], h.i) in j}"),
255+
("Complex expression: j",
256+
"{(j := 1) for a, (*b, c[d+e::f(g)], h.i) in j}"),
257+
]
258+
for case, code in cases:
259+
with self.subTest(case=case):
260+
# Names used in snippets are not defined,
261+
# but we are fine with it: just must not be a SyntaxError.
262+
with self.assertRaises(NameError):
263+
exec(code, {}) # Module scope
264+
with self.assertRaises(NameError):
265+
exec(code, {}, {}) # Class scope
266+
exec(f"lambda: {code}", {}) # Function scope
187267

188268
def test_named_expression_invalid_rebinding_set_comprehension_inner_loop(self):
189269
cases = [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :exc:`SyntaxError` on comprehension rebind checking with names that are
2+
not actually redefined.

Python/symtable.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -1488,7 +1488,8 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e)
14881488
*/
14891489
if (ste->ste_comprehension) {
14901490
long target_in_scope = _PyST_GetSymbol(ste, target_name);
1491-
if (target_in_scope & DEF_COMP_ITER) {
1491+
if ((target_in_scope & DEF_COMP_ITER) &&
1492+
(target_in_scope & (DEF_LOCAL | DEF_GLOBAL))) {
14921493
PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_CONFLICT, target_name);
14931494
PyErr_RangedSyntaxLocationObject(st->st_filename,
14941495
e->lineno,

0 commit comments

Comments
 (0)