Skip to content

Commit 73cf03e

Browse files
committed
account for AST changes between 3.6 and 3.9
1 parent 14a2b9f commit 73cf03e

File tree

13 files changed

+113
-82
lines changed

13 files changed

+113
-82
lines changed

unpythonic/syntax/autoref.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
# -*- coding: utf-8 -*-
22
"""Implicitly reference attributes of an object."""
33

4-
from ast import (Name, Assign, Load, Call, Lambda, With, Str, arg,
4+
from ast import (Name, Assign, Load, Call, Lambda, With, Constant, arg,
55
Attribute, Subscript, Store, Del)
66

77
from macropy.core.quotes import macros, q, u, name, ast_literal
88
from macropy.core.hquotes import macros, hq # noqa: F811, F401
99
from macropy.core.walkers import Walker
1010

11+
from .astcompat import getconstant, Str
1112
from .util import wrapwith, AutorefMarker
1213
from .letdoutil import isdo, islet, ExpandedDoView, ExpandedLetView
1314

@@ -92,9 +93,9 @@ def isexpandedautorefblock(tree):
9293
ctxmanager = tree.items[0].context_expr
9394
return (type(ctxmanager) is Call and
9495
type(ctxmanager.func) is Name and ctxmanager.func.id == "AutorefMarker" and
95-
len(ctxmanager.args) == 1 and type(ctxmanager.args[0]) is Str)
96+
len(ctxmanager.args) == 1 and type(ctxmanager.args[0]) in (Constant, Str)) # Python 3.8+: ast.Constant
9697
def getreferent(tree):
97-
return tree.items[0].context_expr.args[0].s
98+
return getconstant(tree.items[0].context_expr.args[0])
9899

99100
# (lambda _ar314: _ar314[1] if _ar314[0] else x)(_autoref_resolve((p, o, "x")))
100101
def isautoreference(tree):
@@ -138,7 +139,7 @@ def transform(tree, *, referents, set_ctx, stop, **kw):
138139
set_ctx(referents=referents + [getreferent(tree)])
139140
elif isautoreference(tree): # generated by an inner already expanded autoref block
140141
stop()
141-
thename = get_resolver_list(tree)[-1].s # TODO: Python 3.8: ast.Constant, no ast.Str
142+
thename = getconstant(get_resolver_list(tree)[-1])
142143
if thename in referents:
143144
# This case is tricky to trigger, so let's document it here. This code:
144145
#

unpythonic/syntax/lambdatools.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
"""Lambdas with multiple expressions, local variables, and a name."""
33

44
from ast import (Lambda, List, Name, Assign, Subscript, Call, FunctionDef,
5-
AsyncFunctionDef, Attribute, keyword, Dict, Str, arg,
5+
AsyncFunctionDef, Attribute, keyword, Dict, Constant, arg,
66
copy_location)
77
from copy import deepcopy
8+
import sys
89

910
from macropy.core.quotes import macros, q, u, ast_literal, name
1011
from macropy.core.hquotes import macros, hq # noqa: F811, F401
@@ -16,6 +17,7 @@
1617
from ..fun import orf
1718
from ..env import env
1819

20+
from .astcompat import getconstant, Str
1921
from .letdo import do
2022
from .letdoutil import islet, isenvassign, UnexpandedLetView, UnexpandedEnvAssignView, ExpandedDoView
2123
from .util import (is_decorated_lambda, isx, make_isxpred, has_deco,
@@ -141,8 +143,9 @@ def transform(tree, *, stop, **kw):
141143
if k is None: # {..., **d, ...}
142144
tree.values[j] = rec(v)
143145
else:
144-
if type(k) is Str: # TODO: Python 3.8 ast.Constant
145-
tree.values[j], thelambda, match = nameit(k.s, v)
146+
if type(k) in (Constant, Str): # Python 3.8+: ast.Constant
147+
thename = getconstant(k)
148+
tree.values[j], thelambda, match = nameit(thename, v)
146149
if match:
147150
thelambda.body = rec(thelambda.body)
148151
else:
@@ -186,12 +189,16 @@ def isquicklambda(tree):
186189
@Walker
187190
def transform(tree, **kw):
188191
if isquicklambda(tree):
192+
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
193+
theexpr = tree.slice
194+
else:
195+
theexpr = tree.slice.value
189196
# TODO: With MacroPy3 from azazel75/macropy/HEAD, we can call `f.transform`
190197
# TODO: and we don't need our own `f_transform` function. Kill the hack
191198
# TODO: once a new version of MacroPy3 is released.
192199
if hasattr(f, "transform"):
193-
return f.transform(tree.slice.value)
194-
return f_transform(tree.slice.value) # pragma: no cover, fallback for MacroPy3 1.1.0b2
200+
return f.transform(theexpr)
201+
return f_transform(theexpr) # pragma: no cover, fallback for MacroPy3 1.1.0b2
195202
return tree
196203
new_block_body = [transform.recurse(stmt) for stmt in block_body]
197204
yield new_block_body

unpythonic/syntax/letdo.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
FunctionDef, Return,
2121
AsyncFunctionDef,
2222
arguments, arg,
23-
Load, Subscript, Index)
23+
Load, Subscript)
24+
import sys
2425

2526
from macropy.core.quotes import macros, q, u, ast_literal, name
2627
from macropy.core.hquotes import macros, hq # noqa: F811, F401
@@ -32,6 +33,7 @@
3233
from ..dynassign import dyn
3334
from ..misc import namelambda
3435

36+
from .astcompat import Index
3537
from .scopeanalyzer import scoped_walker
3638
from .letdoutil import isenvassign, UnexpandedEnvAssignView
3739

@@ -316,9 +318,12 @@ def isdelete(tree):
316318
@Walker
317319
def find_localdefs(tree, collect, **kw):
318320
if islocaldef(tree):
319-
if type(tree.slice) is not Index: # no slice syntax allowed
320-
assert False, "local[...] takes exactly one expression of the form 'name << value'" # pragma: no cover
321-
expr = tree.slice.value
321+
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
322+
expr = tree.slice
323+
else:
324+
if type(tree.slice) is not Index: # no slice syntax allowed
325+
assert False, "local[...] takes exactly one expression of the form 'name << value'" # pragma: no cover
326+
expr = tree.slice.value
322327
if not isenvassign(expr):
323328
assert False, "local(...) takes exactly one expression of the form 'name << value'" # pragma: no cover
324329
view = UnexpandedEnvAssignView(expr)
@@ -328,9 +333,12 @@ def find_localdefs(tree, collect, **kw):
328333
@Walker
329334
def find_deletes(tree, collect, **kw):
330335
if isdelete(tree):
331-
if type(tree.slice) is not Index: # no slice syntax allowed
332-
assert False, "delete[...] takes exactly one name" # pragma: no cover
333-
expr = tree.slice.value
336+
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
337+
expr = tree.slice
338+
else:
339+
if type(tree.slice) is not Index: # no slice syntax allowed
340+
assert False, "delete[...] takes exactly one name" # pragma: no cover
341+
expr = tree.slice.value
334342
if type(expr) is not Name:
335343
assert False, "delete[...] takes exactly one name" # pragma: no cover
336344
collect(expr.id)

unpythonic/syntax/letdoutil.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
"""
77

88
from ast import (Call, Name, Subscript, Index, Compare, In,
9-
Tuple, List, Str, Constant, BinOp, LShift, Lambda)
9+
Tuple, List, Constant, BinOp, LShift, Lambda)
1010
import sys
1111

12+
from .astcompat import getconstant, Str
1213
from .nameutil import isx, make_isxpred
1314

1415
def where(*bindings):
@@ -105,11 +106,8 @@ def islet(tree, expanded=True):
105106
elif not isx(tree.func, _isletf):
106107
return False
107108
mode = [kw.value for kw in tree.keywords if kw.arg == "mode"]
108-
assert len(mode) == 1 and type(mode[0]) in (Str, Constant)
109-
if type(mode[0]) is Constant: # Python 3.8+: ast.Constant
110-
mode = mode[0].value
111-
else:
112-
mode = mode[0].s
109+
assert len(mode) == 1 and type(mode[0]) in (Constant, Str)
110+
mode = getconstant(mode[0])
113111
kwnames = [kw.arg for kw in tree.keywords]
114112
if "_envname" in kwnames:
115113
return ("{}_decorator".format(kind), mode) # this call was generated by _dletimpl
@@ -406,7 +404,10 @@ def _setbody(self, newbody):
406404
if t == "decorator":
407405
raise TypeError("the body of a decorator let form is the body of decorated function, not a subform of the let.")
408406
elif t == "lispy_expr":
409-
self._tree.slice.value = newbody
407+
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
408+
self._tree.slice = newbody
409+
else:
410+
self._tree.slice.value = newbody
410411
else:
411412
theexpr = self._theexpr_ref()
412413
if t == "in_expr":
@@ -593,28 +594,22 @@ def _setbindings(self, newbindings):
593594
for oldb, newb in zip(thebindings.elts, newbindings.elts):
594595
oldk, thev = oldb.elts
595596
newk, newv = newb.elts
596-
newk_string = newk.value if type(newk) is Constant else newk.s # Python 3.8+: ast.Constant
597+
newk_string = getconstant(newk) # Python 3.8+: ast.Constant
597598
if type(newv) is not Lambda:
598599
raise TypeError("ExpandedLetView: letrec: each value must be of the form `lambda e: ...`") # pragma: no cover
599600
if curried:
600601
# ((k, currycall(currycall(namelambda, "letrec_binding_YYY"), curryf(lambda e: ...))), ...)
601602
# ~~~~~~~~~~~~~~~~~~~~ ^^^^^^^^^^^^^
602603
newv.args.args[0].arg = envname # v0.14.3+: convenience: auto-inject correct envname
603604
thev.args[1].args[0] = newv
604-
if type(thev.args[0].args[1]) is Constant: # Python 3.8+: ast.Constant
605-
thev.args[0].args[1].value = "letrec_binding_{}".format(newk_string)
606-
else: # ast.Str
607-
thev.args[0].args[1].s = "letrec_binding_{}".format(newk_string)
605+
thev.args[0].args[1] = Constant(value="letrec_binding_{}".format(newk_string)) # Python 3.8+: ast.Constant
608606
else:
609607
# ((k, (namelambda("letrec_binding_YYY"))(lambda e: ...)), ...)
610608
# ~~~~~~~~~~~~~~~~~~~~ ^^^^^^^^^^^^^
611609
newv.args.args[0].arg = envname # v0.14.3+: convenience: auto-inject correct envname
612610
thev.args[0] = newv
613611
# update name in the namelambda(...)
614-
if type(thev.func.args[0]) is Constant: # Python 3.8+: ast.Constant
615-
thev.func.args[0].value = "letrec_binding_{}".format(newk_string)
616-
else:
617-
thev.func.args[0].s = "letrec_binding_{}".format(newk_string)
612+
thev.func.args[0] = Constant(value="letrec_binding_{}".format(newk_string)) # Python 3.8+: ast.Constant
618613
# Macro-generated nodes may be missing source location information,
619614
# in which case we let MacroPy fix it later.
620615
# This is mainly an issue for the unit tests of this module, which macro-generate the "old" data.

unpythonic/syntax/letsyntax.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
# at macro expansion time. If you're looking for regular run-time let et al. macros,
55
# see letdo.py.
66

7-
from copy import deepcopy
8-
from ast import (Name, Call, Starred, If, Num, Expr, With,
7+
from ast import (Name, Call, Starred, If, Constant, Expr, With,
98
FunctionDef, AsyncFunctionDef, ClassDef, Attribute)
9+
from copy import deepcopy
1010

1111
from macropy.core.walkers import Walker
1212

@@ -95,7 +95,7 @@ def register_binding(withstmt, mode, kind):
9595
args = []
9696

9797
if mode == "block":
98-
value = If(test=Num(n=1), # TODO: Python 3.8+: ast.Constant, no ast.Num
98+
value = If(test=Constant(value=1),
9999
body=withstmt.body,
100100
orelse=[],
101101
lineno=stmt.lineno, col_offset=stmt.col_offset)

unpythonic/syntax/prefix.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
Experimental, not for use in production code.
55
"""
66

7-
from ast import Name, Call, Tuple, Load, Index, Subscript
7+
from ast import Name, Call, Tuple, Load, Subscript
8+
import sys
89

910
from macropy.core.quotes import macros, q, u, ast_literal # noqa: F811, F401
1011
from macropy.core.walkers import Walker
@@ -46,15 +47,18 @@ def transform(tree, *, quotelevel, set_ctx, stop, **kw):
4647
# Integration with other macros, including the testing framework.
4748
# Macros may take a tuple as the top-level expr, but typically don't take slice syntax.
4849
#
49-
# A top-level tuple is packed into an Index, not into an ExtSlice:
50+
# Up to Python 3.8, a top-level tuple is packed into an Index:
5051
# ast.parse("a[1, 2]").body[0].value.slice # --> <_ast.Index at 0x7fd57505f208>
5152
# ast.parse("a[1, 2]").body[0].value.slice.value # --> <_ast.Tuple at 0x7fd590962ef0>
5253
# The structure is for this example is
5354
# Module
5455
# Expr
5556
# Subscript
56-
if type(tree) is Subscript and type(tree.slice) is Index:
57-
body = tree.slice.value
57+
if type(tree) is Subscript:
58+
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
59+
body = tree.slice
60+
else:
61+
body = tree.slice.value
5862
if type(body) is Tuple:
5963
stop()
6064
# skip the transformation of the argument tuple itself, but transform its elements

unpythonic/syntax/scopeanalyzer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ def get_names_in_del_context(tree, *, stop, collect, **kw):
338338
stop()
339339
# We want to detect things like "del x":
340340
# Delete(targets=[Name(id='x', ctx=Del()),])
341-
# We don't currently care about "del myobj.x" or "del mydict['x']":
341+
# We don't currently care about "del myobj.x" or "del mydict['x']" (these examples in Python 3.6):
342342
# Delete(targets=[Attribute(value=Name(id='myobj', ctx=Load()), attr='x', ctx=Del()),])
343343
# Delete(targets=[Subscript(value=Name(id='mydict', ctx=Load()), slice=Index(value=Str(s='x')), ctx=Del()),])
344344
elif type(tree) is Name and hasattr(tree, "ctx") and type(tree.ctx) is Del:

unpythonic/syntax/tailtools.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@
88
from ast import (Lambda, FunctionDef, AsyncFunctionDef,
99
arguments, arg, keyword,
1010
List, Tuple,
11-
Subscript, Index,
12-
Call, Name, Starred, NameConstant,
11+
Subscript,
12+
Call, Name, Starred, Constant,
1313
BoolOp, And, Or,
1414
With, AsyncWith, If, IfExp, Try, Assign, Return, Expr,
1515
copy_location)
16+
import sys
1617

1718
from macropy.core.macros import macro_stub
1819
from macropy.core.quotes import macros, q, u, ast_literal, name
1920
from macropy.core.hquotes import macros, hq # noqa: F811, F401
2021
from macropy.core.walkers import Walker
2122

23+
from .astcompat import getconstant, NameConstant
2224
from .util import (isx, make_isxpred, isec,
2325
detect_callec, detect_lambda,
2426
has_tco, sort_lambda_decorators,
@@ -254,11 +256,7 @@ def iscallcc(tree):
254256
if type(tree) not in (Assign, Expr):
255257
return False
256258
tree = tree.value
257-
if type(tree) is Subscript and type(tree.value) is Name and tree.value.id == "call_cc":
258-
if type(tree.slice) is Index:
259-
return True
260-
assert False, "expected single expr, not slice in call_cc[...]" # pragma: no cover
261-
return False
259+
return type(tree) is Subscript and type(tree.value) is Name and tree.value.id == "call_cc"
262260
def split_at_callcc(body):
263261
if not body:
264262
return [], None, []
@@ -314,13 +312,16 @@ def maybe_starred(expr): # return expr.id or set starget
314312
# extract the function call(s)
315313
if type(stmt.value) is not Subscript: # both Assign and Expr have a .value
316314
assert False, "expected either an assignment with a call_cc[] expr on RHS, or a bare call_cc[] expr, got {}".format(stmt.value) # pragma: no cover
317-
theexpr = stmt.value.slice.value
318-
if not (type(theexpr) in (Call, IfExp) or (type(theexpr) is NameConstant and theexpr.value is None)):
315+
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
316+
theexpr = stmt.value.slice
317+
else:
318+
theexpr = stmt.value.slice.value
319+
if not (type(theexpr) in (Call, IfExp) or (type(theexpr) in (Constant, NameConstant) and getconstant(theexpr) is None)):
319320
assert False, "the bracketed expression in call_cc[...] must be a function call, an if-expression, or None" # pragma: no cover
320321
def extract_call(tree):
321322
if type(tree) is Call:
322323
return tree
323-
elif type(tree) is NameConstant and tree.value is None:
324+
elif type(tree) in (Constant, NameConstant) and getconstant(tree) is None:
324325
return None
325326
else:
326327
assert False, "call_cc[...]: expected a function call or None" # pragma: no cover

unpythonic/syntax/test/test_letdoutil.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
do, local,
1313
curry)
1414

15-
from ast import Tuple, Name, Num, Lambda, BinOp, Attribute, Call
15+
from ast import Tuple, Name, Constant, Lambda, BinOp, Attribute, Call
16+
import sys
1617

1718
from macropy.core import unparse
1819

20+
from ...syntax.astcompat import getconstant, Num
1921
from ...syntax.letdoutil import (canonize_bindings,
2022
isenvassign, islet, isdo,
2123
UnexpandedEnvAssignView,
@@ -175,13 +177,13 @@ def f2():
175177

176178
# read
177179
test[view.name == "x"]
178-
test[type(the[view.value]) is Num and view.value.n == 42] # TODO: Python 3.8: ast.Constant, no ast.Num
180+
test[type(the[view.value]) in (Constant, Num) and getconstant(view.value) == 42] # Python 3.8: ast.Constant
179181

180182
# write
181183
view.name = "y"
182184
view.value = q[23]
183185
test[view.name == "y"]
184-
test[type(the[view.value]) is Num and view.value.n == 23]
186+
test[type(the[view.value]) in (Constant, Num) and getconstant(view.value) == 23] # Python 3.8: ast.Constant
185187

186188
# it's a live view
187189
test[unparse(testdata) == "(y << 23)"]
@@ -376,7 +378,7 @@ def testbindings(*expected):
376378
test[the[unparse(bk)] == the["'{}'".format(k)]]
377379
test[type(the[lam]) is Lambda]
378380
lambody = lam.body
379-
test[type(the[lambody]) is Num and lambody.n == the[v]] # TODO: Python 3.8: ast.Constant, no ast.Num
381+
test[type(the[lambody]) in (Constant, Num) and getconstant(lambody) == the[v]] # Python 3.8: ast.Constant
380382

381383
# read
382384
test[len(view.bindings.elts) == 2]
@@ -460,7 +462,11 @@ def f5():
460462
view = UnexpandedDoView(testdata)
461463
# read
462464
thebody = view.body
463-
test[isenvassign(the[thebody[0].slice.value])]
465+
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
466+
thing = thebody[0].slice
467+
else:
468+
thing = thebody[0].slice.value
469+
test[isenvassign(the[thing])]
464470
# write
465471
# This mutates the original, but we have to assign `view.body` to trigger the setter.
466472
thebody[0] = q[local[x << 9001]] # noqa: F821
@@ -470,11 +476,18 @@ def f5():
470476
testdata = q[definitelynotlet[[local[x << 21], # noqa: F821
471477
2 * x]]] # noqa: F821
472478
testdata.value.id = "let"
473-
theimplicitdo = testdata.slice.value
479+
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
480+
theimplicitdo = testdata.slice
481+
else:
482+
theimplicitdo = testdata.slice.value
474483
view = UnexpandedDoView(theimplicitdo)
475484
# read
476485
thebody = view.body
477-
test[isenvassign(the[thebody[0].slice.value])]
486+
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
487+
thing = thebody[0].slice
488+
else:
489+
thing = thebody[0].slice.value
490+
test[isenvassign(the[thing])]
478491
# write
479492
thebody[0] = q[local[x << 9001]] # noqa: F821
480493
view.body = thebody

0 commit comments

Comments
 (0)