Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't flag intentionally empty generators unreachable #15722

17 changes: 16 additions & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
Var,
WhileStmt,
WithStmt,
YieldExpr,
is_final_node,
)
from mypy.options import Options
Expand Down Expand Up @@ -1063,6 +1064,20 @@ def enter_attribute_inference_context(self) -> Iterator[None]:
yield None
self.inferred_attribute_types = old_types

def _is_empty_generator(self, func: FuncItem) -> bool:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
def _is_empty_generator(self, func: FuncItem) -> bool:
@staticmethod
def _is_empty_generator(func: FuncItem) -> bool:

Or even better: move the function into the global scope?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"""
Checks whether a function's body is 'return; yield' (the yield being added only
to promote the function into a generator).
"""
return (
len(body := func.body.body) == 2
and isinstance(ret_stmt := body[0], ReturnStmt)
and (ret_stmt.expr is None or is_literal_none(ret_stmt.expr))
and isinstance(expr_stmt := body[1], ExpressionStmt)
and isinstance(yield_expr := expr_stmt.expr, YieldExpr)
and (yield_expr.expr is None or is_literal_none(yield_expr.expr))
)

def check_func_def(
self, defn: FuncItem, typ: CallableType, name: str | None, allow_empty: bool = False
) -> None:
Expand Down Expand Up @@ -1240,7 +1255,7 @@ def check_func_def(
# have no good way of doing this.
#
# TODO: Find a way of working around this limitation
if len(expanded) >= 2:
if len(expanded) >= 2 or self._is_empty_generator(item):
Copy link
Member

Choose a reason for hiding this comment

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

Maybe update the multiline comment immediately above this line? We're not just suppressing reachability warnings for TypeVars with value restrictions anymore

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I didn't want to muddy the blame for that comment 🤣

Copy link
Contributor Author

Choose a reason for hiding this comment

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

self.binder.suppress_unreachable_warnings()
self.accept(item.body)
unreachable = self.binder.is_unreachable()
Expand Down
16 changes: 16 additions & 0 deletions test-data/unit/check-unreachable-code.test
Original file line number Diff line number Diff line change
Expand Up @@ -1446,3 +1446,19 @@ def f() -> None:
Foo()['a'] = 'a'
x = 0 # This should not be reported as unreachable
[builtins fixtures/exception.pyi]

[case testIntentionallyEmptyGenerator]
ikonst marked this conversation as resolved.
Show resolved Hide resolved
# flags: --warn-unreachable
from typing import Generator

def f() -> Generator[None, None, None]:
return
ikonst marked this conversation as resolved.
Show resolved Hide resolved
yield

[case testIntentionallyEmptyGenerator_None]
ikonst marked this conversation as resolved.
Show resolved Hide resolved
# flags: --warn-unreachable
from typing import Generator

def f() -> Generator[None, None, None]:
return None
yield None