#4486·ty

`AbstractContextManager[..., bool | None]` is incorrectly treated as if it can never suppress an exception

Author: DetachHeadCreated Sep 8, 2026Updated Sep 15, 2026
Labelsneeds-decisioncontrol flowconformance-blocked

Summary

python
import contextlib
from typing import Generator

@contextlib.contextmanager
def context_func() -> Generator[None]:
    try:
        yield
    except Exception:
        pass

with context_func():
    raise Exception

print("unreachable?")  # actually reached at runtime

this is because contextmanager changes the function's return type to _GeneratorContextManager which looks like this:

python
class _GeneratorContextManager(
    _GeneratorContextManagerBase[Generator[_T_co, _SendT_contra, _ReturnT_co]],
    AbstractContextManager[_T_co, bool | None],
    ContextDecorator,
): ...

specifically the bool | None generic in AbstractContextManager[_T_co, bool | None] means the context manager could suppress the exception. ty incorrectly treats bool | None as Literal[False] in this case. this is the same behavior as pyright, which was discussed in https://github.com/microsoft/pyright/issues/6034

the logic should be:

  • bool | None, bool - may or may not suppress the exception
  • Literal[True] - always suppresses the exception
  • Literal[False] - never suppresses the exception

at first glance this looks like it should be a typeshed issue easily fixable by adding another generic to _GeneratorContextManager which gets passed to its AbstractContextManagerBase, however i think the type checker still needs special casing on the contextmanager decorator to be able to infer whether the exception can be suppressed by looking at the context manager's implementation.

Version

No response