Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -726,22 +726,44 @@ def verify_funcitem(
stub_sig = Signature.from_funcitem(stub)
runtime_sig = Signature.from_inspect_signature(signature)
runtime_sig_desc = f'{"async " if runtime_is_coroutine else ""}def {signature}'
stub_desc = f'def {stub_sig!r}'
stub_desc = str(stub_sig)
else:
runtime_sig_desc, stub_desc = None, None

# Don't raise an error if the stub is a coroutine, but the runtime isn't.
# That results in false positives.
# See https://github.com/python/typeshed/issues/7344
if runtime_is_coroutine and not stub.is_coroutine:
yield Error(
object_path,
'is an "async def" function at runtime, but not in the stub',
stub,
runtime,
stub_desc=stub_desc,
runtime_desc=runtime_sig_desc
)
if (
getattr(runtime, "__isabstractmethod__", False)
or (isinstance(stub, nodes.FuncDef) and stub.is_abstract)
):
error_msg = (
"is an \"async def\" function at runtime, "
"but doesn't return an awaitable in the stub"
)
# Be more permissive if the method is an abstractmethod:
# Only error if the return type of the stub isn't awaitable
if isinstance(stub.type, mypy.types.CallableType):
ret_type = mypy.types.get_proper_type(stub.type.ret_type)
should_error = (
isinstance(ret_type, mypy.types.Instance)
and not any("__await__" in clsdef.names for clsdef in ret_type.type.mro)
)
else:
should_error = False
else:
error_msg = 'is an "async def" function at runtime, but not in the stub'
should_error = True
if should_error:
yield Error(
object_path,
error_msg,
stub,
runtime,
stub_desc=stub_desc,
runtime_desc=runtime_sig_desc
)

if not signature:
return
Expand Down
60 changes: 59 additions & 1 deletion mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,11 @@ def __init__(self, name, covariant: bool = ..., contravariant: bool = ...) -> No
_S = TypeVar("_S", contravariant=True)
_R = TypeVar("_R", covariant=True)

class Coroutine(Generic[_T_co, _S, _R]): ...
class Iterable(Generic[_T_co]): ...
class Generator(Iterable[_T_co], Generic[_T_co, _S, _R]): ...
class Awaitable(Generic[_T_co]):
def __await__(self) -> Generator[Any, None, _T_co]: ...
class Coroutine(Awaitable[_T_co], Generic[_T_co, _S, _R]): ...
class Mapping(Generic[_K, _V]): ...
class Sequence(Iterable[_T_co]): ...
class Tuple(Sequence[_T_co]): ...
Expand Down Expand Up @@ -229,6 +232,61 @@ def test_coroutines(self) -> Iterator[Case]:
runtime="async def bingo(): return 5",
error=None,
)
# Be more permissive if it's an abstractmethod in the stub...
yield Case(
stub="""
from abc import abstractmethod
from typing import Awaitable
class Bar:
@abstractmethod
def abstract(self) -> Awaitable[int]: ...
""",
runtime="""
class Bar:
async def abstract(self): return 5
""",
error=None,
)
# ...and/or an abstractmethod at runtime...
yield Case(
stub="""
from typing import Generator, Any, Coroutine, Awaitable
class _CustomAwaitable:
def __await__(self) -> Generator[Any, Any, Any]: ...
class _CustomAwaitable2(Awaitable[Any]): ...
class Foo:
def abstract(self) -> _CustomAwaitable: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want to allow this? Doesn't seem particularly useful.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't remember, and I'm not sure I care anymore. I guess I'll just close this PR :)

def abstract2(self) -> _CustomAwaitable2: ...
def abstract3(self) -> Coroutine[Any, Any, Any]: ...
""",
runtime="""
from abc import abstractmethod
class Foo:
@abstractmethod
async def abstract(self): return 5
@abstractmethod
async def abstract2(self): return 4
@abstractmethod
async def abstract3(self): return 5
""",
error=None,
)
# ...but still error if the stub's return type isn't awaitable
yield Case(
stub="""
from abc import abstractmethod
class Baz:
@abstractmethod
def abstract(self) -> int: ...
""",
runtime="""
from abc import abstractmethod
class Baz:
@abstractmethod
async def abstract(self): return 5
""",
error="Baz.abstract",
)

@collect_cases
def test_arg_name(self) -> Iterator[Case]:
Expand Down