Skip to content

Commit

Permalink
Flag unused generators (#800)
Browse files Browse the repository at this point in the history
  • Loading branch information
JelleZijlstra authored Jul 22, 2024
1 parent 1961de2 commit 58124fe
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

- Flag use of generators that are immediately discarded (#800)
- Fix crash on some occurrences of `ParamSpec` in stub files (#797)
- Fix crash when Pydantic 1 is installed (#793)
- Fix error on use of TypeVar defaults in stubs (PEP 696). The
Expand Down
1 change: 1 addition & 0 deletions pyanalyze/error_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ def __iter__(self) -> Iterator[Error]:
Error("readonly_typeddict", "TypedDict is read-only"),
Error("generator_return", "Generator must return an iterable"),
Error("unsafe_comparison", "Non-overlapping equality checks"),
Error("must_use", "Value cannot be discarded"),
]
)

Expand Down
8 changes: 8 additions & 0 deletions pyanalyze/name_check_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4658,6 +4658,14 @@ def visit_Expr(self, node: ast.Expr) -> Value:
self._show_error_if_checking(
node, error_code=ErrorCode.missing_await, replacement=replacement
)
elif value.is_type(collections.abc.Generator) or value.is_type(
collections.abc.AsyncGenerator
):
self._show_error_if_checking(
node,
f"Must use {value} (for example, by iterating over it)",
error_code=ErrorCode.must_use,
)
return value

# Assignments
Expand Down
24 changes: 24 additions & 0 deletions pyanalyze/test_name_check_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2257,3 +2257,27 @@ async def use_async_contextlib_manager():
async with async_empty_contextlib_manager():
a = 3
assert_is_value(a, KnownValue(3))


class TestMustUse(TestNameCheckVisitorBase):
@assert_passes()
def test_generator(self):
from typing import Generator

def gen() -> Generator[int, None, None]:
yield 1
yield 2

def capybara() -> None:
gen() # E: must_use

@assert_passes()
def test_async_generator(self):
from typing import AsyncGenerator

async def gen() -> AsyncGenerator[int, None]:
yield 1
yield 2

def capybara() -> None:
gen() # E: must_use

0 comments on commit 58124fe

Please sign in to comment.