Fix "with as" indenting and decorated "async def"

Misc other improvements: make check-short now is short
This commit is contained in:
rocky
2019-12-18 09:14:09 -05:00
parent 8470bded59
commit e39c6c7f0a
7 changed files with 161 additions and 85 deletions

View File

@@ -23,7 +23,7 @@ COVER_DIR=../tmp/grammar-cover
# Run short tests # Run short tests
check-short: check-short:
@$(PYTHON) -V && PYTHON_VERSION=`$(PYTHON) -V 2>&1 | cut -d ' ' -f 2 | cut -d'.' -f1,2`; \ @$(PYTHON) -V && PYTHON_VERSION=`$(PYTHON) -V 2>&1 | cut -d ' ' -f 2 | cut -d'.' -f1,2`; \
$(MAKE) check-bytecode-short $(MAKE) check-bytecode-$${PYTHON_VERSION}
# Run all tests # Run all tests
check: check:

Binary file not shown.

View File

@@ -0,0 +1,12 @@
# from 3.7 test_contextlib_async.py
# Bugs were not adding "async" when a function is a decorator,
# and a misaligment when using "async with as".
@_async_test
async def test_enter(self):
self.assertIs(await manager.__aenter__(), manager)
async with manager as context:
async with woohoo() as x:
x = 1
y = 2
assert manager is context

View File

@@ -165,6 +165,7 @@ case $PYVERSION in
SKIP_TESTS=( SKIP_TESTS=(
[test_atexit.py]=1 # [test_atexit.py]=1 #
[test_decorators.py]=1 # Control flow wrt "if elif" [test_decorators.py]=1 # Control flow wrt "if elif"
[test_dis.py]=1 # We change line numbers - duh!
) )
if (( batch )) ; then if (( batch )) ; then
# Fails in crontab environment? # Fails in crontab environment?
@@ -178,16 +179,43 @@ case $PYVERSION in
SKIP_TESTS=( SKIP_TESTS=(
[test_contains.py]=1 # Code "while False: yield None" is optimized away in compilation [test_contains.py]=1 # Code "while False: yield None" is optimized away in compilation
[test_decorators.py]=1 # Control flow wrt "if elif" [test_decorators.py]=1 # Control flow wrt "if elif"
[test_dis.py]=1 # We change line numbers - duh!
[test_pow.py]=1 # Control flow wrt "continue" [test_pow.py]=1 # Control flow wrt "continue"
[test_quopri.py]=1 # Only fails on POWER [test_quopri.py]=1 # Only fails on POWER
) )
;; ;;
3.7) 3.7)
SKIP_TESTS=( SKIP_TESTS=(
[test_argparse.py]=1 #
[test_ast.py]=1 # [test_ast.py]=1 #
[test_atexit.py]=1 #
[test_bdb.py]=1 #
[test_buffer.py]=1 #
[test_builtin.py]=1 #
[test_c_locale_coercion.py]=1 # Parse error
[test_cmdline.py]=1 # Interactive?
[test_codecs-3.7.py]=1
[test_collections.py]=1 # Investigate syntax error: self.assertEqual(p, Point(**))
[test_compare.py]=1
[test_compile.py]=1
[test_complex.py]=1 # Investigate: NameError: global name 'infj' is not defined
[test_contains.py]=1 # Code "while False: yield None" is optimized away in compilation
[test_contextlib_async.py]=1 # Investigate
[test_context.py]=1
[test_coroutines.py]=1 # Parse error
[test_curses.py]=1 # Parse error
[test_cmath.py]=1 # Syntax error - investigate
[test_decorators.py]=1 # Control flow wrt "if elif"
[test_dis.py]=1 # We change line numbers - duh!
# ...
)
3.8)
SKIP_TESTS=(
[test_contains.py]=1 # Code "while False: yield None" is optimized away in compilation [test_contains.py]=1 # Code "while False: yield None" is optimized away in compilation
[test_decorators.py]=1 # Control flow wrt "if elif" [test_decorators.py]=1 # Control flow wrt "if elif"
[test_dis.py]=1 # We change line numbers - duh!
[test_pow.py]=1 # Control flow wrt "continue"
[test_quopri.py]=1 # Only fails on POWER
# ...
) )
;; ;;
*) *)

View File

@@ -316,10 +316,18 @@ TABLE_DIRECT = {
'compare_chained1': ( '%[3]{pattr.replace("-", " ")} %p %p', (0, 19), (-2, 19)), 'compare_chained1': ( '%[3]{pattr.replace("-", " ")} %p %p', (0, 19), (-2, 19)),
'compare_chained2': ( '%[1]{pattr.replace("-", " ")} %p', (0, 19)), 'compare_chained2': ( '%[1]{pattr.replace("-", " ")} %p', (0, 19)),
# 'classdef': (), # handled by n_classdef() # 'classdef': (), # handled by n_classdef()
# A custom rule in n_function def distinguishes whether to call this or
# function_def_async
'function_def': ( '\n\n%|def %c\n', -2), # -2 to handle closures 'function_def': ( '\n\n%|def %c\n', -2), # -2 to handle closures
'function_def_deco': ( '\n\n%c', 0), 'function_def_deco': ( '\n\n%c', 0),
'mkfuncdeco': ( '%|@%c\n%c', 0, 1), 'mkfuncdeco': ( '%|@%c\n%c', 0, 1),
# A custom rule in n_function def distinguishes whether to call this or
# function_def_async
'mkfuncdeco0': ( '%|def %c\n', 0), 'mkfuncdeco0': ( '%|def %c\n', 0),
'classdefdeco': ( '\n\n%c', 0), 'classdefdeco': ( '\n\n%c', 0),
'classdefdeco1': ( '%|@%c\n%c', 0, 1), 'classdefdeco1': ( '%|@%c\n%c', 0, 1),
'kwarg': ( '%[0]{pattr}=%c', 1), # Change when Python 2 does LOAD_STR 'kwarg': ( '%[0]{pattr}=%c', 1), # Change when Python 2 does LOAD_STR

View File

@@ -49,7 +49,7 @@ def customize_for_version3(self, version):
"raise_stmt2": ("%|raise %c from %c\n", 0, 1), "raise_stmt2": ("%|raise %c from %c\n", 0, 1),
"store_locals": ("%|# inspect.currentframe().f_locals = __locals__\n",), "store_locals": ("%|# inspect.currentframe().f_locals = __locals__\n",),
"withstmt": ("%|with %c:\n%+%c%-", 0, 3), "withstmt": ("%|with %c:\n%+%c%-", 0, 3),
"withasstmt": ("%|with %c as (%c):\n%+%c%-", 0, 2, 3), "withasstmt": ("%|with %c as %c:\n%+%c%-", 0, 2, 3),
} }
) )

View File

@@ -17,44 +17,47 @@
from xdis.code import iscode from xdis.code import iscode
from xdis.util import COMPILER_FLAG_BIT from xdis.util import COMPILER_FLAG_BIT
from uncompyle6.semantics.consts import ( from uncompyle6.semantics.consts import INDENT_PER_LEVEL, TABLE_DIRECT
INDENT_PER_LEVEL, TABLE_DIRECT) from uncompyle6.semantics.helper import flatten_list, gen_function_parens_adjust
from uncompyle6.semantics.helper import (
flatten_list, gen_function_parens_adjust)
####################### #######################
# Python 3.5+ Changes # # Python 3.5+ Changes #
####################### #######################
def customize_for_version35(self, version): def customize_for_version35(self, version):
TABLE_DIRECT.update({ TABLE_DIRECT.update(
'await_expr': ( 'await %c', 0), {
'await_stmt': ( '%|%c\n', 0), "await_expr": ("await %c", 0),
'async_for_stmt': ( "await_stmt": ("%|%c\n", 0),
'%|async for %c in %c:\n%+%|%c%-\n\n', 9, 1, 25 ), "async_for_stmt": ("%|async for %c in %c:\n%+%|%c%-\n\n", 9, 1, 25),
'async_forelse_stmt': ( "async_forelse_stmt": (
'%|async for %c in %c:\n%+%c%-%|else:\n%+%c%-\n\n', "%|async for %c in %c:\n%+%c%-%|else:\n%+%c%-\n\n",
9, 1, 25, (27, 'else_suite') ), 9,
'async_with_stmt': ( 1,
'%|async with %c:\n%+%|%c%-', 25,
(0, 'expr'), 7 ), (27, "else_suite"),
'async_with_as_stmt': ( ),
'%|async with %c as %c:\n%+%|%c%-', "async_with_stmt": ("%|async with %c:\n%+%c%-", (0, "expr"), 7),
(0, 'expr'), (6, 'store'), 7), "async_with_as_stmt": (
'unmap_dict': ( '{**%C}', (0, -1, ', **') ), "%|async with %c as %c:\n%+%c%-",
# 'unmapexpr': ( '{**%c}', 0), # done by n_unmapexpr (0, "expr"),
(6, "store"),
}) 7,
),
"unmap_dict": ("{**%C}", (0, -1, ", **")),
# "unmapexpr": ( "{**%c}", 0), # done by n_unmapexpr
}
)
def async_call(node): def async_call(node):
self.f.write('async ') self.f.write("async ")
node.kind == 'call' node.kind == "call"
p = self.prec p = self.prec
self.prec = 80 self.prec = 80
self.template_engine(('%c(%P)', 0, (1, -4, ', ', self.template_engine(("%c(%P)", 0, (1, -4, ", ", 100)), node)
100)), node)
self.prec = p self.prec = p
node.kind == 'async_call' node.kind == "async_call"
self.prune() self.prune()
self.n_async_call = async_call self.n_async_call = async_call
def n_build_list_unpack(node): def n_build_list_unpack(node):
@@ -90,7 +93,9 @@ def customize_for_version35(self, version):
if value.startswith("("): if value.startswith("("):
assert value.endswith(")") assert value.endswith(")")
use_star = False use_star = False
value = value[1:-1].rstrip(" ") # Remove starting '(' and trailing ')' and additional spaces value = value[1:-1].rstrip(
" "
) # Remove starting "(" and trailing ")" and additional spaces
if value == "": if value == "":
pass pass
else: else:
@@ -114,6 +119,7 @@ def customize_for_version35(self, version):
self.prec = p self.prec = p
self.prune() self.prune()
return return
self.n_build_list_unpack = n_build_list_unpack self.n_build_list_unpack = n_build_list_unpack
def n_call(node): def n_call(node):
@@ -123,7 +129,7 @@ def customize_for_version35(self, version):
for i in mapping[1:]: for i in mapping[1:]:
key = key[i] key = key[i]
pass pass
if key.kind.startswith('CALL_FUNCTION_VAR_KW'): if key.kind.startswith("CALL_FUNCTION_VAR_KW"):
# Python 3.5 changes the stack position of # Python 3.5 changes the stack position of
# *args: kwargs come after *args whereas # *args: kwargs come after *args whereas
# in earlier Pythons, *args is at the end # in earlier Pythons, *args is at the end
@@ -137,12 +143,12 @@ def customize_for_version35(self, version):
kwarg_pos = entry[2][1] kwarg_pos = entry[2][1]
args_pos = kwarg_pos - 1 args_pos = kwarg_pos - 1
# Put last node[args_pos] after subsequent kwargs # Put last node[args_pos] after subsequent kwargs
while node[kwarg_pos] == 'kwarg' and kwarg_pos < len(node): while node[kwarg_pos] == "kwarg" and kwarg_pos < len(node):
# swap node[args_pos] with node[kwargs_pos] # swap node[args_pos] with node[kwargs_pos]
node[kwarg_pos], node[args_pos] = node[args_pos], node[kwarg_pos] node[kwarg_pos], node[args_pos] = node[args_pos], node[kwarg_pos]
args_pos = kwarg_pos args_pos = kwarg_pos
kwarg_pos += 1 kwarg_pos += 1
elif key.kind.startswith('CALL_FUNCTION_VAR'): elif key.kind.startswith("CALL_FUNCTION_VAR"):
# CALL_FUNCTION_VAR's top element of the stack contains # CALL_FUNCTION_VAR's top element of the stack contains
# the variable argument list, then comes # the variable argument list, then comes
# annotation args, then keyword args. # annotation args, then keyword args.
@@ -153,65 +159,79 @@ def customize_for_version35(self, version):
kwargs = (argc >> 8) & 0xFF kwargs = (argc >> 8) & 0xFF
# FIXME: handle annotation args # FIXME: handle annotation args
if nargs > 0: if nargs > 0:
template = ('%c(%C, ', 0, (1, nargs+1, ', ')) template = ("%c(%C, ", 0, (1, nargs + 1, ", "))
else: else:
template = ('%c(', 0) template = ("%c(", 0)
self.template_engine(template, node) self.template_engine(template, node)
args_node = node[-2] args_node = node[-2]
if args_node in ('pos_arg', 'expr'): if args_node in ("pos_arg", "expr"):
args_node = args_node[0] args_node = args_node[0]
if args_node == 'build_list_unpack': if args_node == "build_list_unpack":
template = ('*%P)', (0, len(args_node)-1, ', *', 100)) template = ("*%P)", (0, len(args_node) - 1, ", *", 100))
self.template_engine(template, args_node) self.template_engine(template, args_node)
else: else:
if len(node) - nargs > 3: if len(node) - nargs > 3:
template = ('*%c, %C)', nargs+1, (nargs+kwargs+1, -1, ', ')) template = ("*%c, %C)", nargs + 1, (nargs + kwargs + 1, -1, ", "))
else: else:
template = ('*%c)', nargs+1) template = ("*%c)", nargs + 1)
self.template_engine(template, node) self.template_engine(template, node)
self.prune() self.prune()
else: else:
gen_function_parens_adjust(key, node) gen_function_parens_adjust(key, node)
self.default(node) self.default(node)
self.n_call = n_call self.n_call = n_call
def n_function_def(node): def is_async_fn(node):
n0 = node[0] code_node = node[0][0]
is_code = False for n in node[0]:
for i in list(range(len(n0)-2, -1, -1)): if hasattr(n, "attr") and iscode(n.attr):
code_node = n0[i] code_node = n
if hasattr(code_node, 'attr') and iscode(code_node.attr):
is_code = True
break break
pass
pass
if is_code and ( is_code = hasattr(code_node, "attr") and iscode(code_node.attr)
return is_code and (
code_node.attr.co_flags code_node.attr.co_flags
& ( & (
COMPILER_FLAG_BIT["COROUTINE"] COMPILER_FLAG_BIT["COROUTINE"]
| COMPILER_FLAG_BIT["ITERABLE_COROUTINE"] | COMPILER_FLAG_BIT["ITERABLE_COROUTINE"]
| COMPILER_FLAG_BIT["ASYNC_GENERATOR"] | COMPILER_FLAG_BIT["ASYNC_GENERATOR"]
) )
): )
self.template_engine(('\n\n%|async def %c\n',
-2), node) def n_function_def(node):
if is_async_fn(node):
self.template_engine(("\n\n%|async def %c\n", -2), node)
else: else:
self.template_engine(('\n\n%|def %c\n', -2), self.default(node)
node)
self.prune() self.prune()
self.n_function_def = n_function_def self.n_function_def = n_function_def
def n_mkfuncdeco0(node):
if is_async_fn(node):
self.template_engine(("%|async def %c\n", 0), node)
else:
self.default(node)
self.prune()
self.n_mkfuncdeco0 = n_mkfuncdeco0
def unmapexpr(node): def unmapexpr(node):
last_n = node[0][-1] last_n = node[0][-1]
for n in node[0]: for n in node[0]:
self.preorder(n) self.preorder(n)
if n != last_n: if n != last_n:
self.f.write(', **') self.f.write(", **")
pass pass
pass pass
self.prune() self.prune()
pass pass
self.n_unmapexpr = unmapexpr self.n_unmapexpr = unmapexpr
# FIXME: start here # FIXME: start here
@@ -228,67 +248,75 @@ def customize_for_version35(self, version):
# then the first * has already been printed. # then the first * has already been printed.
# Until I have a better way to check for CALL_FUNCTION_VAR, # Until I have a better way to check for CALL_FUNCTION_VAR,
# will assume that if the text ends in *. # will assume that if the text ends in *.
last_was_star = self.f.getvalue().endswith('*') last_was_star = self.f.getvalue().endswith("*")
if lastnodetype.startswith('BUILD_LIST'): if lastnodetype.startswith("BUILD_LIST"):
self.write('['); endchar = ']' self.write("[")
elif lastnodetype.startswith('BUILD_TUPLE'): endchar = "]"
elif lastnodetype.startswith("BUILD_TUPLE"):
# Tuples can appear places that can NOT # Tuples can appear places that can NOT
# have parenthesis around them, like array # have parenthesis around them, like array
# subscripts. We check for that by seeing # subscripts. We check for that by seeing
# if a tuple item is some sort of slice. # if a tuple item is some sort of slice.
no_parens = False no_parens = False
for n in node: for n in node:
if n == 'expr' and n[0].kind.startswith('build_slice'): if n == "expr" and n[0].kind.startswith("build_slice"):
no_parens = True no_parens = True
break break
pass pass
if no_parens: if no_parens:
endchar = '' endchar = ""
else: else:
self.write('('); endchar = ')' self.write("(")
endchar = ")"
pass pass
elif lastnodetype.startswith('BUILD_SET'): elif lastnodetype.startswith("BUILD_SET"):
self.write('{'); endchar = '}' self.write("{")
elif lastnodetype.startswith('BUILD_MAP_UNPACK'): endchar = "}"
self.write('{*'); endchar = '}' elif lastnodetype.startswith("BUILD_MAP_UNPACK"):
elif lastnodetype.startswith('ROT_TWO'): self.write("{*")
self.write('('); endchar = ')' endchar = "}"
elif lastnodetype.startswith("ROT_TWO"):
self.write("(")
endchar = ")"
else: else:
raise TypeError('Internal Error: n_build_list expects list, tuple, set, or unpack') raise TypeError(
"Internal Error: n_build_list expects list, tuple, set, or unpack"
)
flat_elems = flatten_list(node) flat_elems = flatten_list(node)
self.indent_more(INDENT_PER_LEVEL) self.indent_more(INDENT_PER_LEVEL)
sep = '' sep = ""
for elem in flat_elems: for elem in flat_elems:
if elem in ('ROT_THREE', 'EXTENDED_ARG'): if elem in ("ROT_THREE", "EXTENDED_ARG"):
continue continue
assert elem == 'expr' assert elem == "expr"
line_number = self.line_number line_number = self.line_number
value = self.traverse(elem) value = self.traverse(elem)
if elem[0] == 'tuple': if elem[0] == "tuple":
assert value[0] == '(' assert value[0] == "("
assert value[-1] == ')' assert value[-1] == ")"
value = value[1:-1] value = value[1:-1]
if value[-1] == ',': if value[-1] == ",":
# singleton tuple # singleton tuple
value = value[:-1] value = value[:-1]
else: else:
value = '*' + value value = "*" + value
if line_number != self.line_number: if line_number != self.line_number:
sep += '\n' + self.indent + INDENT_PER_LEVEL[:-1] sep += "\n" + self.indent + INDENT_PER_LEVEL[:-1]
else: else:
if sep != '': sep += ' ' if sep != "":
sep += " "
if not last_was_star: if not last_was_star:
pass pass
else: else:
last_was_star = False last_was_star = False
self.write(sep, value) self.write(sep, value)
sep = ',' sep = ","
if lastnode.attr == 1 and lastnodetype.startswith('BUILD_TUPLE'): if lastnode.attr == 1 and lastnodetype.startswith("BUILD_TUPLE"):
self.write(',') self.write(",")
self.write(endchar) self.write(endchar)
self.indent_less(INDENT_PER_LEVEL) self.indent_less(INDENT_PER_LEVEL)