[mypyc] Fix exception swallowing in async try/finally blocks with await by Chainfire · Pull Request #19353 · python/mypy
Chainfire
changed the title
[mypyc] Test cases and compilation error for mypyc-try-finally-await
[mypyc] Fix exception swallowing in async try/finally blocks with await
Any await that causes a context switch inside a finally block swallows any exception (re-)raised in the preceding try or except blocks. As the exception 'never happened', this also changes control flow. This commit adds several tests (which fail) for this bug, and triggers a compiler error if this pattern is detected in the code. `# type: ignore[mypyc-try-finally-await, unused-ignore]` can be used on the try line to bypass the error. This also newly causes the testAsyncReturn test to fail, as it should. See mypyc/mypyc#1114
When a try/finally block in an async function contains an await statement in the finally block, exceptions raised in the try block are silently swallowed if a context switch occurs. This happens because mypyc stores exception information in registers that don't survive across await points. The Problem: - mypyc's transform_try_finally_stmt uses error_catch_op to save exceptions to a register, then reraise_exception_op to restore from that register - When await causes a context switch, register values are lost - The exception information is gone, causing silent exception swallowing The Solution: - Add new transform_try_finally_stmt_async for async-aware exception handling - Use sys.exc_info() to preserve exceptions across context switches instead of registers - Check error indicator first to handle new exceptions raised in finally - Route to async version when finally block contains await expressions Implementation Details: - transform_try_finally_stmt_async uses get_exc_info_op/restore_exc_info_op which work with sys.exc_info() that survives context switches - Proper exception priority: new exceptions in finally replace originals - Added has_await_in_block helper to detect await expressions Test Coverage: Added comprehensive async exception handling tests: - testAsyncTryExceptFinallyAwait: 8 test cases covering various scenarios - Simple try/finally with exception and await - Exception caught but not re-raised - Exception caught and re-raised - Different exception raised in except - Try/except inside finally block - Try/finally inside finally block - Control case without await - Normal flow without exceptions - testAsyncContextManagerExceptionHandling: Verifies async with still works - Basic exception propagation - Exception in __aexit__ replacing original See mypyc/mypyc#1114
esarp pushed a commit that referenced this pull request
Jul 10, 2025…it (#19353) When a try/finally block in an async function contains an await statement in the finally block, exceptions raised in the try block are silently swallowed if a context switch occurs. This happens because mypyc stores exception information in registers that don't survive across await points. The Problem: - mypyc's transform_try_finally_stmt uses error_catch_op to save exceptions - to a register, then reraise_exception_op to restore from that register - When await causes a context switch, register values are lost - The exception information is gone, causing silent exception swallowing The Solution: - Add new transform_try_finally_stmt_async for async-aware exception handling - Use sys.exc_info() to preserve exceptions across context switches instead - of registers - Check error indicator first to handle new exceptions raised in finally - Route to async version when finally block contains await expressions Implementation Details: - transform_try_finally_stmt_async uses get_exc_info_op/restore_exc_info_op - which work with sys.exc_info() that survives context switches - Proper exception priority: new exceptions in finally replace originals - Added has_await_in_block helper to detect await expressions Test Coverage: Added comprehensive async exception handling tests: - testAsyncTryExceptFinallyAwait: 8 test cases covering various scenarios - Simple try/finally with exception and await - Exception caught but not re-raised - Exception caught and re-raised - Different exception raised in except - Try/except inside finally block - Try/finally inside finally block - Control case without await - Normal flow without exceptions - testAsyncContextManagerExceptionHandling: Verifies async with still works - Basic exception propagation - Exception in **aexit** replacing original See mypyc/mypyc#1114.
Jdubz added a commit to Jdubz/blinky_time that referenced this pull request
Apr 2, 2026scoring.py type safety (review feedback): Added _DetectionDict and _MusicStateDict TypedDicts to replace dict[str, Any] intermediates. Removes all type: ignore comments from the scoring pipeline. Mypy now fully validates field access on detection and music state dicts. routes_devices.py inline import (review feedback): Moved `from ..firmware import upload_firmware` to top-level import. No circular dependency exists. MCP disconnect description (review feedback): Updated tool description to clarify it stops streaming only, does not release the transport. mypy pin (review feedback): Added upstream issue URL to the <1.20 pin comment (python/mypy#19353). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters