Skip to content

Commit cb1ea49

Browse files
pytype authorscopybara-github
authored andcommitted
Add a pytype error unused-coroutine.
PiperOrigin-RevId: 765456902
1 parent 48d1294 commit cb1ea49

File tree

4 files changed

+85
-5
lines changed

4 files changed

+85
-5
lines changed

pytype/errors/errors.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,14 @@ def typed_dict_error(self, stack, obj, name):
13651365
)
13661366
self.error(stack, err_msg)
13671367

1368+
@_error_name("unused-coroutine")
1369+
def unused_coroutine(self, stack):
1370+
self.error(
1371+
stack,
1372+
"Coroutine result was not used. Did you forget to await it or assign it"
1373+
" to a variable?",
1374+
)
1375+
13681376
@_error_name("final-error")
13691377
def _overriding_final(self, stack, cls, base, name, *, is_method, details):
13701378
desc = "method" if is_method else "class attribute"

pytype/tests/test_coroutine.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -500,9 +500,9 @@ async def func2(x: Coroutine[Any, Any, str]):
500500
var.func()
501501
return res
502502
503-
func1(foo.f1())
504-
func1(foo.f2())
505-
func2(foo.f1())
503+
coro1 = func1(foo.f1())
504+
coro2 = func1(foo.f2())
505+
coro3 = func2(foo.f1())
506506
""",
507507
pythonpath=[d.path],
508508
)
@@ -512,6 +512,10 @@ async def func2(x: Coroutine[Any, Any, str]):
512512
import foo
513513
from typing import Any, Awaitable, Coroutine, List
514514
515+
coro1: Coroutine[Any, Any, list[str]]
516+
coro2: Coroutine[Any, Any, list[str]]
517+
coro3: Coroutine[Any, Any, list[str]]
518+
515519
def func1(x: Awaitable[str]) -> Coroutine[Any, Any, List[str]]: ...
516520
def func2(x: Coroutine[Any, Any, str]) -> Coroutine[Any, Any, List[str]]: ...
517521
""",
@@ -570,7 +574,7 @@ async def worker(queue):
570574
571575
async def main():
572576
queue = asyncio.Queue()
573-
worker(queue)
577+
await worker(queue)
574578
""")
575579
self.assertTypesMatchPytd(
576580
ty,
@@ -646,5 +650,58 @@ async def call_with_retry(
646650
""")
647651

648652

653+
class UnusedCoroutineTest(test_base.BaseTest):
654+
"""Tests for the unused-coroutine error."""
655+
656+
def test_trigger_unused_coroutine(self):
657+
self.CheckWithErrors("""
658+
async def sample_coro():
659+
return 1
660+
sample_coro() # unused-coroutine
661+
""")
662+
663+
def test_correct_coroutine_usage(self):
664+
self.Check("""
665+
import asyncio
666+
667+
async def sample_coro():
668+
return 1
669+
670+
async def main():
671+
await sample_coro()
672+
x = sample_coro()
673+
asyncio.create_task(sample_coro())
674+
my_list = [sample_coro()]
675+
my_dict = {"a": sample_coro()}
676+
return x, my_list, my_dict
677+
678+
def regular_func(): return 1
679+
regular_func()
680+
""")
681+
682+
def test_wrapper_returns_unused_coroutine(self):
683+
self.CheckWithErrors("""
684+
async def sample_coro():
685+
return 1
686+
def wrapper():
687+
return sample_coro()
688+
wrapper() # unused-coroutine
689+
""")
690+
691+
def test_assign_to_underscore(self):
692+
self.Check("""
693+
async def sample_coro():
694+
return 1
695+
_ = sample_coro()
696+
""")
697+
698+
def test_coroutine_in_used_expression(self):
699+
self.CheckWithErrors("""
700+
async def sample_coro():
701+
return 1
702+
y = 1 + sample_coro() # unsupported-operands
703+
""")
704+
705+
649706
if __name__ == "__main__":
650707
test_base.main()

pytype/tests/test_stdlib2.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,14 +453,17 @@ async def iterate(x):
453453
pass
454454
else:
455455
pass
456-
iterate(AsyncIterable())
456+
iterate_coro = iterate(AsyncIterable())
457457
""")
458458
self.assertTypesMatchPytd(
459459
ty,
460460
"""
461461
import asyncio
462462
from typing import Any, Coroutine, TypeVar
463463
_TAsyncIterable = TypeVar('_TAsyncIterable', bound=AsyncIterable)
464+
465+
iterate_coro: Coroutine[Any, Any, None]
466+
464467
class AsyncIterable:
465468
def __aiter__(self: _TAsyncIterable) -> _TAsyncIterable: ...
466469
def __anext__(self) -> Coroutine[Any, Any, int]: ...

pytype/vm.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,6 +1638,18 @@ def byte_SETUP_EXCEPT_311(self, state, op):
16381638
return self._setup_except(state, op)
16391639

16401640
def byte_POP_TOP(self, state, op):
1641+
"""Pops and discards, checking for an unused coroutine."""
1642+
value_on_stack = None
1643+
try:
1644+
value_on_stack = state.top()
1645+
except IndexError:
1646+
pass
1647+
if value_on_stack and state.node:
1648+
if any(
1649+
isinstance(b.data, abstract.Coroutine)
1650+
for b in value_on_stack.Bindings(state.node)
1651+
):
1652+
self.ctx.errorlog.unused_coroutine(self.frames)
16411653
return state.pop_and_discard()
16421654

16431655
def byte_DUP_TOP(self, state, op):

0 commit comments

Comments
 (0)