You've already forked python-uncompyle6
mirror of
https://github.com/rocky/python-uncompyle6.git
synced 2025-08-02 08:34:46 +08:00
Added support for support for Python 3.6 CALL_FUNCTION_KW
This commit is contained in:
6
pytest/test_CALL_FUNCTION_KW.sh
Normal file
6
pytest/test_CALL_FUNCTION_KW.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
source ../.venv.3.6/bin/activate
|
||||
py.test -k test_CALL_FUNCTION_KW
|
||||
source ../.venv.3.5/bin/activate
|
||||
py.test -k test_CALL_FUNCTION_KW
|
||||
source ../.venv.2.7/bin/activate
|
||||
py.test -k test_CALL_FUNCTION_KW
|
@@ -1,20 +1,24 @@
|
||||
# std
|
||||
import string
|
||||
# 3rd party
|
||||
from hypothesis import given, assume, strategies as st
|
||||
from hypothesis import given, assume, example, settings, strategies as st
|
||||
import pytest
|
||||
# uncompyle
|
||||
from validate import validate_uncompyle
|
||||
from test_fstring import expressions
|
||||
|
||||
|
||||
alpha = st.sampled_from(string.ascii_lowercase)
|
||||
numbers = st.sampled_from(string.digits)
|
||||
alphanum = st.sampled_from(string.ascii_lowercase + string.digits)
|
||||
expressions = st.sampled_from([x for x in string.ascii_lowercase + string.digits] + ['x+1'])
|
||||
|
||||
|
||||
@st.composite
|
||||
def function_calls(draw):
|
||||
def function_calls(draw,
|
||||
min_keyword_args=0, max_keyword_args=5,
|
||||
min_positional_args=0, max_positional_args=5,
|
||||
min_star_args=0, max_star_args=1,
|
||||
min_double_star_args=0, max_double_star_args=1):
|
||||
"""
|
||||
Strategy factory for generating function calls.
|
||||
|
||||
@@ -22,21 +26,49 @@ def function_calls(draw):
|
||||
|
||||
:return: The function call text.
|
||||
"""
|
||||
list1 = st.lists(alpha, min_size=0, max_size=1)
|
||||
list3 = st.lists(alpha, min_size=0, max_size=3)
|
||||
st_positional_args = st.lists(
|
||||
alpha,
|
||||
min_size=min_positional_args,
|
||||
max_size=max_positional_args
|
||||
)
|
||||
st_keyword_args = st.lists(
|
||||
alpha,
|
||||
min_size=min_keyword_args,
|
||||
max_size=max_keyword_args
|
||||
)
|
||||
st_star_args = st.lists(
|
||||
alpha,
|
||||
min_size=min_star_args,
|
||||
max_size=max_star_args
|
||||
)
|
||||
st_double_star_args = st.lists(
|
||||
alpha,
|
||||
min_size=min_double_star_args,
|
||||
max_size=max_double_star_args
|
||||
)
|
||||
|
||||
positional_args = draw(list3)
|
||||
named_args = [x + '=0' for x in draw(list3)]
|
||||
star_args = ['*' + x for x in draw(list1)]
|
||||
double_star_args = ['**' + x for x in draw(list1)]
|
||||
positional_args = draw(st_positional_args)
|
||||
keyword_args = draw(st_keyword_args)
|
||||
st_values = st.lists(
|
||||
expressions(),
|
||||
min_size=len(keyword_args),
|
||||
max_size=len(keyword_args)
|
||||
)
|
||||
keyword_args = [
|
||||
x + '=' + e
|
||||
for x, e in
|
||||
zip(keyword_args, draw(st_values))
|
||||
]
|
||||
star_args = ['*' + x for x in draw(st_star_args)]
|
||||
double_star_args = ['**' + x for x in draw(st_double_star_args)]
|
||||
|
||||
arguments = positional_args + named_args + star_args + double_star_args
|
||||
arguments = positional_args + keyword_args + star_args + double_star_args
|
||||
draw(st.randoms()).shuffle(arguments)
|
||||
arguments = ','.join(arguments)
|
||||
|
||||
function_call = 'fn({arguments})'.format(arguments=arguments)
|
||||
try:
|
||||
# TODO: Figure out the exact rules for ordering of positional, named,
|
||||
# TODO: Figure out the exact rules for ordering of positional, keyword,
|
||||
# star args, double star args and in which versions the various
|
||||
# types of arguments are supported so we don't need to check that the
|
||||
# expression compiles like this.
|
||||
@@ -46,9 +78,56 @@ def function_calls(draw):
|
||||
return function_call
|
||||
|
||||
|
||||
@pytest.mark.xfail()
|
||||
def test_CALL_FUNCTION():
|
||||
validate_uncompyle("fn(w,m,f)")
|
||||
def test_function_no_args():
|
||||
validate_uncompyle("fn()")
|
||||
|
||||
|
||||
def isolated_function_calls(which):
|
||||
"""
|
||||
Returns a strategy for generating function calls, but isolated to
|
||||
particular types of arguments, for example only positional arguments.
|
||||
|
||||
This can help reason about debugging errors in specific types of function
|
||||
calls.
|
||||
|
||||
:param which: One of 'keyword', 'positional', 'star', 'double_star'
|
||||
|
||||
:return: Strategy for generating an function call isolated to specific
|
||||
argument types.
|
||||
"""
|
||||
kwargs = dict(
|
||||
max_keyword_args=0,
|
||||
max_positional_args=0,
|
||||
max_star_args=0,
|
||||
max_double_star_args=0,
|
||||
)
|
||||
kwargs['_'.join(('min', which, 'args'))] = 1
|
||||
kwargs['_'.join(('max', which, 'args'))] = 5 if 'star' not in which else 1
|
||||
return function_calls(**kwargs)
|
||||
|
||||
|
||||
with settings(max_examples=25):
|
||||
|
||||
@given(isolated_function_calls('positional'))
|
||||
@example("fn(0)")
|
||||
def test_function_positional_only(expr):
|
||||
validate_uncompyle(expr)
|
||||
|
||||
@given(isolated_function_calls('keyword'))
|
||||
@example("fn(a=0)")
|
||||
def test_function_call_keyword_only(expr):
|
||||
validate_uncompyle(expr)
|
||||
|
||||
@given(isolated_function_calls('star'))
|
||||
@example("fn(*items)")
|
||||
def test_function_call_star_only(expr):
|
||||
validate_uncompyle(expr)
|
||||
|
||||
@pytest.mark.xfail()
|
||||
@given(isolated_function_calls('double_star'))
|
||||
@example("fn(**{})")
|
||||
def test_function_call_double_star_only(expr):
|
||||
validate_uncompyle(expr)
|
||||
|
||||
|
||||
@pytest.mark.xfail()
|
||||
@@ -61,11 +140,6 @@ def test_BUILD_MAP_BUILD_MAP_UNPACK_WITH_CALL_BUILD_TUPLE_CALL_FUNCTION_EX():
|
||||
validate_uncompyle("fn(a=0,**g)")
|
||||
|
||||
|
||||
@pytest.mark.xfail()
|
||||
def test_CALL_FUNCTION_KW():
|
||||
validate_uncompyle("fn(j=0)")
|
||||
|
||||
|
||||
@pytest.mark.xfail()
|
||||
def test_CALL_FUNCTION_EX():
|
||||
validate_uncompyle("fn(*g,**j)")
|
||||
@@ -100,29 +174,3 @@ def test_BUILD_CONST_KEY_MAP_CALL_FUNCTION_EX():
|
||||
@given(function_calls())
|
||||
def test_function_call(function_call):
|
||||
validate_uncompyle(function_call)
|
||||
|
||||
|
||||
examples = set()
|
||||
generate_examples = False
|
||||
|
||||
|
||||
@pytest.mark.skipif(not generate_examples, reason='not generating examples')
|
||||
@given(function_calls())
|
||||
def test_generate_hypothesis(function_call):
|
||||
examples.add(function_call)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not generate_examples, reason='not generating examples')
|
||||
def test_generate_examples():
|
||||
import dis
|
||||
example_opcodes = {}
|
||||
for example in examples:
|
||||
opcodes = tuple(sorted(set(
|
||||
instruction.opname
|
||||
for instruction in dis.Bytecode(example)
|
||||
if instruction.opname not in ('LOAD_CONST', 'LOAD_NAME', 'RETURN_VALUE')
|
||||
)))
|
||||
example_opcodes[opcodes] = example
|
||||
for k, v in example_opcodes.items():
|
||||
print('def test_' + '_'.join(k) + '():\n validate_uncompyle("' + v + '")\n\n')
|
||||
return
|
||||
|
@@ -607,7 +607,8 @@ class Python3Parser(PythonParser):
|
||||
""", nop_func)
|
||||
continue
|
||||
elif opname in ('CALL_FUNCTION', 'CALL_FUNCTION_VAR',
|
||||
'CALL_FUNCTION_VAR_KW', 'CALL_FUNCTION_KW'):
|
||||
'CALL_FUNCTION_VAR_KW') \
|
||||
or opname.startswith('CALL_FUNCTION_KW'):
|
||||
self.custom_classfunc_rule(opname, token, customize)
|
||||
elif opname == 'LOAD_DICTCOMP':
|
||||
rule_pat = ("dictcomp ::= LOAD_DICTCOMP %sMAKE_FUNCTION_0 expr "
|
||||
|
@@ -58,6 +58,17 @@ class Python36Parser(Python35Parser):
|
||||
""" % (fstring_expr_or_str_n, fstring_expr_or_str_n, "fstring_expr_or_str " * v)
|
||||
self.add_unique_doc_rules(rules_str, customize)
|
||||
|
||||
def custom_classfunc_rule(self, opname, token, customize):
|
||||
|
||||
if opname.startswith('CALL_FUNCTION_KW'):
|
||||
values = 'expr ' * token.attr
|
||||
rule = 'call_function ::= expr kwargs_only_36 {token.type}'.format(**locals())
|
||||
self.add_unique_rule(rule, token.type, token.attr, customize)
|
||||
rule = 'kwargs_only_36 ::= {values} LOAD_CONST'.format(**locals())
|
||||
self.add_unique_rule(rule, token.type, token.attr, customize)
|
||||
else:
|
||||
super(Python36Parser, self).custom_classfunc_rule(opname, token, customize)
|
||||
|
||||
|
||||
class Python36ParserSingle(Python36Parser, PythonParserSingle):
|
||||
pass
|
||||
|
@@ -28,6 +28,8 @@ class Scanner36(Scanner3):
|
||||
if t.op == self.opc.CALL_FUNCTION_EX and t.attr & 1:
|
||||
t.type = 'CALL_FUNCTION_EX_KW'
|
||||
pass
|
||||
if t.op == self.opc.CALL_FUNCTION_KW:
|
||||
t.type = 'CALL_FUNCTION_KW_{t.attr}'.format(**locals())
|
||||
pass
|
||||
return tokens, customize
|
||||
|
||||
|
@@ -392,6 +392,7 @@ class SourceWalker(GenericASTTraversal, object):
|
||||
'fstring_single': ( "f'{%c%{conversion}}'", 0),
|
||||
'fstring_multi': ( "f'%c'", 0),
|
||||
'func_args36': ( "%c(**", 0),
|
||||
#'kwargs_only_36': ( "%c(**", 0),
|
||||
})
|
||||
TABLE_R.update({
|
||||
'CALL_FUNCTION_EX': ('%c(*%P)', 0, (1, 2, ', ', 100)),
|
||||
@@ -413,6 +414,17 @@ class SourceWalker(GenericASTTraversal, object):
|
||||
self.default(node)
|
||||
self.n_fstring_single = n_fstring_single
|
||||
|
||||
def n_kwargs_only_36(node):
|
||||
keys = node[-1].attr
|
||||
num_kwargs = len(keys)
|
||||
values = node[:num_kwargs]
|
||||
for i, (key, value) in enumerate(zip(keys, values)):
|
||||
self.write(key + '=')
|
||||
self.preorder(value)
|
||||
if i < num_kwargs:
|
||||
self.write(',')
|
||||
self.prune()
|
||||
self.n_kwargs_only_36 = n_kwargs_only_36
|
||||
return
|
||||
|
||||
f = property(lambda s: s.params['f'],
|
||||
@@ -1800,6 +1812,8 @@ class SourceWalker(GenericASTTraversal, object):
|
||||
if k.startswith('CALL_METHOD'):
|
||||
# This happens in PyPy only
|
||||
TABLE_R[k] = ('%c(%P)', 0, (1, -1, ', ', 100))
|
||||
elif self.version >= 3.6 and k.startswith('CALL_FUNCTION_KW'):
|
||||
TABLE_R[k] = ('%c(%P)', 0, (1, -1, ', ', 100))
|
||||
elif op == 'CALL_FUNCTION':
|
||||
TABLE_R[k] = ('%c(%P)', 0, (1, -1, ', ', 100))
|
||||
elif op in ('CALL_FUNCTION_VAR',
|
||||
|
Reference in New Issue
Block a user