-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wrong return type from ExitStack.enter_context when given a child of ExitStack #7961
Comments
The typeshed stubs look correct here to me — @JelleZijlstra, are you sure this is a typeshed bug and not a mypy bug? |
This is because of this line: typeshed/stdlib/contextlib.pyi Line 166 in 62a8a69
AbstractContextManager[ExitStack] . Logically this should be AbstractContextManager[Self] , but that's not allowed. As a result, even though __enter__ is annotated as returning Self, when we match a subclass of ExitStack against AbstractContextManager[T] , T gets solved to ExitStack instead of the subclass.
Here is a simple repro with mypy: from typing import TypeVar
from contextlib import AbstractContextManager
T = TypeVar("T")
Self = TypeVar("Self")
class ExitStack(AbstractContextManager[ExitStack]):
def __enter__(self: Self) -> Self: ...
def __exit__(self, *args: object) -> bool: ...
class A(ExitStack):
pass
def f(x: AbstractContextManager[T]) -> T:
raise
reveal_type(f(ExitStack())) # Revealed type is "__main__.ExitStack"
reveal_type(f(A())) # Revealed type is "__main__.ExitStack" One solution is to define our own protocol that ExitStack doesn't inherit from explicitly: from typing import TypeVar, Protocol
from contextlib import AbstractContextManager
T = TypeVar("T")
T_co = TypeVar("T_co", covariant=True)
Self = TypeVar("Self")
class _CMProto(Protocol[T_co]):
def __enter__(self) -> T_co: ...
def __exit__(self, *args: object) -> bool | None: ...
class ExitStack(AbstractContextManager[ExitStack]):
def __enter__(self: Self) -> Self: ...
def __exit__(self, *args: object) -> bool: ...
class A(ExitStack):
pass
def f(x: _CMProto[T]) -> T:
raise
reveal_type(f(ExitStack())) # Revealed type is "__main__.ExitStack"
reveal_type(f(A())) # Revealed type is "__main__.A" |
Not having from typing import TypeVar
from contextlib import AbstractContextManager
T = TypeVar("T")
Self = TypeVar("Self")
class ExitStack:
def __enter__(self: Self) -> Self: ...
def __exit__(self, *args: object) -> bool: ...
class A(ExitStack):
pass
def f(x: AbstractContextManager[T]) -> T:
raise
reveal_type(f(ExitStack())) # Revealed type is "__main__.ExitStack"
reveal_type(f(A())) # Revealed type is "__main__.A" Mypy still understands that https://mypy-play.net/?mypy=latest&python=3.10&gist=615a2b644f87ffc9f42397f83853ed17 |
But ExitStack inherits from AbstractContextManager at runtime, so ideally the stubs should reflect that. |
I guess. The solution of creating a new protocol for annotating I really wish we were allowed to do |
Given that AbstractContextManager is a Protocol, it's probably fine to remove the explicit base class from the stub.
PEP 673 explicitly disallows this (the example is |
I found this issue while working on python/mypy#13327 With my patch
This happens because |
How hard would it be to allow |
Bug Report
The return type from
enter_context()
is incorrect when the argument is a child ofExitStack
.To Reproduce
Expected Behavior
The type of
thing
should beThing
, notExitStack
.Actual Behavior
Your Environment
The text was updated successfully, but these errors were encountered: