Skip to content

v1.2.4

Choose a tag to compare

@skuznetsov skuznetsov released this 22 May 23:50
· 15 commits to main since this release

Fixed — Issue #3: if cond: return x was being folded into return cond and x

src/depyo/handlers/misc_other.py was unconditionally rewriting one-statement
if cond: return x blocks into a short-circuit return cond and x. The
rewrite is only sound when the compiler actually emitted a sticky-truth
jump (JUMP_IF_FALSE_OR_POP, real short-circuit). For a normal
if-statement the compiler emits POP_JUMP_IF_FALSE, and the fold then
quietly loses information.

What was broken

  • Falsy returns silently disappeared. if cond: return 0 collapses to
    cond and 0 which evaluates to cond itself when cond is truthy — the
    decompiled source no longer matches the original semantics. Same for
    return None, return "", return [], etc.
  • elif chains imploded. A 4-way if leftover == 1: ... elif leftover == 2: ...
    chain in stdlib base64.encode was rendered as
    return leftover == 1 and ...; return leftover == 2 and ...; ...
    on a single line.
  • Dead return artifacts. Code below the folded if was emitted as
    unreachable but still rendered.
  • Cascade folds. if a: if b: return x could re-enter the fold and
    produce nonsense like return a and b and None.

Fix

Removed the fold entirely. The handler still pops the if-block, appends it
to the parent block, and consumes the trailing JUMP_ABSOLUTE/JUMP_FORWARD
— but no longer rewrites the AST. Genuine cond and x short-circuit
round-trips through the JUMP_IF_FALSE_OR_POP handler in
control_flow_jumps.py and is unaffected.

Fixed — Issue #2: Garbled non-ASCII output on Windows

depyo opens output files (.py, .dump, .pyasm text variant) without
explicitly setting an encoding. Python defaults open(path, "w") to the
locale codepage on Windows — CP936 for Simplified Chinese, CP1251 for
Russian, CP932 for Japanese — so any non-ASCII identifier or string
literal in the decompiled source gets either mojibake'd or raises
UnicodeEncodeError. On macOS/Linux you don't notice because the locale
default is already UTF-8.

Fix

open(...) calls in src/depyo/__main__.py for the three text-mode
outputs now explicitly pass encoding="utf-8". Bytes-mode outputs
(.pyc, binary .dump/.pyasm) are unchanged.

Regression coverage

New fixture test/modern_features/issue12_if_return_falsy.py covers:

  • falsy_x — the exact issue #3 case (x = 1; if cond: return x; return 0)
  • none_x, empty_string — falsy return value variants
  • truthy_x — control case where the fold would have looked superficially fine
  • negative_branch — if not cond form
  • real_short_circuit — genuine return cond and x (must still fold via
    the JUMP_IF_FALSE_OR_POP path)

Compiled for 3.10–3.14 via the shared test root used by both the JS and
Python ports.

The UTF-8 encoding fix doesn't have a dedicated fixture (CI runs on
Linux/macOS where the default is already UTF-8), but the symptom only
appears when the OS locale is non-UTF-8, which the explicit encoding
fully removes.

Compatibility

No API changes. Output paths and CLI flags are unchanged. Decompiled
output for any code matching the buggy fold pattern (#3) will change
shape — refresh your local snapshots if you maintain any.

Thanks to @CtrlCThenCtrlV for both reports.