`AbstractContextManager[..., bool | None]` is incorrectly treated as if it can never suppress an exception
Summary
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 runtimethis is because contextmanager changes the function's return type to _GeneratorContextManager which looks like this:
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 exceptionLiteral[True]- always suppresses the exceptionLiteral[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
Source: astral-sh/ty