You've already forked python-uncompyle6
mirror of
https://github.com/rocky/python-uncompyle6.git
synced 2025-08-03 00:45:53 +08:00
Port over some recent decompyle3 3.8 fixes
This commit is contained in:
BIN
test/bytecode_3.8_run/00_bug_dict_comp.pyc
Normal file
BIN
test/bytecode_3.8_run/00_bug_dict_comp.pyc
Normal file
Binary file not shown.
@@ -14,6 +14,12 @@ assert (
|
|||||||
assert "def0" == f"{abc}0"
|
assert "def0" == f"{abc}0"
|
||||||
assert "defdef" == f"{abc}{abc!s}"
|
assert "defdef" == f"{abc}{abc!s}"
|
||||||
|
|
||||||
|
# From 3.8 test/test_string.py
|
||||||
|
# We had the precedence of yield vs. lambda incorrect.
|
||||||
|
def fn(x):
|
||||||
|
yield f"x:{yield (lambda i: x * i)}"
|
||||||
|
|
||||||
|
|
||||||
# From 3.6 functools.py
|
# From 3.6 functools.py
|
||||||
# Bug was handling format operator strings.
|
# Bug was handling format operator strings.
|
||||||
|
|
||||||
|
12
test/simple_source/bug38/00_bug_dict_comp.py
Normal file
12
test/simple_source/bug38/00_bug_dict_comp.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Issue 104
|
||||||
|
# Python 3.8 reverses the order or keys and values in
|
||||||
|
# dictionary comprehensions from the order in all previous Pythons.
|
||||||
|
# Also we were looking in the wrong place for the collection of the
|
||||||
|
# dictionary comprehension
|
||||||
|
# RUNNABLE!
|
||||||
|
|
||||||
|
"""This program is self-checking!"""
|
||||||
|
x = [(0, [1]), (2, [3])]
|
||||||
|
for i in range(0, 1):
|
||||||
|
y = {key: val[i - 1] for (key, val) in x}
|
||||||
|
assert y == {0: 1, 2: 3}
|
@@ -17,7 +17,7 @@ spark grammar differences over Python 3.7 for Python 3.8
|
|||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
from uncompyle6.parser import PythonParserSingle
|
from uncompyle6.parser import PythonParserSingle, nop_func
|
||||||
from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG
|
from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG
|
||||||
from uncompyle6.parsers.parse37 import Python37Parser
|
from uncompyle6.parsers.parse37 import Python37Parser
|
||||||
|
|
||||||
@@ -367,6 +367,195 @@ class Python38Parser(Python37Parser):
|
|||||||
self.check_reduce["whilestmt38"] = "tokens"
|
self.check_reduce["whilestmt38"] = "tokens"
|
||||||
self.check_reduce["try_elsestmtl38"] = "AST"
|
self.check_reduce["try_elsestmtl38"] = "AST"
|
||||||
|
|
||||||
|
# For a rough break out on the first word. This may
|
||||||
|
# include instructions that don't need customization,
|
||||||
|
# but we'll do a finer check after the rough breakout.
|
||||||
|
customize_instruction_basenames = frozenset(
|
||||||
|
(
|
||||||
|
"BEFORE",
|
||||||
|
"BUILD",
|
||||||
|
"CALL",
|
||||||
|
"DICT",
|
||||||
|
"GET",
|
||||||
|
"FORMAT",
|
||||||
|
"LIST",
|
||||||
|
"LOAD",
|
||||||
|
"MAKE",
|
||||||
|
"SETUP",
|
||||||
|
"UNPACK",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Opcode names in the custom_ops_processed set have rules that get added
|
||||||
|
# unconditionally and the rules are constant. So they need to be done
|
||||||
|
# only once and if we see the opcode a second we don't have to consider
|
||||||
|
# adding more rules.
|
||||||
|
#
|
||||||
|
custom_ops_processed = frozenset()
|
||||||
|
|
||||||
|
# A set of instruction operation names that exist in the token stream.
|
||||||
|
# We use this customize the grammar that we create.
|
||||||
|
# 2.6-compatible set comprehensions
|
||||||
|
self.seen_ops = frozenset([t.kind for t in tokens])
|
||||||
|
self.seen_op_basenames = frozenset(
|
||||||
|
[opname[: opname.rfind("_")] for opname in self.seen_ops]
|
||||||
|
)
|
||||||
|
|
||||||
|
custom_ops_processed = set(["DICT_MERGE"])
|
||||||
|
|
||||||
|
# Loop over instructions adding custom grammar rules based on
|
||||||
|
# a specific instruction seen.
|
||||||
|
|
||||||
|
if "PyPy" in customize:
|
||||||
|
self.addRule(
|
||||||
|
"""
|
||||||
|
stmt ::= assign3_pypy
|
||||||
|
stmt ::= assign2_pypy
|
||||||
|
assign3_pypy ::= expr expr expr store store store
|
||||||
|
assign2_pypy ::= expr expr store store
|
||||||
|
""",
|
||||||
|
nop_func,
|
||||||
|
)
|
||||||
|
|
||||||
|
n = len(tokens)
|
||||||
|
# Determine if we have an iteration CALL_FUNCTION_1.
|
||||||
|
has_get_iter_call_function1 = False
|
||||||
|
for i, token in enumerate(tokens):
|
||||||
|
if (
|
||||||
|
token == "GET_ITER"
|
||||||
|
and i < n - 2
|
||||||
|
and tokens[i + 1] == "CALL_FUNCTION_1"
|
||||||
|
):
|
||||||
|
has_get_iter_call_function1 = True
|
||||||
|
|
||||||
|
for i, token in enumerate(tokens):
|
||||||
|
opname = token.kind
|
||||||
|
|
||||||
|
# Do a quick breakout before testing potentially
|
||||||
|
# each of the dozen or so instruction in if elif.
|
||||||
|
if (
|
||||||
|
opname[: opname.find("_")] not in customize_instruction_basenames
|
||||||
|
or opname in custom_ops_processed
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
opname_base = opname[: opname.rfind("_")]
|
||||||
|
|
||||||
|
# Do a quick breakout before testing potentially
|
||||||
|
# each of the dozen or so instruction in if elif.
|
||||||
|
if (
|
||||||
|
opname[: opname.find("_")] not in customize_instruction_basenames
|
||||||
|
or opname in custom_ops_processed
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
if opname_base in (
|
||||||
|
"BUILD_LIST",
|
||||||
|
"BUILD_SET",
|
||||||
|
"BUILD_SET_UNPACK",
|
||||||
|
"BUILD_TUPLE",
|
||||||
|
"BUILD_TUPLE_UNPACK",
|
||||||
|
):
|
||||||
|
v = token.attr
|
||||||
|
|
||||||
|
is_LOAD_CLOSURE = False
|
||||||
|
if opname_base == "BUILD_TUPLE":
|
||||||
|
# If is part of a "load_closure", then it is not part of a
|
||||||
|
# "list".
|
||||||
|
is_LOAD_CLOSURE = True
|
||||||
|
for j in range(v):
|
||||||
|
if tokens[i - j - 1].kind != "LOAD_CLOSURE":
|
||||||
|
is_LOAD_CLOSURE = False
|
||||||
|
break
|
||||||
|
if is_LOAD_CLOSURE:
|
||||||
|
rule = "load_closure ::= %s%s" % (("LOAD_CLOSURE " * v), opname)
|
||||||
|
self.add_unique_rule(rule, opname, token.attr, customize)
|
||||||
|
|
||||||
|
elif opname_base == "BUILD_LIST":
|
||||||
|
v = token.attr
|
||||||
|
if v == 0:
|
||||||
|
rule_str = """
|
||||||
|
list ::= BUILD_LIST_0
|
||||||
|
list_unpack ::= BUILD_LIST_0 expr LIST_EXTEND
|
||||||
|
list ::= list_unpack
|
||||||
|
"""
|
||||||
|
self.add_unique_doc_rules(rule_str, customize)
|
||||||
|
|
||||||
|
elif opname == "BUILD_TUPLE_UNPACK_WITH_CALL":
|
||||||
|
# FIXME: should this be parameterized by EX value?
|
||||||
|
self.addRule(
|
||||||
|
"""expr ::= call_ex_kw3
|
||||||
|
call_ex_kw3 ::= expr
|
||||||
|
build_tuple_unpack_with_call
|
||||||
|
expr
|
||||||
|
CALL_FUNCTION_EX_KW
|
||||||
|
""",
|
||||||
|
nop_func,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not is_LOAD_CLOSURE or v == 0:
|
||||||
|
# We do this complicated test to speed up parsing of
|
||||||
|
# pathelogically long literals, especially those over 1024.
|
||||||
|
build_count = token.attr
|
||||||
|
thousands = build_count // 1024
|
||||||
|
thirty32s = (build_count // 32) % 32
|
||||||
|
if thirty32s > 0:
|
||||||
|
rule = "expr32 ::=%s" % (" expr" * 32)
|
||||||
|
self.add_unique_rule(rule, opname_base, build_count, customize)
|
||||||
|
pass
|
||||||
|
if thousands > 0:
|
||||||
|
self.add_unique_rule(
|
||||||
|
"expr1024 ::=%s" % (" expr32" * 32),
|
||||||
|
opname_base,
|
||||||
|
build_count,
|
||||||
|
customize,
|
||||||
|
)
|
||||||
|
pass
|
||||||
|
collection = opname_base[opname_base.find("_") + 1 :].lower()
|
||||||
|
rule = (
|
||||||
|
("%s ::= " % collection)
|
||||||
|
+ "expr1024 " * thousands
|
||||||
|
+ "expr32 " * thirty32s
|
||||||
|
+ "expr " * (build_count % 32)
|
||||||
|
+ opname
|
||||||
|
)
|
||||||
|
self.add_unique_rules(["expr ::= %s" % collection, rule], customize)
|
||||||
|
continue
|
||||||
|
continue
|
||||||
|
elif opname == "LOAD_CLOSURE":
|
||||||
|
self.addRule("""load_closure ::= LOAD_CLOSURE+""", nop_func)
|
||||||
|
|
||||||
|
elif opname == "LOOKUP_METHOD":
|
||||||
|
# A PyPy speciality - DRY with parse3
|
||||||
|
self.addRule(
|
||||||
|
"""
|
||||||
|
expr ::= attribute
|
||||||
|
attribute ::= expr LOOKUP_METHOD
|
||||||
|
""",
|
||||||
|
nop_func,
|
||||||
|
)
|
||||||
|
custom_ops_processed.add(opname)
|
||||||
|
|
||||||
|
elif opname == "MAKE_FUNCTION_8":
|
||||||
|
if "LOAD_DICTCOMP" in self.seen_ops:
|
||||||
|
# Is there something general going on here?
|
||||||
|
rule = """
|
||||||
|
dict_comp ::= load_closure LOAD_DICTCOMP LOAD_STR
|
||||||
|
MAKE_FUNCTION_8 expr
|
||||||
|
GET_ITER CALL_FUNCTION_1
|
||||||
|
"""
|
||||||
|
self.addRule(rule, nop_func)
|
||||||
|
elif "LOAD_SETCOMP" in self.seen_ops:
|
||||||
|
rule = """
|
||||||
|
set_comp ::= load_closure LOAD_SETCOMP LOAD_STR
|
||||||
|
MAKE_FUNCTION_CLOSURE expr
|
||||||
|
GET_ITER CALL_FUNCTION_1
|
||||||
|
"""
|
||||||
|
self.addRule(rule, nop_func)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def reduce_is_invalid(self, rule, ast, tokens, first, last):
|
def reduce_is_invalid(self, rule, ast, tokens, first, last):
|
||||||
invalid = super(Python38Parser,
|
invalid = super(Python38Parser,
|
||||||
self).reduce_is_invalid(rule, ast,
|
self).reduce_is_invalid(rule, ast,
|
||||||
|
@@ -50,14 +50,15 @@ NO_PARENTHESIS_EVER = 100
|
|||||||
# fmt: off
|
# fmt: off
|
||||||
PRECEDENCE = {
|
PRECEDENCE = {
|
||||||
"named_expr": 40, # :=
|
"named_expr": 40, # :=
|
||||||
"yield": 38, # Needs to be below named_expr
|
|
||||||
"yield_from": 38,
|
|
||||||
"tuple_list_starred": 38, # *x, *y, *z - about at the level of yield?
|
|
||||||
"dict_unpack": 38, # **kwargs
|
"dict_unpack": 38, # **kwargs
|
||||||
"list_unpack": 38, # *args
|
"list_unpack": 38, # *args
|
||||||
|
"yield_from": 38,
|
||||||
|
"tuple_list_starred": 38, # *x, *y, *z - about at the level of yield?
|
||||||
|
|
||||||
"_lambda_body": 30,
|
"_lambda_body": 30,
|
||||||
"lambda_body": 30, # lambda ... : lambda_body
|
"lambda_body": 32, # lambda ... : lambda_body
|
||||||
|
|
||||||
|
"yield": 30, # Needs to be below named_expr and lambda_body
|
||||||
|
|
||||||
"if_exp": 28, # IfExp ( a if x else b)
|
"if_exp": 28, # IfExp ( a if x else b)
|
||||||
"if_exp_lambda": 28, # IfExp involving a lambda expression
|
"if_exp_lambda": 28, # IfExp involving a lambda expression
|
||||||
|
@@ -66,6 +66,9 @@ def customize_for_version38(self, version):
|
|||||||
(1, "c_suite_stmts_opt"),
|
(1, "c_suite_stmts_opt"),
|
||||||
(-2, "c_suite_stmts_opt"),
|
(-2, "c_suite_stmts_opt"),
|
||||||
),
|
),
|
||||||
|
# Python 3.8 reverses the order of keys and items
|
||||||
|
# from all prior versions of Python.
|
||||||
|
"dict_comp_body": ("%c: %c", (0, "expr"), (1, "expr"),),
|
||||||
"except_cond1a": ("%|except %c:\n", (1, "expr"),),
|
"except_cond1a": ("%|except %c:\n", (1, "expr"),),
|
||||||
"except_cond_as": (
|
"except_cond_as": (
|
||||||
"%|except %c as %c:\n",
|
"%|except %c as %c:\n",
|
||||||
|
@@ -302,6 +302,9 @@ class ComprehensionMixin:
|
|||||||
store = set_iter_async[1]
|
store = set_iter_async[1]
|
||||||
assert store == "store"
|
assert store == "store"
|
||||||
n = set_iter_async[2]
|
n = set_iter_async[2]
|
||||||
|
elif node == "list_comp" and tree[0] == "expr":
|
||||||
|
tree = tree[0][0]
|
||||||
|
n = tree[iter_index]
|
||||||
else:
|
else:
|
||||||
n = tree[iter_index]
|
n = tree[iter_index]
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user