Skip to content

Commit 49b772c

Browse files
committed
fix Python 3.8 and 3.9 compatibility
Walrus inside subscript (without parentheses) was added in Python 3.10.
1 parent 26ed664 commit 49b772c

File tree

3 files changed

+56
-42
lines changed

3 files changed

+56
-42
lines changed

unpythonic/syntax/letdo.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ def dlet(tree, *, args, syntax, expander, **kw):
217217
218218
@dlet[x := 0]
219219
def count():
220-
(x := x + 1) # walrus requires parens here; or use `x << x + 1`
220+
(x := x + 1)
221221
return x
222222
assert count() == 1
223223
assert count() == 2
@@ -926,7 +926,12 @@ def _do0(tree):
926926
raise SyntaxError("do0 body: expected a sequence of comma-separated expressions") # pragma: no cover
927927
elts = tree.elts
928928
# Use `local[]` and `do[]` as hygienically captured macros.
929-
newelts = [q[a[_our_local][_do0_result := a[elts[0]]]], # noqa: F821, local[] defines it inside the do[].
929+
#
930+
# Python 3.8 and Python 3.9 require the parens around the walrus when used inside a subscript.
931+
# TODO: Remove the parens when we bump minimum Python to 3.10.
932+
# From https://docs.python.org/3/whatsnew/3.10.html:
933+
# Assignment expressions can now be used unparenthesized within set literals and set comprehensions, as well as in sequence indexes (but not slices).
934+
newelts = [q[a[_our_local][(_do0_result := a[elts[0]])]], # noqa: F821, local[] defines it inside the do[].
930935
*elts[1:],
931936
q[_do0_result]] # noqa: F821
932937
return q[a[_our_do][t[newelts]]] # do0[] is also just a do[]

unpythonic/syntax/tests/test_letdo.py

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ def runtests():
2626
# (including nested ``let`` constructs and similar).
2727
# - No need for ``lambda e: ...`` wrappers. Inserted automatically,
2828
# so the lines are only evaluated as the underlying seq.do() runs.
29-
d1 = do[local[x := 17],
29+
#
30+
# Python 3.8 and Python 3.9 require the parens around the walrus when used inside a subscript.
31+
# TODO: Remove the parens (in all walrus-inside-subscript instances in this file) when we bump minimum Python to 3.10.
32+
# From https://docs.python.org/3/whatsnew/3.10.html:
33+
# Assignment expressions can now be used unparenthesized within set literals and set comprehensions, as well as in sequence indexes (but not slices).
34+
d1 = do[local[(x := 17)],
3035
print(x),
3136
x := 23,
3237
x]
@@ -37,7 +42,7 @@ def runtests():
3742

3843
# v0.14.0: do[] now supports deleting previously defined local names with delete[]
3944
a = 5
40-
d = do[local[a := 17], # noqa: F841, yes, d is unused.
45+
d = do[local[(a := 17)], # noqa: F841, yes, d is unused.
4146
test[a == 17],
4247
delete[a],
4348
test[a == 5], # lexical scoping
@@ -46,7 +51,7 @@ def runtests():
4651
test_raises[KeyError, do[delete[a], ], "should have complained about deleting nonexistent local 'a'"]
4752

4853
# do0[]: like do[], but return the value of the **first** expression
49-
d2 = do0[local[y := 5], # noqa: F821, `local` defines the name on the LHS of the `<<`.
54+
d2 = do0[local[(y := 5)], # noqa: F821, `local` defines the name on the LHS of the `<<`.
5055
print("hi there, y =", y), # noqa: F821
5156
42] # evaluated but not used
5257
test[d2 == 5]
@@ -75,30 +80,30 @@ def runtests():
7580
# Let macros. Lexical scoping supported.
7681
with testset("let, letseq, letrec basic usage (new env-assignment syntax 0.15.3+)"):
7782
# parallel binding, i.e. bindings don't see each other
78-
test[let[x := 17,
79-
y := 23][ # noqa: F821, `let` defines `y` here.
83+
test[let[(x := 17),
84+
(y := 23)][ # noqa: F821, `let` defines `y` here.
8085
(x, y)] == (17, 23)] # noqa: F821
8186

8287
# sequential binding, i.e. Scheme/Racket let*
83-
test[letseq[x := 1,
84-
y := x + 1][ # noqa: F821
88+
test[letseq[(x := 1),
89+
(y := x + 1)][ # noqa: F821
8590
(x, y)] == (1, 2)] # noqa: F821
8691

87-
test[letseq[x := 1,
88-
x := x + 1][ # in a letseq, rebinding the same name is ok
92+
test[letseq[(x := 1),
93+
(x := x + 1)][ # in a letseq, rebinding the same name is ok
8994
x] == 2]
9095

9196
# letrec sugars unpythonic.lispylet.letrec, removing the need for quotes on LHS
9297
# and "lambda e: ..." wrappers on RHS (these are inserted by the macro):
93-
test[letrec[evenp := (lambda x: (x == 0) or oddp(x - 1)), # noqa: F821, `letrec` defines `evenp` here.
94-
oddp := (lambda x: (x != 0) and evenp(x - 1))][ # noqa: F821
98+
test[letrec[(evenp := (lambda x: (x == 0) or oddp(x - 1))), # noqa: F821, `letrec` defines `evenp` here.
99+
(oddp := (lambda x: (x != 0) and evenp(x - 1)))][ # noqa: F821
95100
evenp(42)] is True] # noqa: F821
96101

97102
# nested letrecs work, too - each environment is internally named by a gensym
98103
# so that outer ones "show through":
99-
test[letrec[z := 9000][ # noqa: F821
100-
letrec[evenp := (lambda x: (x == 0) or oddp(x - 1)), # noqa: F821
101-
oddp := (lambda x: (x != 0) and evenp(x - 1))][ # noqa: F821
104+
test[letrec[(z := 9000)][ # noqa: F821
105+
letrec[(evenp := (lambda x: (x == 0) or oddp(x - 1))), # noqa: F821
106+
(oddp := (lambda x: (x != 0) and evenp(x - 1)))][ # noqa: F821
102107
(evenp(42), z)]] == (True, 9000)] # noqa: F821
103108

104109
with testset("let, letseq, letrec basic usage (previous modern env-assignment syntax)"):
@@ -151,8 +156,8 @@ def runtests():
151156

152157
# implicit do: an extra set of brackets denotes a multi-expr body
153158
with testset("implicit do (extra bracket syntax for multi-expr let body) (new env-assignment syntax v0.15.3+)"):
154-
a = let[x := 1,
155-
y := 2][[ # noqa: F821
159+
a = let[(x := 1),
160+
(y := 2)][[ # noqa: F821
156161
y := 1337, # noqa: F821
157162
(x, y)]] # noqa: F821
158163
test[a == (1, 1337)]
@@ -164,14 +169,14 @@ def runtests():
164169
test[a == [1, 2]]
165170

166171
# implicit do works also in letseq, letrec
167-
a = letseq[x := 1,
168-
y := x + 1][[ # noqa: F821
172+
a = letseq[(x := 1),
173+
(y := x + 1)][[ # noqa: F821
169174
x := 1337,
170175
(x, y)]] # noqa: F821
171176
test[a == (1337, 2)]
172177

173-
a = letrec[x := 1,
174-
y := x + 1][[ # noqa: F821
178+
a = letrec[(x := 1),
179+
(y := x + 1)][[ # noqa: F821
175180
x := 1337,
176181
(x, y)]] # noqa: F821
177182
test[a == (1337, 2)]
@@ -486,23 +491,23 @@ def test14():
486491
x = "the nonlocal x" # restore the test environment
487492

488493
# v0.15.3+: walrus syntax
489-
@dlet[x := "the env x"]
494+
@dlet[(x := "the env x")]
490495
def test15():
491496
def inner():
492497
(x := "updated env x") # noqa: F841, this writes to the let env since there is no `x` in an intervening scope, according to Python's standard rules.
493498
inner()
494499
return x
495500
test[test15() == "updated env x"]
496501

497-
@dlet[x := "the env x"]
502+
@dlet[(x := "the env x")]
498503
def test16():
499504
def inner():
500505
x = "the inner x" # noqa: F841, unused on purpose, for testing. An assignment *statement* does NOT write to the let env.
501506
inner()
502507
return x
503508
test[test16() == "the env x"]
504509

505-
@dlet[x := "the env x"]
510+
@dlet[(x := "the env x")]
506511
def test17():
507512
x = "the local x" # This lexical variable shadows the env x.
508513
def inner():

unpythonic/syntax/tests/test_letdoutil.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,16 @@ def validate(lst):
3838
if type(k) is not Name:
3939
return False # pragma: no cover, only reached if the test fails.
4040
return True
41+
# Python 3.8 and Python 3.9 require the parens around the walrus when used inside a subscript.
42+
# TODO: Remove the parens (in all walrus-inside-subscript instances in this file) when we bump minimum Python to 3.10.
43+
# From https://docs.python.org/3/whatsnew/3.10.html:
44+
# Assignment expressions can now be used unparenthesized within set literals and set comprehensions, as well as in sequence indexes (but not slices).
4145
test[validate(the[canonize_bindings(q[k0, v0].elts)])] # noqa: F821, it's quoted.
4246
test[validate(the[canonize_bindings(q[((k0, v0),)].elts)])] # noqa: F821
4347
test[validate(the[canonize_bindings(q[(k0, v0), (k1, v1)].elts)])] # noqa: F821
44-
test[validate(the[canonize_bindings([q[k0 := v0]])])] # noqa: F821, it's quoted.
48+
test[validate(the[canonize_bindings([q[(k0 := v0)]])])] # noqa: F821, it's quoted.
4549
test[validate(the[canonize_bindings([q[k0 << v0]])])] # noqa: F821, it's quoted.
46-
test[validate(the[canonize_bindings(q[k0 := v0, k1 := v1].elts)])] # noqa: F821, it's quoted.
50+
test[validate(the[canonize_bindings(q[(k0 := v0), (k1 := v1)].elts)])] # noqa: F821, it's quoted.
4751
test[validate(the[canonize_bindings(q[k0 << v0, k1 << v1].elts)])] # noqa: F821, it's quoted.
4852

4953
# --------------------------------------------------------------------------------
@@ -53,17 +57,17 @@ def validate(lst):
5357
# need this utility, so we must test it first.
5458
with testset("isenvassign"):
5559
test[not isenvassign(q[x])] # noqa: F821
56-
test[isenvassign(q[x := 42])] # noqa: F821
60+
test[isenvassign(q[(x := 42)])] # noqa: F821
5761
test[isenvassign(q[x << 42])] # noqa: F821
5862

5963
with testset("islet"):
6064
test[not islet(q[x])] # noqa: F821
6165
test[not islet(q[f()])] # noqa: F821
6266

6367
# unpythonic 0.15.3+, Python 3.8+
64-
test[islet(the[expandrq[let[x := 21][2 * x]]]) == ("expanded_expr", "let")] # noqa: F821, `let` defines `x`
68+
test[islet(the[expandrq[let[(x := 21)][2 * x]]]) == ("expanded_expr", "let")] # noqa: F821, `let` defines `x`
6569
test[islet(the[expandrq[let[[x := 21] in 2 * x]]]) == ("expanded_expr", "let")] # noqa: F821
66-
test[islet(the[expandrq[let[2 * x, where[x := 21]]]]) == ("expanded_expr", "let")] # noqa: F821
70+
test[islet(the[expandrq[let[2 * x, where[(x := 21)]]]]) == ("expanded_expr", "let")] # noqa: F821
6771

6872
# unpythonic 0.15.0 to 0.15.2, previous modern notation for bindings
6973
test[islet(the[expandrq[let[x << 21][2 * x]]]) == ("expanded_expr", "let")] # noqa: F821, `let` defines `x`
@@ -96,7 +100,7 @@ def f2():
96100
return 2 * x # noqa: F821
97101
test[islet(the[testdata[0].decorator_list[0]]) == ("expanded_decorator", "let")]
98102

99-
testdata = q[let[x := 21][2 * x]] # noqa: F821
103+
testdata = q[let[(x := 21)][2 * x]] # noqa: F821
100104
test[islet(the[testdata], expanded=False) == ("lispy_expr", "let")]
101105

102106
testdata = q[let[x << 21][2 * x]] # noqa: F821
@@ -196,7 +200,7 @@ def f5():
196200
test[not isdo(q[f()])] # noqa: F821
197201

198202
# unpythonic 0.15.3+, Python 3.8+
199-
test[isdo(the[expandrq[do[x := 21, # noqa: F821
203+
test[isdo(the[expandrq[do[(x := 21), # noqa: F821
200204
2 * x]]]) == "expanded"] # noqa: F821
201205

202206
test[isdo(the[expandrq[do[x << 21, # noqa: F821
@@ -210,16 +214,16 @@ def f5():
210214
test[isdo(the[thedo]) == "curried"]
211215

212216
# unpythonic 0.15.3+, Python 3.8+
213-
testdata = q[do[x := 21, # noqa: F821
217+
testdata = q[do[(x := 21), # noqa: F821
214218
2 * x]] # noqa: F821
215219
test[isdo(the[testdata], expanded=False) == "do"]
216220

217221
testdata = q[do0[23, # noqa: F821
218-
x := 21, # noqa: F821
222+
(x := 21), # noqa: F821
219223
2 * x]] # noqa: F821
220224
test[isdo(the[testdata], expanded=False) == "do0"]
221225

222-
testdata = q[someothermacro[x := 21, # noqa: F821
226+
testdata = q[someothermacro[(x := 21), # noqa: F821
223227
2 * x]] # noqa: F821
224228
test[not isdo(the[testdata], expanded=False)]
225229

@@ -241,7 +245,7 @@ def f5():
241245
# Destructuring - envassign
242246

243247
with testset("envassign destructuring (new env-assign syntax v0.15.3+)"):
244-
testdata = q[x := 42] # noqa: F821
248+
testdata = q[(x := 42)] # noqa: F821
245249
view = UnexpandedEnvAssignView(testdata)
246250

247251
# read
@@ -316,7 +320,7 @@ def testletdestructuring(testdata):
316320
test[unparse(view.body) == "(z * t)"]
317321

318322
# lispy expr
319-
testdata = q[let[x := 21, y := 2][y * x]] # noqa: F821
323+
testdata = q[let[(x := 21), (y := 2)][y * x]] # noqa: F821
320324
testletdestructuring(testdata)
321325
testdata = q[let[x << 21, y << 2][y * x]] # noqa: F821
322326
testletdestructuring(testdata)
@@ -374,7 +378,7 @@ def testletdestructuring(testdata):
374378
testletdestructuring(testdata)
375379

376380
# disembodied haskelly let-where (just the content, no macro invocation)
377-
testdata = q[y * x, where[x := 21, y := 2]] # noqa: F821
381+
testdata = q[y * x, where[(x := 21), (y := 2)]] # noqa: F821
378382
testletdestructuring(testdata)
379383
testdata = q[y * x, where[x << 21, y << 2]] # noqa: F821
380384
testletdestructuring(testdata)
@@ -599,7 +603,7 @@ def f8():
599603
# Destructuring - unexpanded do
600604

601605
with testset("do destructuring (unexpanded) (new env-assign syntax v0.15.3+)"):
602-
testdata = q[do[local[x := 21], # noqa: F821
606+
testdata = q[do[local[(x := 21)], # noqa: F821
603607
2 * x]] # noqa: F821
604608
view = UnexpandedDoView(testdata)
605609
# read
@@ -611,11 +615,11 @@ def f8():
611615
test[isenvassign(the[thing])]
612616
# write
613617
# This mutates the original, but we have to assign `view.body` to trigger the setter.
614-
thebody[0] = q[local[x := 9001]] # noqa: F821
618+
thebody[0] = q[local[(x := 9001)]] # noqa: F821
615619
view.body = thebody
616620

617621
# implicit do, a.k.a. extra bracket syntax
618-
testdata = q[let[[local[x := 21], # noqa: F821
622+
testdata = q[let[[local[(x := 21)], # noqa: F821
619623
2 * x]]] # noqa: F821
620624
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
621625
theimplicitdo = testdata.slice
@@ -630,7 +634,7 @@ def f8():
630634
thing = thebody[0].slice.value
631635
test[isenvassign(the[thing])]
632636
# write
633-
thebody[0] = q[local[x := 9001]] # noqa: F821
637+
thebody[0] = q[local[(x := 9001)]] # noqa: F821
634638
view.body = thebody
635639

636640
test_raises[TypeError,

0 commit comments

Comments
 (0)