From fc94c81173a50ad24476626d8aacf1148794894e Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 7 Oct 2026 15:48:27 +0100 Subject: [PATCH] [mypyc] Fix crash when exception variable is captured by nested function Fixes mypyc/mypyc#1184. This crashed with "unexpected type DeletedType": ```python def f() -> None: try: raise RuntimeError() except RuntimeError as exc: fn = lambda: print(exc) ``` Mypy gives the variable of an except clause the type `DeletedType` after the except block, and this type is used when adding a captured variable to the environment class of the function. Now we map `DeletedType` to `object`. --- mypyc/irbuild/mapper.py | 4 ++++ mypyc/test-data/run-exceptions.test | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/mypyc/irbuild/mapper.py b/mypyc/irbuild/mapper.py index 523c718c9b867..ac472117c08cb 100644 --- a/mypyc/irbuild/mapper.py +++ b/mypyc/irbuild/mapper.py @@ -6,6 +6,7 @@ from mypy.types import ( AnyType, CallableType, + DeletedType, Instance, LiteralType, NoneTyp, @@ -166,6 +167,9 @@ def type_to_rtype(self, typ: Type | None) -> RType: elif isinstance(typ, (UninhabitedType, UnboundType)): # Sure, whatever! return object_rprimitive + elif isinstance(typ, DeletedType): + # The type of an exception variable after the except block, for example + return object_rprimitive # I think we've covered everything that is supposed to # actually show up, so anything else is a bug somewhere. diff --git a/mypyc/test-data/run-exceptions.test b/mypyc/test-data/run-exceptions.test index 8257bfd9b8943..e6178876aae8b 100644 --- a/mypyc/test-data/run-exceptions.test +++ b/mypyc/test-data/run-exceptions.test @@ -665,3 +665,32 @@ from typing import Any class OtherException(Exception): def __new__(cls) -> Any: return ValueError("other") + +[case testExceptionVariableCapturedByNestedFunction] +from typing import Callable, Iterator + +def call(f: Callable[[], str]) -> str: + return f() + +def test_lambda() -> None: + try: + raise RuntimeError("x") + except RuntimeError as exc: + assert call(lambda: str(exc)) == "x" + +def test_nested_function() -> None: + try: + raise ValueError("y") + except Exception as exc: + def f() -> str: + return type(exc).__name__ + str(exc) + assert call(f) == "ValueErrory" + +def gen() -> Iterator[str]: + try: + raise KeyError("z") + except KeyError as exc: + yield call(lambda: repr(exc)) + +def test_generator() -> None: + assert list(gen()) == ["KeyError('z')"]