You've already forked python-uncompyle6
mirror of
https://github.com/rocky/python-uncompyle6.git
synced 2025-08-03 00:45:53 +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
|
# std
|
||||||
import string
|
import string
|
||||||
# 3rd party
|
# 3rd party
|
||||||
from hypothesis import given, assume, strategies as st
|
from hypothesis import given, assume, example, settings, strategies as st
|
||||||
import pytest
|
import pytest
|
||||||
# uncompyle
|
# uncompyle
|
||||||
from validate import validate_uncompyle
|
from validate import validate_uncompyle
|
||||||
|
from test_fstring import expressions
|
||||||
|
|
||||||
|
|
||||||
alpha = st.sampled_from(string.ascii_lowercase)
|
alpha = st.sampled_from(string.ascii_lowercase)
|
||||||
numbers = st.sampled_from(string.digits)
|
numbers = st.sampled_from(string.digits)
|
||||||
alphanum = st.sampled_from(string.ascii_lowercase + 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
|
@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.
|
Strategy factory for generating function calls.
|
||||||
|
|
||||||
@@ -22,21 +26,49 @@ def function_calls(draw):
|
|||||||
|
|
||||||
:return: The function call text.
|
:return: The function call text.
|
||||||
"""
|
"""
|
||||||
list1 = st.lists(alpha, min_size=0, max_size=1)
|
st_positional_args = st.lists(
|
||||||
list3 = st.lists(alpha, min_size=0, max_size=3)
|
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)
|
positional_args = draw(st_positional_args)
|
||||||
named_args = [x + '=0' for x in draw(list3)]
|
keyword_args = draw(st_keyword_args)
|
||||||
star_args = ['*' + x for x in draw(list1)]
|
st_values = st.lists(
|
||||||
double_star_args = ['**' + x for x in draw(list1)]
|
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)
|
draw(st.randoms()).shuffle(arguments)
|
||||||
arguments = ','.join(arguments)
|
arguments = ','.join(arguments)
|
||||||
|
|
||||||
function_call = 'fn({arguments})'.format(arguments=arguments)
|
function_call = 'fn({arguments})'.format(arguments=arguments)
|
||||||
try:
|
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
|
# 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
|
# types of arguments are supported so we don't need to check that the
|
||||||
# expression compiles like this.
|
# expression compiles like this.
|
||||||
@@ -46,9 +78,56 @@ def function_calls(draw):
|
|||||||
return function_call
|
return function_call
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail()
|
def test_function_no_args():
|
||||||
def test_CALL_FUNCTION():
|
validate_uncompyle("fn()")
|
||||||
validate_uncompyle("fn(w,m,f)")
|
|
||||||
|
|
||||||
|
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()
|
@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)")
|
validate_uncompyle("fn(a=0,**g)")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail()
|
|
||||||
def test_CALL_FUNCTION_KW():
|
|
||||||
validate_uncompyle("fn(j=0)")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail()
|
@pytest.mark.xfail()
|
||||||
def test_CALL_FUNCTION_EX():
|
def test_CALL_FUNCTION_EX():
|
||||||
validate_uncompyle("fn(*g,**j)")
|
validate_uncompyle("fn(*g,**j)")
|
||||||
@@ -100,29 +174,3 @@ def test_BUILD_CONST_KEY_MAP_CALL_FUNCTION_EX():
|
|||||||
@given(function_calls())
|
@given(function_calls())
|
||||||
def test_function_call(function_call):
|
def test_function_call(function_call):
|
||||||
validate_uncompyle(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)
|
""", nop_func)
|
||||||
continue
|
continue
|
||||||
elif opname in ('CALL_FUNCTION', 'CALL_FUNCTION_VAR',
|
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)
|
self.custom_classfunc_rule(opname, token, customize)
|
||||||
elif opname == 'LOAD_DICTCOMP':
|
elif opname == 'LOAD_DICTCOMP':
|
||||||
rule_pat = ("dictcomp ::= LOAD_DICTCOMP %sMAKE_FUNCTION_0 expr "
|
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)
|
""" % (fstring_expr_or_str_n, fstring_expr_or_str_n, "fstring_expr_or_str " * v)
|
||||||
self.add_unique_doc_rules(rules_str, customize)
|
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):
|
class Python36ParserSingle(Python36Parser, PythonParserSingle):
|
||||||
pass
|
pass
|
||||||
|
@@ -28,6 +28,8 @@ class Scanner36(Scanner3):
|
|||||||
if t.op == self.opc.CALL_FUNCTION_EX and t.attr & 1:
|
if t.op == self.opc.CALL_FUNCTION_EX and t.attr & 1:
|
||||||
t.type = 'CALL_FUNCTION_EX_KW'
|
t.type = 'CALL_FUNCTION_EX_KW'
|
||||||
pass
|
pass
|
||||||
|
if t.op == self.opc.CALL_FUNCTION_KW:
|
||||||
|
t.type = 'CALL_FUNCTION_KW_{t.attr}'.format(**locals())
|
||||||
pass
|
pass
|
||||||
return tokens, customize
|
return tokens, customize
|
||||||
|
|
||||||
|
@@ -392,6 +392,7 @@ class SourceWalker(GenericASTTraversal, object):
|
|||||||
'fstring_single': ( "f'{%c%{conversion}}'", 0),
|
'fstring_single': ( "f'{%c%{conversion}}'", 0),
|
||||||
'fstring_multi': ( "f'%c'", 0),
|
'fstring_multi': ( "f'%c'", 0),
|
||||||
'func_args36': ( "%c(**", 0),
|
'func_args36': ( "%c(**", 0),
|
||||||
|
#'kwargs_only_36': ( "%c(**", 0),
|
||||||
})
|
})
|
||||||
TABLE_R.update({
|
TABLE_R.update({
|
||||||
'CALL_FUNCTION_EX': ('%c(*%P)', 0, (1, 2, ', ', 100)),
|
'CALL_FUNCTION_EX': ('%c(*%P)', 0, (1, 2, ', ', 100)),
|
||||||
@@ -413,6 +414,17 @@ class SourceWalker(GenericASTTraversal, object):
|
|||||||
self.default(node)
|
self.default(node)
|
||||||
self.n_fstring_single = n_fstring_single
|
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
|
return
|
||||||
|
|
||||||
f = property(lambda s: s.params['f'],
|
f = property(lambda s: s.params['f'],
|
||||||
@@ -1800,6 +1812,8 @@ class SourceWalker(GenericASTTraversal, object):
|
|||||||
if k.startswith('CALL_METHOD'):
|
if k.startswith('CALL_METHOD'):
|
||||||
# This happens in PyPy only
|
# This happens in PyPy only
|
||||||
TABLE_R[k] = ('%c(%P)', 0, (1, -1, ', ', 100))
|
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':
|
elif op == 'CALL_FUNCTION':
|
||||||
TABLE_R[k] = ('%c(%P)', 0, (1, -1, ', ', 100))
|
TABLE_R[k] = ('%c(%P)', 0, (1, -1, ', ', 100))
|
||||||
elif op in ('CALL_FUNCTION_VAR',
|
elif op in ('CALL_FUNCTION_VAR',
|
||||||
|
Reference in New Issue
Block a user