Skip to content

Commit

Permalink
account for AST changes between 3.6 and 3.9
Browse files Browse the repository at this point in the history
  • Loading branch information
Technologicat committed Apr 16, 2021
1 parent 14a2b9f commit 73cf03e
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 82 deletions.
9 changes: 5 additions & 4 deletions unpythonic/syntax/autoref.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
"""Implicitly reference attributes of an object."""

from ast import (Name, Assign, Load, Call, Lambda, With, Str, arg,
from ast import (Name, Assign, Load, Call, Lambda, With, Constant, arg,
Attribute, Subscript, Store, Del)

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

from .astcompat import getconstant, Str
from .util import wrapwith, AutorefMarker
from .letdoutil import isdo, islet, ExpandedDoView, ExpandedLetView

Expand Down Expand Up @@ -92,9 +93,9 @@ def isexpandedautorefblock(tree):
ctxmanager = tree.items[0].context_expr
return (type(ctxmanager) is Call and
type(ctxmanager.func) is Name and ctxmanager.func.id == "AutorefMarker" and
len(ctxmanager.args) == 1 and type(ctxmanager.args[0]) is Str)
len(ctxmanager.args) == 1 and type(ctxmanager.args[0]) in (Constant, Str)) # Python 3.8+: ast.Constant
def getreferent(tree):
return tree.items[0].context_expr.args[0].s
return getconstant(tree.items[0].context_expr.args[0])

# (lambda _ar314: _ar314[1] if _ar314[0] else x)(_autoref_resolve((p, o, "x")))
def isautoreference(tree):
Expand Down Expand Up @@ -138,7 +139,7 @@ def transform(tree, *, referents, set_ctx, stop, **kw):
set_ctx(referents=referents + [getreferent(tree)])
elif isautoreference(tree): # generated by an inner already expanded autoref block
stop()
thename = get_resolver_list(tree)[-1].s # TODO: Python 3.8: ast.Constant, no ast.Str
thename = getconstant(get_resolver_list(tree)[-1])
if thename in referents:
# This case is tricky to trigger, so let's document it here. This code:
#
Expand Down
17 changes: 12 additions & 5 deletions unpythonic/syntax/lambdatools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
"""Lambdas with multiple expressions, local variables, and a name."""

from ast import (Lambda, List, Name, Assign, Subscript, Call, FunctionDef,
AsyncFunctionDef, Attribute, keyword, Dict, Str, arg,
AsyncFunctionDef, Attribute, keyword, Dict, Constant, arg,
copy_location)
from copy import deepcopy
import sys

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

from .astcompat import getconstant, Str
from .letdo import do
from .letdoutil import islet, isenvassign, UnexpandedLetView, UnexpandedEnvAssignView, ExpandedDoView
from .util import (is_decorated_lambda, isx, make_isxpred, has_deco,
Expand Down Expand Up @@ -141,8 +143,9 @@ def transform(tree, *, stop, **kw):
if k is None: # {..., **d, ...}
tree.values[j] = rec(v)
else:
if type(k) is Str: # TODO: Python 3.8 ast.Constant
tree.values[j], thelambda, match = nameit(k.s, v)
if type(k) in (Constant, Str): # Python 3.8+: ast.Constant
thename = getconstant(k)
tree.values[j], thelambda, match = nameit(thename, v)
if match:
thelambda.body = rec(thelambda.body)
else:
Expand Down Expand Up @@ -186,12 +189,16 @@ def isquicklambda(tree):
@Walker
def transform(tree, **kw):
if isquicklambda(tree):
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
theexpr = tree.slice
else:
theexpr = tree.slice.value
# TODO: With MacroPy3 from azazel75/macropy/HEAD, we can call `f.transform`
# TODO: and we don't need our own `f_transform` function. Kill the hack
# TODO: once a new version of MacroPy3 is released.
if hasattr(f, "transform"):
return f.transform(tree.slice.value)
return f_transform(tree.slice.value) # pragma: no cover, fallback for MacroPy3 1.1.0b2
return f.transform(theexpr)
return f_transform(theexpr) # pragma: no cover, fallback for MacroPy3 1.1.0b2
return tree
new_block_body = [transform.recurse(stmt) for stmt in block_body]
yield new_block_body
Expand Down
22 changes: 15 additions & 7 deletions unpythonic/syntax/letdo.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
FunctionDef, Return,
AsyncFunctionDef,
arguments, arg,
Load, Subscript, Index)
Load, Subscript)
import sys

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

from .astcompat import Index
from .scopeanalyzer import scoped_walker
from .letdoutil import isenvassign, UnexpandedEnvAssignView

Expand Down Expand Up @@ -316,9 +318,12 @@ def isdelete(tree):
@Walker
def find_localdefs(tree, collect, **kw):
if islocaldef(tree):
if type(tree.slice) is not Index: # no slice syntax allowed
assert False, "local[...] takes exactly one expression of the form 'name << value'" # pragma: no cover
expr = tree.slice.value
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
expr = tree.slice
else:
if type(tree.slice) is not Index: # no slice syntax allowed
assert False, "local[...] takes exactly one expression of the form 'name << value'" # pragma: no cover
expr = tree.slice.value
if not isenvassign(expr):
assert False, "local(...) takes exactly one expression of the form 'name << value'" # pragma: no cover
view = UnexpandedEnvAssignView(expr)
Expand All @@ -328,9 +333,12 @@ def find_localdefs(tree, collect, **kw):
@Walker
def find_deletes(tree, collect, **kw):
if isdelete(tree):
if type(tree.slice) is not Index: # no slice syntax allowed
assert False, "delete[...] takes exactly one name" # pragma: no cover
expr = tree.slice.value
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
expr = tree.slice
else:
if type(tree.slice) is not Index: # no slice syntax allowed
assert False, "delete[...] takes exactly one name" # pragma: no cover
expr = tree.slice.value
if type(expr) is not Name:
assert False, "delete[...] takes exactly one name" # pragma: no cover
collect(expr.id)
Expand Down
27 changes: 11 additions & 16 deletions unpythonic/syntax/letdoutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
"""

from ast import (Call, Name, Subscript, Index, Compare, In,
Tuple, List, Str, Constant, BinOp, LShift, Lambda)
Tuple, List, Constant, BinOp, LShift, Lambda)
import sys

from .astcompat import getconstant, Str
from .nameutil import isx, make_isxpred

def where(*bindings):
Expand Down Expand Up @@ -105,11 +106,8 @@ def islet(tree, expanded=True):
elif not isx(tree.func, _isletf):
return False
mode = [kw.value for kw in tree.keywords if kw.arg == "mode"]
assert len(mode) == 1 and type(mode[0]) in (Str, Constant)
if type(mode[0]) is Constant: # Python 3.8+: ast.Constant
mode = mode[0].value
else:
mode = mode[0].s
assert len(mode) == 1 and type(mode[0]) in (Constant, Str)
mode = getconstant(mode[0])
kwnames = [kw.arg for kw in tree.keywords]
if "_envname" in kwnames:
return ("{}_decorator".format(kind), mode) # this call was generated by _dletimpl
Expand Down Expand Up @@ -406,7 +404,10 @@ def _setbody(self, newbody):
if t == "decorator":
raise TypeError("the body of a decorator let form is the body of decorated function, not a subform of the let.")
elif t == "lispy_expr":
self._tree.slice.value = newbody
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
self._tree.slice = newbody
else:
self._tree.slice.value = newbody
else:
theexpr = self._theexpr_ref()
if t == "in_expr":
Expand Down Expand Up @@ -593,28 +594,22 @@ def _setbindings(self, newbindings):
for oldb, newb in zip(thebindings.elts, newbindings.elts):
oldk, thev = oldb.elts
newk, newv = newb.elts
newk_string = newk.value if type(newk) is Constant else newk.s # Python 3.8+: ast.Constant
newk_string = getconstant(newk) # Python 3.8+: ast.Constant
if type(newv) is not Lambda:
raise TypeError("ExpandedLetView: letrec: each value must be of the form `lambda e: ...`") # pragma: no cover
if curried:
# ((k, currycall(currycall(namelambda, "letrec_binding_YYY"), curryf(lambda e: ...))), ...)
# ~~~~~~~~~~~~~~~~~~~~ ^^^^^^^^^^^^^
newv.args.args[0].arg = envname # v0.14.3+: convenience: auto-inject correct envname
thev.args[1].args[0] = newv
if type(thev.args[0].args[1]) is Constant: # Python 3.8+: ast.Constant
thev.args[0].args[1].value = "letrec_binding_{}".format(newk_string)
else: # ast.Str
thev.args[0].args[1].s = "letrec_binding_{}".format(newk_string)
thev.args[0].args[1] = Constant(value="letrec_binding_{}".format(newk_string)) # Python 3.8+: ast.Constant
else:
# ((k, (namelambda("letrec_binding_YYY"))(lambda e: ...)), ...)
# ~~~~~~~~~~~~~~~~~~~~ ^^^^^^^^^^^^^
newv.args.args[0].arg = envname # v0.14.3+: convenience: auto-inject correct envname
thev.args[0] = newv
# update name in the namelambda(...)
if type(thev.func.args[0]) is Constant: # Python 3.8+: ast.Constant
thev.func.args[0].value = "letrec_binding_{}".format(newk_string)
else:
thev.func.args[0].s = "letrec_binding_{}".format(newk_string)
thev.func.args[0] = Constant(value="letrec_binding_{}".format(newk_string)) # Python 3.8+: ast.Constant
# Macro-generated nodes may be missing source location information,
# in which case we let MacroPy fix it later.
# This is mainly an issue for the unit tests of this module, which macro-generate the "old" data.
Expand Down
6 changes: 3 additions & 3 deletions unpythonic/syntax/letsyntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
# at macro expansion time. If you're looking for regular run-time let et al. macros,
# see letdo.py.

from copy import deepcopy
from ast import (Name, Call, Starred, If, Num, Expr, With,
from ast import (Name, Call, Starred, If, Constant, Expr, With,
FunctionDef, AsyncFunctionDef, ClassDef, Attribute)
from copy import deepcopy

from macropy.core.walkers import Walker

Expand Down Expand Up @@ -95,7 +95,7 @@ def register_binding(withstmt, mode, kind):
args = []

if mode == "block":
value = If(test=Num(n=1), # TODO: Python 3.8+: ast.Constant, no ast.Num
value = If(test=Constant(value=1),
body=withstmt.body,
orelse=[],
lineno=stmt.lineno, col_offset=stmt.col_offset)
Expand Down
12 changes: 8 additions & 4 deletions unpythonic/syntax/prefix.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
Experimental, not for use in production code.
"""

from ast import Name, Call, Tuple, Load, Index, Subscript
from ast import Name, Call, Tuple, Load, Subscript
import sys

from macropy.core.quotes import macros, q, u, ast_literal # noqa: F811, F401
from macropy.core.walkers import Walker
Expand Down Expand Up @@ -46,15 +47,18 @@ def transform(tree, *, quotelevel, set_ctx, stop, **kw):
# Integration with other macros, including the testing framework.
# Macros may take a tuple as the top-level expr, but typically don't take slice syntax.
#
# A top-level tuple is packed into an Index, not into an ExtSlice:
# Up to Python 3.8, a top-level tuple is packed into an Index:
# ast.parse("a[1, 2]").body[0].value.slice # --> <_ast.Index at 0x7fd57505f208>
# ast.parse("a[1, 2]").body[0].value.slice.value # --> <_ast.Tuple at 0x7fd590962ef0>
# The structure is for this example is
# Module
# Expr
# Subscript
if type(tree) is Subscript and type(tree.slice) is Index:
body = tree.slice.value
if type(tree) is Subscript:
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
body = tree.slice
else:
body = tree.slice.value
if type(body) is Tuple:
stop()
# skip the transformation of the argument tuple itself, but transform its elements
Expand Down
2 changes: 1 addition & 1 deletion unpythonic/syntax/scopeanalyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ def get_names_in_del_context(tree, *, stop, collect, **kw):
stop()
# We want to detect things like "del x":
# Delete(targets=[Name(id='x', ctx=Del()),])
# We don't currently care about "del myobj.x" or "del mydict['x']":
# We don't currently care about "del myobj.x" or "del mydict['x']" (these examples in Python 3.6):
# Delete(targets=[Attribute(value=Name(id='myobj', ctx=Load()), attr='x', ctx=Del()),])
# Delete(targets=[Subscript(value=Name(id='mydict', ctx=Load()), slice=Index(value=Str(s='x')), ctx=Del()),])
elif type(tree) is Name and hasattr(tree, "ctx") and type(tree.ctx) is Del:
Expand Down
21 changes: 11 additions & 10 deletions unpythonic/syntax/tailtools.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@
from ast import (Lambda, FunctionDef, AsyncFunctionDef,
arguments, arg, keyword,
List, Tuple,
Subscript, Index,
Call, Name, Starred, NameConstant,
Subscript,
Call, Name, Starred, Constant,
BoolOp, And, Or,
With, AsyncWith, If, IfExp, Try, Assign, Return, Expr,
copy_location)
import sys

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

from .astcompat import getconstant, NameConstant
from .util import (isx, make_isxpred, isec,
detect_callec, detect_lambda,
has_tco, sort_lambda_decorators,
Expand Down Expand Up @@ -254,11 +256,7 @@ def iscallcc(tree):
if type(tree) not in (Assign, Expr):
return False
tree = tree.value
if type(tree) is Subscript and type(tree.value) is Name and tree.value.id == "call_cc":
if type(tree.slice) is Index:
return True
assert False, "expected single expr, not slice in call_cc[...]" # pragma: no cover
return False
return type(tree) is Subscript and type(tree.value) is Name and tree.value.id == "call_cc"
def split_at_callcc(body):
if not body:
return [], None, []
Expand Down Expand Up @@ -314,13 +312,16 @@ def maybe_starred(expr): # return expr.id or set starget
# extract the function call(s)
if type(stmt.value) is not Subscript: # both Assign and Expr have a .value
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
theexpr = stmt.value.slice.value
if not (type(theexpr) in (Call, IfExp) or (type(theexpr) is NameConstant and theexpr.value is None)):
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
theexpr = stmt.value.slice
else:
theexpr = stmt.value.slice.value
if not (type(theexpr) in (Call, IfExp) or (type(theexpr) in (Constant, NameConstant) and getconstant(theexpr) is None)):
assert False, "the bracketed expression in call_cc[...] must be a function call, an if-expression, or None" # pragma: no cover
def extract_call(tree):
if type(tree) is Call:
return tree
elif type(tree) is NameConstant and tree.value is None:
elif type(tree) in (Constant, NameConstant) and getconstant(tree) is None:
return None
else:
assert False, "call_cc[...]: expected a function call or None" # pragma: no cover
Expand Down
27 changes: 20 additions & 7 deletions unpythonic/syntax/test/test_letdoutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
do, local,
curry)

from ast import Tuple, Name, Num, Lambda, BinOp, Attribute, Call
from ast import Tuple, Name, Constant, Lambda, BinOp, Attribute, Call
import sys

from macropy.core import unparse

from ...syntax.astcompat import getconstant, Num
from ...syntax.letdoutil import (canonize_bindings,
isenvassign, islet, isdo,
UnexpandedEnvAssignView,
Expand Down Expand Up @@ -175,13 +177,13 @@ def f2():

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

# write
view.name = "y"
view.value = q[23]
test[view.name == "y"]
test[type(the[view.value]) is Num and view.value.n == 23]
test[type(the[view.value]) in (Constant, Num) and getconstant(view.value) == 23] # Python 3.8: ast.Constant

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

# read
test[len(view.bindings.elts) == 2]
Expand Down Expand Up @@ -460,7 +462,11 @@ def f5():
view = UnexpandedDoView(testdata)
# read
thebody = view.body
test[isenvassign(the[thebody[0].slice.value])]
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
thing = thebody[0].slice
else:
thing = thebody[0].slice.value
test[isenvassign(the[thing])]
# write
# This mutates the original, but we have to assign `view.body` to trigger the setter.
thebody[0] = q[local[x << 9001]] # noqa: F821
Expand All @@ -470,11 +476,18 @@ def f5():
testdata = q[definitelynotlet[[local[x << 21], # noqa: F821
2 * x]]] # noqa: F821
testdata.value.id = "let"
theimplicitdo = testdata.slice.value
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
theimplicitdo = testdata.slice
else:
theimplicitdo = testdata.slice.value
view = UnexpandedDoView(theimplicitdo)
# read
thebody = view.body
test[isenvassign(the[thebody[0].slice.value])]
if sys.version_info >= (3, 9, 0): # Python 3.9+: the Index wrapper is gone.
thing = thebody[0].slice
else:
thing = thebody[0].slice.value
test[isenvassign(the[thing])]
# write
thebody[0] = q[local[x << 9001]] # noqa: F821
view.body = thebody
Expand Down
Loading

0 comments on commit 73cf03e

Please sign in to comment.