Skip to content

[Python] Fix nonlocal declarations inside match/case blocks (SyntaxError regression)#4379

Merged
dbrattli merged 2 commits intomainfrom
dbrattli/python-case-nonlocal
Mar 3, 2026
Merged

[Python] Fix nonlocal declarations inside match/case blocks (SyntaxError regression)#4379
dbrattli merged 2 commits intomainfrom
dbrattli/python-case-nonlocal

Conversation

@dbrattli
Copy link
Collaborator

@dbrattli dbrattli commented Mar 3, 2026

Summary

  • Python's nonlocal/global declarations were being generated inside case bodies in match/case statements, causing SyntaxError: name 'x' is used prior to nonlocal declaration at runtime (regression reported in fsharp_control_async_rx/filter.py)
  • Lift NonLocal/Global statements out of all match case bodies before building the Statement.match' node; prepend them before the match so transformBody can hoist them to the top of the enclosing function
  • Add getNonLocals and getNonLocalsFromMatchCases helpers to Fable2Python.Util (single source of truth) and apply them at all 6 match statement creation sites in Fable2Python.Transforms
  • Add regression tests: takeFn (take operator with mutable closure) and nestedUnionMatch (nested union match with mutable capture)

Test plan

  • ./build.sh test python --skip-fable-library passes all tests including the two new regression tests
  • fsharp_control_async_rx/filter.py (timeflies example) no longer throws SyntaxError
  • Verify nonlocal/global declarations appear before any use of the variable in generated Python output

🤖 Generated with Claude Code

dbrattli and others added 2 commits March 3, 2026 22:50
…ror regression)

Python requires `nonlocal`/`global` declarations to appear before any use of the
variable in the enclosing function. Because `match/case` does not create a new scope,
nonlocal statements generated inside case bodies caused `SyntaxError: name 'x' is used
prior to nonlocal declaration` at runtime.

Fix: lift NonLocal/Global statements out of all match case bodies before building the
`Statement.match'` node. The lifted declarations are prepended to the result list, where
`transformBody`'s existing `getNonLocals` pass then hoists them to the top of the
enclosing function.

Add `getNonLocals` and `getNonLocalsFromMatchCases` helpers to `Fable2Python.Util` and
apply them at all 6 match statement creation sites in `Fable2Python.Transforms`. Also
add regression tests covering the `take` operator pattern and nested union match with
mutable closure captures.

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 dbrattli merged commit 6be17f5 into main Mar 3, 2026
24 checks passed
@dbrattli dbrattli deleted the dbrattli/python-case-nonlocal branch March 3, 2026 23:06
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