diff --git a/test/bytecode_3.7_run/01_assert2.pyc b/test/bytecode_3.7_run/01_assert2.pyc index 87af4c39..f911ce15 100644 Binary files a/test/bytecode_3.7_run/01_assert2.pyc and b/test/bytecode_3.7_run/01_assert2.pyc differ diff --git a/test/simple_source/bug37/01_assert2.py b/test/simple_source/bug37/01_assert2.py index 2973bb3f..cc26879c 100644 --- a/test/simple_source/bug37/01_assert2.py +++ b/test/simple_source/bug37/01_assert2.py @@ -6,3 +6,13 @@ def test_assert2(c): raise SyntaxError('Oops') test_assert2(5) + +# Bug is handling "assert" and confusing it with "or". +# It is important that the assert be at the end of the loop. +for x in (2, 4, 6): + assert x == x + +# Bug in 3.7 was not having a rule for 2-arg assert. +# 2-arg assert code doesn't match "if not ... raise " +for x in (1, 3, 5): + assert x == x, "foo" diff --git a/uncompyle6/parsers/parse37.py b/uncompyle6/parsers/parse37.py index e9355cfe..5184d7bf 100644 --- a/uncompyle6/parsers/parse37.py +++ b/uncompyle6/parsers/parse37.py @@ -760,6 +760,10 @@ class Python37Parser(Python37BaseParser): stmt ::= classdefdeco classdefdeco ::= classdefdeco1 store + # In 3.7 there are some LOAD_GLOBALs we don't convert to LOAD_ASSERT + stmt ::= assert2 + assert2 ::= expr jmp_true LOAD_GLOBAL expr CALL_FUNCTION_1 RAISE_VARARGS_1 + expr ::= LOAD_ASSERT ifstmt ::= testexpr _ifstmts_jump diff --git a/uncompyle6/parsers/reducecheck/or_check.py b/uncompyle6/parsers/reducecheck/or_check.py index 3fea3eb9..ed12a798 100644 --- a/uncompyle6/parsers/reducecheck/or_check.py +++ b/uncompyle6/parsers/reducecheck/or_check.py @@ -3,6 +3,23 @@ def or_check(self, lhs, n, rule, ast, tokens, first, last): if rule == ("or", ("expr", "jmp_true", "expr")): + if tokens[last] in ( + "LOAD_ASSERT", + "RAISE_VARARGS_1", + ): + return True + + # The following test is be the most accurate. It prevents "or" from being + # mistake for part of an "assert". + # There one might conceivably be "expr or AssertionError" code, but the + # likelihood of that is vanishingly small. + # The below then is useful until we get better control-flow analysis. + # Note it is too hard in the scanner right nowto turn the LOAD_GLOBAL into + # int LOAD_ASSERT, however in 3.9ish code generation does this by default. + load_global = tokens[last-1] + if load_global == "LOAD_GLOBAL" and load_global.attr == "AssertionError": + return True + jmp_true_target = ast[1][0].attr jmp_false = tokens[last] # If the jmp is backwards @@ -13,9 +30,5 @@ def or_check(self, lhs, n, rule, ast, tokens, first, last): jmp_false = tokens[last+1] return not (jmp_true_target == jmp_false.off2int() or jmp_true_target < tokens[first].off2int()) - return tokens[last] in ( - "LOAD_ASSERT", - "RAISE_VARARGS_1", - ) return False diff --git a/uncompyle6/semantics/customize.py b/uncompyle6/semantics/customize.py index f632fac8..e4aa2e56 100644 --- a/uncompyle6/semantics/customize.py +++ b/uncompyle6/semantics/customize.py @@ -48,8 +48,8 @@ def customize_for_version(self, is_pypy, version): ####################### TABLE_DIRECT.update({ # "assert" and "assert_expr" are added via transform rules. - "assert": ("%|assert %c\n", (0, "assert_expr")), - "assert2": ("%|assert %c, %c\n", (0, "assert_expr"), 3), + "assert": ("%|assert %c\n", 0), + "assert2": ("%|assert %c, %c\n", 0, 3), # Created only via transformation "assertnot": ("%|assert not %p\n", (0, PRECEDENCE['unary_not'])),