Skip to content

[Python] Fix exception variable captured in deferred closures (NameError)#4382

Merged
dbrattli merged 4 commits intomainfrom
dbrattli/python-ex-not-defined
Mar 3, 2026
Merged

[Python] Fix exception variable captured in deferred closures (NameError)#4382
dbrattli merged 4 commits intomainfrom
dbrattli/python-ex-not-defined

Conversation

@dbrattli
Copy link
Collaborator

@dbrattli dbrattli commented Mar 3, 2026

Summary

  • Python's except E as name: implicitly deletes name at end of the except block (PEP 3110). Closures that capture the variable and are called after the block exit get a NameError: free variable 'ex' referenced before assignment in enclosing scope.
  • Fix in makeHandler (transformTryCatch): copy exex_ immediately inside the block via an annotated assignment, then rename all body references using FableTransforms.replaceValues. The original name stays in except ... as ex: for readability.
  • Handles both zero-arg closures (fun () -> ex.Message) and lambda-with-argument patterns (fun obv _ -> obv.OnErrorAsync ex) — the latter mirrors the AsyncRx defer function.

Before:

except Exception as ex:
    def _arrow107(obv, _arg):
        return obv.OnErrorAsync(ex)  # NameError: ex deleted by Python
    result = of_async_worker(_arrow107)

After:

except Exception as ex:
    ex_: Exception = ex             # safe copy, Python won't delete this
    def _arrow107(obv, _arg):
        return obv.OnErrorAsync(ex_)  # uses safe copy
    result = of_async_worker(_arrow107)

Test plan

  • Two new regression tests in TestMisc.fs covering both closure patterns
  • All 2080 Python tests pass (./build.sh test python --skip-fable-library)
  • Verified fix in AsyncRx defer function output

🤖 Generated with Claude Code

dbrattli and others added 2 commits March 4, 2026 00:09
…ror)

Python's `except E as name:` implicitly deletes `name` at the end of
the except block (per PEP 3110). This caused a NameError at runtime
when a closure capturing the exception variable was stored and called
after the except block exited.

Fix: in `makeHandler`, copy the exception variable to `name_` (a regular
local that Python won't delete) immediately inside the except block, then
rename all body references from `name` to `name_` via
`FableTransforms.replaceValues`. The original name is kept in the
`except ... as name:` clause for readability.

Affects both direct deferred closures (`fun () -> ex.Message`) and
lambda-with-argument patterns (`fun obv _ -> obv.OnErrorAsync ex`)
as seen in AsyncRx's `defer` function.

Adds two regression tests to TestMisc.fs covering both patterns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link

github-actions bot commented Mar 3, 2026

Python Type Checking Results (Pyright)

Metric Value
Total errors 18
Files with errors 4
Excluded files 4
New errors ✅ No
Excluded files with errors (4 files)

These files have known type errors and are excluded from CI. Remove from pyrightconfig.ci.json as errors are fixed.

File Errors Status
temp/tests/Python/test_applicative.py 12 Excluded
temp/tests/Python/test_hash_set.py 3 Excluded
temp/tests/Python/test_nested_and_recursive_pattern.py 2 Excluded
temp/tests/Python/fable_modules/thoth_json_python/encode.py 1 Excluded

dbrattli and others added 2 commits March 4, 2026 00:29
…dler body

Use `isIdentUsed` to skip the save-to-safe-copy step when the exception
variable is not referenced in the handler body. When it is used, use
`getUniqueNameInDeclarationScope` to give each handler its own unique
safe-copy name, preventing Pylance `reportRedeclaration` warnings when
multiple typed `except` handlers exist in the same function.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dbrattli dbrattli merged commit 2033315 into main Mar 3, 2026
24 checks passed
@dbrattli dbrattli deleted the dbrattli/python-ex-not-defined branch March 3, 2026 23:51
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