mikeckennedy · GitHub

Summary

switch.__exit__ decides whether an exception occurred inside the with block using a truthiness check rather than an identity check:

https://github.com/mikeckennedy/python-switch/blob/master/switchlang/__switchlang_impl.py#L138

def __exit__(self, exc_type, exc_val, exc_tb) -> None:
    if exc_val:          # <-- truthiness, not `is not None`
        raise exc_val
    ...
    for func in self._func_stack:
        self.__result = func()

If an exception is raised inside the block but its object is falsy (i.e. it defines __bool__/__len__ returning False/0), if exc_val: is False. __exit__ skips the early re-raise, falls through, and runs the matched case action before the exception ultimately propagates via the with-statement machinery (since __exit__ returns None).

The documented and intended contract is that an exception inside the block aborts the switch and no case actions run.

Reproduction

from switchlang import switch
class FalsyError(Exception):
    def __bool__(self):
        return False
visited = []
try:
    with switch(1) as s:
        s.case(1, lambda: visited.append("case ran!") or 1)
        raise FalsyError("boom")
except FalsyError:
    pass
print(visited)   # ['case ran!']  -> BUG: the case action ran

Control case — a normal (truthy) exception behaves correctly and the case action does not run:

visited2 = []
try:
    with switch(1) as s:
        s.case(1, lambda: visited2.append("case ran!") or 1)
        raise RuntimeError("boom")
except RuntimeError:
    pass
print(visited2)  # []  -> correct

Expected vs. actual

  • Expected: any exception raised in the block aborts the switch; no case actions run.
  • Actual: an exception whose object is falsy slips past the guard and the matched case action runs before the exception propagates.

Root cause

if exc_val: invokes the exception's __bool__/__len__. The guard should test for the presence of an exception, not its truthiness.

Proposed fix

if exc_val is not None:
    raise exc_val

This restores the absolute "no case actions run on exception" guarantee. A regression test pinning the falsy-exception case should accompany the change.

Impact

Low real-world likelihood (it requires an exception type with a falsy __bool__/__len__), but it's a correctness hole in a documented guarantee and a trivial, safe fix.

Read the original on github.com ↗