diff --git a/test/bytecode_3.3_run/04_def_annotate.pyc b/test/bytecode_3.3_run/04_def_annotate.pyc new file mode 100644 index 00000000..a0462659 Binary files /dev/null and b/test/bytecode_3.3_run/04_def_annotate.pyc differ diff --git a/test/bytecode_3.3_run/15_assert.pyc b/test/bytecode_3.3_run/15_assert.pyc index 83aa685a..37df7a85 100644 Binary files a/test/bytecode_3.3_run/15_assert.pyc and b/test/bytecode_3.3_run/15_assert.pyc differ diff --git a/test/bytecode_3.4_run/04_def_annotate.pyc b/test/bytecode_3.4_run/04_def_annotate.pyc new file mode 100644 index 00000000..89efdcd0 Binary files /dev/null and b/test/bytecode_3.4_run/04_def_annotate.pyc differ diff --git a/test/bytecode_3.5_run/04_def_annotate.pyc b/test/bytecode_3.5_run/04_def_annotate.pyc new file mode 100644 index 00000000..c21f152b Binary files /dev/null and b/test/bytecode_3.5_run/04_def_annotate.pyc differ diff --git a/test/bytecode_3.6_run/00_docstring.pyc b/test/bytecode_3.6_run/00_docstring.pyc new file mode 100644 index 00000000..1f7aadc1 Binary files /dev/null and b/test/bytecode_3.6_run/00_docstring.pyc differ diff --git a/test/bytecode_3.6_run/04_def_annotate.pyc b/test/bytecode_3.6_run/04_def_annotate.pyc index 11e60bea..8aac3046 100644 Binary files a/test/bytecode_3.6_run/04_def_annotate.pyc and b/test/bytecode_3.6_run/04_def_annotate.pyc differ diff --git a/test/simple_source/bug31/04_def_annotate.py b/test/simple_source/bug31/04_def_annotate.py index 7157e803..a7ee2128 100644 --- a/test/simple_source/bug31/04_def_annotate.py +++ b/test/simple_source/bug31/04_def_annotate.py @@ -1,13 +1,42 @@ -# Python 3 annotations +# Python 3 positional, kwonly, varargs, and annotations. Ick. -def foo(a, b: 'annotating b', c: int) -> float: - print(a + b + c) +# RUNNABLE! +def test1(args_1, c: int, w=4, *varargs: int, **kwargs: 'annotating kwargs') -> tuple: + return (args_1, c, w, kwargs) + +def test2(args_1, args_2, c: int, w=4, *varargs: int, **kwargs: 'annotating kwargs'): + return (args_1, args_2, c, w, varargs, kwargs) + +def test3(c: int, w=4, *varargs: int, **kwargs: 'annotating kwargs') -> float: + return 5.4 + +def test4(a: float, c: int, *varargs: int, **kwargs: 'annotating kwargs') -> float: + return 5.4 + +def test5(a: float, c: int = 5, *varargs: int, **kwargs: 'annotating kwargs') -> float: + return 5.4 + +def test6(a: float, c: int, test=None): + return (a, c, test) + +def test7(*varargs: int, **kwargs): + return (varargs, kwargs) + +def test8(x=55, *varargs: int, **kwargs) -> list: + return (x, varargs, kwargs) + +def test9(arg_1=55, *varargs: int, y=5, **kwargs): + return x, varargs, int, y, kwargs + +def test10(args_1, b: 'annotating b', c: int) -> float: + return 5.4 + +class IOBase: + pass # Python 3.1 _pyio.py uses the -> "IOBase" annotation -def open(file, mode = "r", buffering = None, - encoding = None, errors = None, - newline = None, closefd = True) -> "IOBase": - return text +def o(f, mode = "r", buffering = None) -> "IOBase": + return (f, mode, buffering) def foo1(x: 'an argument that defaults to 5' = 5): print(x) @@ -18,29 +47,38 @@ def div(a: dict(type=float, help='the dividend'), """Divide a by b""" return a / b -class TestSignatureObject(): - def test_signature_on_wkwonly(self): - def test(*, a:float, b:str) -> int: - pass +# FIXME: +# class TestSignatureObject(): +# def test_signature_on_wkwonly(self): +# def test(*, a:float, b:str) -> int: +# pass class SupportsInt(): def __int__(self) -> int: pass -def foo2(a, b: 'annotating b', c: int, *args: str) -> float: - assert foo2.__annotations__['b'] == 'annotating b' - assert foo2.__annotations__['c'] == int - assert foo2.__annotations__['args'] == str - assert foo2.__annotations__['return'] == float +def ann1(args_1, b: 'annotating b', c: int, *varargs: str) -> float: + assert ann1.__annotations__['b'] == 'annotating b' + assert ann1.__annotations__['c'] == int + assert ann1.__annotations__['varargs'] == str + assert ann1.__annotations__['return'] == float -def foo3(a, b: int = 5, **kwargs: float) -> float: - assert foo3.__annotations__['b'] == int - assert foo3.__annotations__['kwargs'] == float - assert foo3.__annotations__['return'] == float +def ann2(args_1, b: int = 5, **kwargs: float) -> float: + assert ann2.__annotations__['b'] == int + assert ann2.__annotations__['kwargs'] == float + assert ann2.__annotations__['return'] == float assert b == 5 +assert test1(1, 5) == (1, 5, 4, {}) +assert test1(1, 5, 6, foo='bar') == (1, 5, 6, {'foo': 'bar'}) +assert test2(2, 3, 4) == (2, 3, 4, 4, (), {}) +assert test3(10, foo='bar') == 5.4 +assert test4(9.5, 7, 6, 4, bar='baz') == 5.4 +### FIXME: fill in... +assert test6(1.2, 3) == (1.2, 3, None) +assert test6(2.3, 4, 5) == (2.3, 4, 5) -foo2(1, 'test', 5) -foo3(1) +ann1(1, 'test', 5) +ann2(1) diff --git a/uncompyle6/parsers/parse3.py b/uncompyle6/parsers/parse3.py index 757c19c5..a77f1474 100644 --- a/uncompyle6/parsers/parse3.py +++ b/uncompyle6/parsers/parse3.py @@ -26,6 +26,7 @@ If we succeed in creating a parse tree, then we have a Python program that a later phase can turn into a sequence of ASCII text. """ +import re from uncompyle6.scanners.tok import Token from uncompyle6.parser import PythonParser, PythonParserSingle, nop_func from uncompyle6.parsers.treenode import SyntaxTree @@ -1050,7 +1051,7 @@ class Python3Parser(PythonParser): (kwargs, 'pos_arg ' * args_pos, opname)) self.add_unique_rule(rule, opname, token.attr, customize) - if opname.startswith('MAKE_FUNCTION_A'): + if re.search('^MAKE_FUNCTION.*_A', opname): if self.version >= 3.6: rule = ('mkfunc_annotate ::= %s%sannotate_tuple LOAD_CONST LOAD_STR %s' % (('pos_arg ' * (args_pos)), @@ -1063,21 +1064,33 @@ class Python3Parser(PythonParser): # Normally we remove EXTENDED_ARG from the opcodes, but in the case of # annotated functions can use the EXTENDED_ARG tuple to signal we have an annotated function. # Yes this is a little hacky - rule = ('mkfunc_annotate ::= %s%sannotate_tuple LOAD_CONST LOAD_STR EXTENDED_ARG %s' % - (('pos_arg ' * (args_pos)), + if self.version < 3.5: + # 3.3 and 3.4 put kwargs before pos_arg + pos_kw_tuple = (('kwargs ' * args_kw), ('pos_arg ' * (args_pos))) + else: + # 3.5 puts pos_arg before kwargs + pos_kw_tuple = (('pos_arg ' * (args_pos), ('kwargs ' * args_kw))) + if self.version < 3.5: + # 3.3 and 3.4 put kwargs before pos_arg + pos_kw_tuple = (('kwargs ' * args_kw), ('pos_arg ' * (args_pos))) + else: + # 3.5 puts pos_arg before kwargs + pos_kw_tuple = (('pos_arg ' * (args_pos), ('kwargs ' * args_kw))) + rule = ('mkfunc_annotate ::= %s%s%s%sannotate_tuple LOAD_CONST LOAD_STR EXTENDED_ARG %s' % + ( pos_kw_tuple[0], pos_kw_tuple[1], ('call ' * (annotate_args-1)), opname)) self.add_unique_rule(rule, opname, token.attr, customize) - rule = ('mkfunc_annotate ::= %s%sannotate_tuple LOAD_CONST LOAD_STR EXTENDED_ARG %s' % - (('pos_arg ' * (args_pos)), + rule = ('mkfunc_annotate ::= %s%s%sannotate_tuple LOAD_CONST LOAD_STR EXTENDED_ARG %s' % + ( pos_kw_tuple[0], pos_kw_tuple[1], ('annotate_arg ' * (annotate_args-1)), opname)) else: # See above comment about use of EXTENDED_ARG - rule = ('mkfunc_annotate ::= %s%sannotate_tuple LOAD_CONST EXTENDED_ARG %s' % - (('pos_arg ' * (args_pos)), + rule = ('mkfunc_annotate ::= %s%s%sannotate_tuple LOAD_CONST EXTENDED_ARG %s' % + (('pos_arg ' * (args_pos)), ('kwargs ' * args_kw), ('annotate_arg ' * (annotate_args-1)), opname)) self.add_unique_rule(rule, opname, token.attr, customize) - rule = ('mkfunc_annotate ::= %s%sannotate_tuple LOAD_CONST EXTENDED_ARG %s' % - (('pos_arg ' * (args_pos)), + rule = ('mkfunc_annotate ::= %s%s%sannotate_tuple LOAD_CONST EXTENDED_ARG %s' % + (('pos_arg ' * (args_pos)), ('kwargs ' * args_kw), ('call ' * (annotate_args-1)), opname)) self.addRule(rule, nop_func) elif opname == 'RETURN_VALUE_LAMBDA': diff --git a/uncompyle6/scanners/scanner3.py b/uncompyle6/scanners/scanner3.py index 491ac49d..a3d0a2f3 100644 --- a/uncompyle6/scanners/scanner3.py +++ b/uncompyle6/scanners/scanner3.py @@ -333,9 +333,10 @@ class Scanner3(Scanner): attr = attr[:4] # remove last value: attr[5] == False else: pos_args, name_pair_args, annotate_args = parse_fn_counts(inst.argval) - pattr = ("%d positional, %d keyword pair, %d annotated" % + pattr = ("%d positional, %d keyword only, %d annotated" % (pos_args, name_pair_args, annotate_args)) if name_pair_args > 0: + # FIXME: this should probably be K_ opname = '%s_N%d' % (opname, name_pair_args) pass if annotate_args > 0: diff --git a/uncompyle6/semantics/make_function.py b/uncompyle6/semantics/make_function.py index 9a1933cc..0b4afdb1 100644 --- a/uncompyle6/semantics/make_function.py +++ b/uncompyle6/semantics/make_function.py @@ -135,10 +135,6 @@ def make_function3_annotate(self, node, is_lambda, nested=1, indent = ' ' * l line_number = self.line_number - if code_has_star_arg(code): - self.write('*%s' % code.co_varnames[argc + kw_pairs]) - argc += 1 - i = len(paramnames) - len(defparams) suffix = '' @@ -148,7 +144,7 @@ def make_function3_annotate(self, node, is_lambda, nested=1, self.write(suffix, param) suffix = ', ' if param in annotate_tuple[0].attr: - p = annotate_tuple[0].attr.index(param) + p = annotate_tuple[0].attr.index(param) + pos_args self.write(': ') self.preorder(node[p]) if (line_number != self.line_number): @@ -187,7 +183,17 @@ def make_function3_annotate(self, node, is_lambda, nested=1, suffix = ', ' + if code_has_star_arg(code): + star_arg = code.co_varnames[argc + kw_pairs] + self.write(suffix, '*%s' % star_arg) + if star_arg in annotate_tuple[0].attr: + p = annotate_tuple[0].attr.index(star_arg) + pos_args + kw_args + self.write(': ') + self.preorder(node[p]) + argc += 1 + # self.println(indent, '#flags:\t', int(code.co_flags)) + ends_in_comma = False if kw_args + annotate_argc > 0: if no_paramnames: if not code_has_star_arg(code): @@ -198,49 +204,47 @@ def make_function3_annotate(self, node, is_lambda, nested=1, pass else: self.write(", ") + ends_in_comma = True - kwargs = node[0] - last = len(kwargs)-1 - i = 0 - for n in node[0]: - if n == 'kwarg': - if (line_number != self.line_number): - self.write("\n" + indent) - line_number = self.line_number - self.write('%s=' % n[0].pattr) - self.preorder(n[1]) - if i < last: - self.write(', ') - i += 1 - pass - pass - annotate_args = [] - for n in node: - if n == 'annotate_arg': - annotate_args.append(n[0]) - elif n == 'annotate_tuple': - t = n[0].attr - if t[-1] == 'return': - t = t[0:-1] - annotate_args = annotate_args[:-1] - pass - last = len(annotate_args) - 1 - for i in range(len(annotate_args)): - self.write("%s: " % (t[i])) - self.preorder(annotate_args[i]) - if i < last: - self.write(', ') - pass - pass - break + kwargs = node[1] + last = len(kwargs)-1 + i = 0 + for n in node[1]: + if n == 'kwarg': + if argc > 0 and not ends_in_comma: + self.write(', ') + if (line_number != self.line_number): + self.write("\n" + indent) + line_number = self.line_number + kn = n[0].pattr + if kn in annotate_tuple[0].attr: + p = annotate_tuple[0].attr.index(star_arg) + pos_args + kw_args + self.write('%s: ' % kn) + self.preorder(node[p]) + self.write('=') + else: + self.write('%s=' % kn) + self.preorder(n[1]) + if i < last: + self.write(', ') + ends_in_comma = True + else: + ends_in_comma = False + i += 1 pass pass + pass if code_has_star_star_arg(code): - if argc > 0: + if argc > 0 and not ends_in_comma: self.write(', ') - self.write('**%s' % code.co_varnames[argc + kw_pairs]) + star_star_arg = code.co_varnames[argc + kw_pairs] + self.write('**%s' % star_star_arg) + if star_star_arg in annotate_tuple[0].attr: + p = annotate_tuple[0].attr.index(star_star_arg) + pos_args + kw_args + self.write(': ') + self.preorder(node[p]) if is_lambda: self.write(": ")