Fixed — Issue #3: if cond: return x was being folded into return cond and x
lib/handlers/misc_other.js 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 0collapses to
cond and 0which evaluates toconditself 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 stdlibbase64.encodewas rendered as
return leftover == 1 and ...; return leftover == 2 and ...; ...
on a single line. - Dead
returnartifacts. Code below the folded if was emitted as
unreachable but still rendered, producing output that no human would
write. - Cascade folds.
if a: if b: return xcould re-enter the fold and
produce nonsense likereturn a and b and None.
Visible examples from the snapshot suite that this release refreshes:
test/ok_lib2.6/.../base64.py— encode elif chaintest/ok_lib2.6/.../calendar.py—if isinstance(i, slice): return [...]test/ok_lib2.7/.../doctest.py—return self._exclude_empty and not docstring and Nonetest/ok_lib2.7/.../formatter.py— same pattern inwriter.send_paragraphtest/.../py310_union_types.py,10_if_else_ternary.py— toy cases
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 was never coming through this path
anyway; it round-trips through the JUMP_IF_FALSE_OR_POP handler in
control_flow_jumps.js and is unaffected.
Why it took two releases to catch
The snapshot test suite had blessed the buggy output — base64.encode,
doctest._DocTestFinder._find, calendar.WeekHeader.__getitem__,
py310_union_types.alias_* all had snapshot files showing the wrong fold,
silently passing CI. Issue #3
exposed it because a user actually read the output instead of trusting
green CI. This is the third regression in the v1.2.x line surfaced by
external eyes rather than the test harness; the snapshot framework cannot
distinguish "matches snapshot" from "correct".
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 variantstruthy_x— control case where the fold would have looked superficially finenegative_branch—if not condformreal_short_circuit— genuinereturn cond and x(must still fold via
theJUMP_IF_FALSE_OR_POPpath)
Compiled for 3.10, 3.11, 3.12, 3.13, 3.14. 3.10–3.12 now produce
correct output. 3.13/3.14 expose a separate pre-existing
if cond: pass / return x artifact unrelated to this fix — documented
in the snapshot, not in scope.
Compatibility
No API changes. Decompiled output for any code matching the buggy fold
pattern will change shape — refresh your local snapshots if you maintain
any.
Thanks to @CtrlCThenCtrlV for the bug report (filed against the
Python port repo, fix applied here in parallel).