Skip to content

fix: preserve RuntimeError propagation and support plain function bodies - #272

Merged
dbrattli merged 2 commits into
mainfrom
fix/builder-body-handling
Aug 18, 2026
Merged

fix: preserve RuntimeError propagation and support plain function bodies#272
dbrattli merged 2 commits into
mainfrom
fix/builder-body-handling

Conversation

@dbrattli

Copy link
Copy Markdown
Owner

Two P0 fixes to the computational-expression builder, aligning it with F# computation-expression semantics.

1. RuntimeError raised in an effect body is no longer silently swallowed

The sync Builder._send had an except RuntimeError branch that converted the error into zero() — a raise inside an effect body produced a silent empty computation (e.g. Nothing) with no trace. Errors now propagate to the caller, matching the async builder.

@effect.option[int]()
def fn():
    raise RuntimeError("boom")
    yield 42

fn()  # before: Nothing (silent); now: RuntimeError("boom")

2. Decorated functions without a yield statement are now supported

Plain function bodies (e.g. def fn(): return 42) previously crashed with AttributeError. The return value is now treated like an F# return statement: a value is lifted with return_, and None maps to zero().

@effect.option[int]()
def fn() -> int:
    return 42

fn()  # Some(42) — before: AttributeError

@effect.option[int]()
def fn() -> None:
    return None

fn()  # Nothing

Design notes

  • A body's return value is treated like F# return (lifted with return_) rather than return_from, because return_ is total and type-safe — a raw non-monad value never leaks into the monad.
  • Signatures and all five effect overrides (option, result, seq, async_option, async_result) were widened for LSP compatibility under pyright strict.

Verification

  • 13 new regression tests across the option/result/seq/async-option/async-result builder suites (plain body, plain body None, RuntimeError propagation)
  • uv run pytest — 491 passed
  • uv run ruff check expression/ && uv run ruff format --check expression/ — clean
  • pyright: no new errors in expression/ relative to the main baseline

dbrattli and others added 2 commits August 18, 2026 10:17
- Stop silently converting RuntimeError raised in effect bodies to zero(): the error now propagates to the caller, matching the async builder.

- Support decorated functions without a yield statement: the body's return value is treated like a return statement (value -> return_(value), None -> zero()), instead of raising AttributeError.

Adds regression tests for both behaviors to the option, result, seq, async-option, and async-result builder suites.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dbrattli
dbrattli merged commit 2b7b0fa into main Aug 18, 2026
6 checks passed
@dbrattli
dbrattli deleted the fix/builder-body-handling branch August 18, 2026 16:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant