From d8ac334af48075f5d118220051d012f0cf90edd0 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 May 2026 15:05:26 +0100 Subject: [PATCH 1/2] Fix broken refactor of awaitable generator patching --- mypy/checker.py | 10 ++++----- test-data/unit/check-async-await.test | 30 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 7c787d9a42c91..2d23f9c133164 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5721,11 +5721,12 @@ def visit_decorator_inner( # Fix the type if decorated with `@types.coroutine` or `@asyncio.coroutine`. defn = e.func if defn.is_awaitable_coroutine: - assert isinstance(defn.type, CallableType) + typ = self.function_type(defn) + assert isinstance(typ, CallableType) # Update the return type to AwaitableGenerator (unless we already did). # Note, this doesn't exist in typing.py, only in typing.pyi. - if not is_named_instance(defn.type.ret_type, "typing.AwaitableGenerator"): - t = defn.type.ret_type + if not is_named_instance(typ.ret_type, "typing.AwaitableGenerator"): + t = typ.ret_type c = defn.is_coroutine ty = self.get_generator_yield_type(t, c) tc = self.get_generator_receive_type(t, c) @@ -5734,8 +5735,7 @@ def visit_decorator_inner( else: tr = self.get_generator_return_type(t, c) ret_type = self.named_generic_type("typing.AwaitableGenerator", [ty, tc, tr, t]) - typ = defn.type.copy_modified(ret_type=ret_type) - defn.type = typ + defn.type = typ.copy_modified(ret_type=ret_type) # Type check initialization expressions as part of top-level. if not self.can_skip_diagnostics: diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index 907bf6027f657..2e439b9eaeb27 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -1091,3 +1091,33 @@ class Launcher(P): # E: "list[int]" has no attribute "__aiter__" (not async iterable) [builtins fixtures/async_await.pyi] [typing fixtures/typing-async.pyi] + +[case testTypesCoroutineDecoratorUntyped] +import types + +@types.coroutine +def f(x): + yield + return 1 + +async def test() -> None: + reveal_type(f) # N: Revealed type is "def (x: Any) -> typing.AwaitableGenerator[Any, Any, Any, Any]" + reveal_type(await f(1)) # N: Revealed type is "Any" + +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-async.pyi] + +[case testTypesCoroutineDecoratorPartiallyTyped] +import types + +@types.coroutine +def f(x: int): + yield + return 1 + +async def test() -> None: + reveal_type(f) # N: Revealed type is "def (x: builtins.int) -> typing.AwaitableGenerator[Any, Any, Any, Any]" + reveal_type(await f(1)) # N: Revealed type is "Any" + +[builtins fixtures/async_await.pyi] +[typing fixtures/typing-async.pyi] From 6a6aa4963884a9f34cddd2c946ad12079ea164ea Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 May 2026 22:44:32 +0100 Subject: [PATCH 2/2] Make is_dynamic() more correct --- mypy/nodes.py | 6 +++++- test-data/unit/check-async-await.test | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 3dafffa5570dd..32a694560b24b 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1068,7 +1068,11 @@ def max_fixed_argc(self) -> int: return self.max_pos def is_dynamic(self) -> bool: - return self.type is None + return ( + self.type is None + or isinstance(self.type, mypy.types.CallableType) + and self.type.implicit + ) FUNCDEF_FLAGS: Final = FUNCITEM_FLAGS + [ diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index 2e439b9eaeb27..e887b4c575528 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -1098,6 +1098,7 @@ import types @types.coroutine def f(x): yield + 1 + "" # OK, in untyped function return 1 async def test() -> None: @@ -1113,6 +1114,7 @@ import types @types.coroutine def f(x: int): yield + 1 + "" # E: Unsupported left operand type for + ("int") return 1 async def test() -> None: