diff --git a/test/bytecode_3.6_run/01_fstring.pyc b/test/bytecode_3.6_run/01_fstring.pyc index 89d675e6..d33ffc50 100644 Binary files a/test/bytecode_3.6_run/01_fstring.pyc and b/test/bytecode_3.6_run/01_fstring.pyc differ diff --git a/test/bytecode_3.7_run/01_fstring.pyc b/test/bytecode_3.7_run/01_fstring.pyc index 9e300b53..80eacf8c 100644 Binary files a/test/bytecode_3.7_run/01_fstring.pyc and b/test/bytecode_3.7_run/01_fstring.pyc differ diff --git a/test/bytecode_3.8_run/01_fstring.pyc b/test/bytecode_3.8_run/01_fstring.pyc index 8555c953..292e6c92 100644 Binary files a/test/bytecode_3.8_run/01_fstring.pyc and b/test/bytecode_3.8_run/01_fstring.pyc differ diff --git a/test/simple_source/bug36/01_fstring.py b/test/simple_source/bug36/01_fstring.py index dd19861e..b218f4f8 100644 --- a/test/simple_source/bug36/01_fstring.py +++ b/test/simple_source/bug36/01_fstring.py @@ -67,3 +67,16 @@ def _repr_fn(fields): fields = ['a', 'b', 'c'] assert _repr_fn(fields) == ['return xx + f"(a={self.a!r}, b={self.b!r}, c={self.c!r})"'] + + +# From Python 3.7 test_fstring. Why this kind of thing matter seems a bit +# academic, but decompile an equivalent thing. For compatiblity with older +# Python we'll use "%" instead of a format string +def f(): + f'''Not a docstring''' +def g(): + '''Not a docstring''' \ + f'' + +assert f.__doc__ is None +assert g.__doc__ is None diff --git a/uncompyle6/semantics/consts.py b/uncompyle6/semantics/consts.py index 0fdb3698..933a842d 100644 --- a/uncompyle6/semantics/consts.py +++ b/uncompyle6/semantics/consts.py @@ -353,6 +353,13 @@ TABLE_DIRECT = { 'print_nl_to': ( '%|print >> %c\n', 0 ), 'print_to_items': ( '%C', (0, 2, ', ') ), + # This is only generated by transform + # it is a string at the beginning of a function that is *not* a docstring + # 3.7 test_fstring.py tests for this kind of crap. + # For compatibility with older Python, we'll use "%" instead of + # a format string. + "string_at_beginning": ( '%|"%%s" %% %c\n', 0), + 'call_stmt': ( '%|%p\n', (0, 200)), 'break': ( '%|break\n', ), 'continue': ( '%|continue\n', ), diff --git a/uncompyle6/semantics/transform.py b/uncompyle6/semantics/transform.py index 78cfdf6f..044ddfe7 100644 --- a/uncompyle6/semantics/transform.py +++ b/uncompyle6/semantics/transform.py @@ -30,6 +30,13 @@ def is_docstring(node): except: return False +def is_not_docstring(call_stmt_node): + try: + return (call_stmt_node == "call_stmt" and + call_stmt_node[0][0] == "LOAD_STR" and + call_stmt_node[1] == "POP_TOP") + except: + return False class TreeTransform(GenericASTTraversal, object): def __init__(self, version, show_ast=None, is_pypy=False): @@ -352,6 +359,17 @@ class TreeTransform(GenericASTTraversal, object): self.ast = copy(ast) self.ast = self.traverse(self.ast, is_lambda=False) + try: + # Disambiguate a string (expression) which appears as a "call_stmt" at + # the beginning of a function versus a docstring. Seems pretty academic, + # but this is Python. + call_stmt = ast[0][0][0] + if is_not_docstring(call_stmt): + call_stmt.kind = "string_at_beginning" + call_stmt.transformed_by = "transform" + pass + except: + pass try: for i in range(len(self.ast)): if is_docstring(self.ast[i]):