Skip to content

Commit 1bf701f

Browse files
Extend contextmanager to asynccontextmanager (#11352)
Closes #9922 ### Description Extending the existing special case for `contextlib.contextmanager` to `contextlib.asynccontextmanager`. When used with `TypeVar` gives much better type. ## Test Plan I hope I have included the unit test you need. My minimal reproduction is: ``` from contextlib import asynccontextmanager from typing import AsyncGenerator, Type, TypeVar R = TypeVar("R") @asynccontextmanager async def foo( cls: Type[R], ) -> AsyncGenerator[R, None]: yield cls() async def bar() -> int: async with foo(int) as foo_int: return ( foo_int # error: Incompatible return value type (got "R", expected "int") ) ```
1 parent 76a7542 commit 1bf701f

File tree

2 files changed

+63
-3
lines changed

2 files changed

+63
-3
lines changed

mypy/plugins/default.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from mypy.subtypes import is_subtype
1616
from mypy.typeops import make_simplified_union
1717
from mypy.checkexpr import is_literal_type_like
18+
from mypy.checker import detach_callable
1819

1920

2021
class DefaultPlugin(Plugin):
@@ -24,7 +25,7 @@ def get_function_hook(self, fullname: str
2425
) -> Optional[Callable[[FunctionContext], Type]]:
2526
from mypy.plugins import ctypes, singledispatch
2627

27-
if fullname == 'contextlib.contextmanager':
28+
if fullname in ('contextlib.contextmanager', 'contextlib.asynccontextmanager'):
2829
return contextmanager_callback
2930
elif fullname == 'builtins.open' and self.python_version[0] == 3:
3031
return open_callback
@@ -193,12 +194,12 @@ def contextmanager_callback(ctx: FunctionContext) -> Type:
193194
and isinstance(default_return, CallableType)):
194195
# The stub signature doesn't preserve information about arguments so
195196
# add them back here.
196-
return default_return.copy_modified(
197+
return detach_callable(default_return.copy_modified(
197198
arg_types=arg_type.arg_types,
198199
arg_kinds=arg_type.arg_kinds,
199200
arg_names=arg_type.arg_names,
200201
variables=arg_type.variables,
201-
is_ellipsis_args=arg_type.is_ellipsis_args)
202+
is_ellipsis_args=arg_type.is_ellipsis_args))
202203
return ctx.default_return_type
203204

204205

test-data/unit/check-default-plugin.test

+59
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,65 @@ f = g # E: Incompatible types in assignment (expression has type "Callable[[Any,
2424
[typing fixtures/typing-medium.pyi]
2525
[builtins fixtures/tuple.pyi]
2626

27+
[case testContextManagerWithGenericFunctionAndSendType]
28+
from contextlib import contextmanager
29+
from typing import TypeVar, Generator
30+
31+
T = TypeVar('T')
32+
S = TypeVar('S')
33+
34+
@contextmanager
35+
def yield_id(item: T) -> Generator[T, S, None]:
36+
yield item
37+
38+
reveal_type(yield_id) # N: Revealed type is "def [T] (item: T`-1) -> contextlib.GeneratorContextManager[T`-1]"
39+
40+
with yield_id(1) as x:
41+
reveal_type(x) # N: Revealed type is "builtins.int*"
42+
43+
f = yield_id
44+
def g(x, y): pass
45+
f = g # E: Incompatible types in assignment (expression has type "Callable[[Any, Any], Any]", variable has type "Callable[[T], GeneratorContextManager[T]]")
46+
[typing fixtures/typing-medium.pyi]
47+
[builtins fixtures/tuple.pyi]
48+
49+
[case testAsyncContextManagerWithGenericFunction]
50+
# flags: --python-version 3.7
51+
from contextlib import asynccontextmanager
52+
from typing import TypeVar, AsyncIterator
53+
54+
T = TypeVar('T')
55+
56+
@asynccontextmanager
57+
async def yield_id(item: T) -> AsyncIterator[T]:
58+
yield item
59+
60+
reveal_type(yield_id) # N: Revealed type is "def [T] (item: T`-1) -> typing.AsyncContextManager[T`-1]"
61+
62+
async with yield_id(1) as x:
63+
reveal_type(x) # N: Revealed type is "builtins.int*"
64+
[typing fixtures/typing-async.pyi]
65+
[builtins fixtures/tuple.pyi]
66+
67+
[case testAsyncContextManagerWithGenericFunctionAndSendType]
68+
# flags: --python-version 3.7
69+
from contextlib import asynccontextmanager
70+
from typing import TypeVar, AsyncGenerator
71+
72+
T = TypeVar('T')
73+
S = TypeVar('S')
74+
75+
@asynccontextmanager
76+
async def yield_id(item: T) -> AsyncGenerator[T, S]:
77+
yield item
78+
79+
reveal_type(yield_id) # N: Revealed type is "def [T] (item: T`-1) -> typing.AsyncContextManager[T`-1]"
80+
81+
async with yield_id(1) as x:
82+
reveal_type(x) # N: Revealed type is "builtins.int*"
83+
[typing fixtures/typing-async.pyi]
84+
[builtins fixtures/tuple.pyi]
85+
2786
[case testContextManagerWithUnspecifiedArguments]
2887
from contextlib import contextmanager
2988
from typing import Callable, Iterator

0 commit comments

Comments
 (0)