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

8 changes: 0 additions & 8 deletions mypy/binder.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,6 @@ def __init__(self, id: int, conditional_frame: bool = False) -> None:
self.types: dict[Key, Type] = {}
self.unreachable = False
self.conditional_frame = conditional_frame

# Should be set only if we're entering a frame where it's not
# possible to accurately determine whether or not contained
# statements will be unreachable or not.
#
# Long-term, we should improve mypy to the point where we no longer
# need this field.
self.suppress_unreachable_warnings = False

def __repr__(self) -> str:
Expand Down Expand Up @@ -174,7 +167,6 @@ def is_unreachable(self) -> bool:
return any(f.unreachable for f in self.frames)

def is_unreachable_warning_suppressed(self) -> bool:
# TODO: See todo in 'is_unreachable'
return any(f.suppress_unreachable_warnings for f in self.frames)

def cleanse(self, expr: Expression) -> None:
Expand Down
25 changes: 23 additions & 2 deletions 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 @@ -1241,13 +1242,17 @@ def check_func_def(
new_frame.types[key] = narrowed_type
self.binder.declarations[key] = old_binder.declarations[key]
with self.scope.push_function(defn):
# We suppress reachability warnings when we use TypeVars with value
# We suppress reachability warnings for empty generator functions
# (return; yield) which have a "yield" that's unreachable by definition
# since it's only there to promote the function into a generator function.
#
# We also suppress reachability warnings when we use TypeVars with value
# restrictions: we only want to report a warning if a certain statement is
# marked as being suppressed in *all* of the expansions, but we currently
# have no good way of doing this.
#
# TODO: Find a way of working around this limitation
if len(expanded) >= 2:
if _is_empty_generator_function(item) or len(expanded) >= 2:
self.binder.suppress_unreachable_warnings()
self.accept(item.body)
unreachable = self.binder.is_unreachable()
Expand Down Expand Up @@ -6968,6 +6973,22 @@ def is_literal_not_implemented(n: Expression) -> bool:
return isinstance(n, NameExpr) and n.fullname == "builtins.NotImplemented"


def _is_empty_generator_function(func: FuncItem) -> bool:
"""
Checks whether a function's body is 'return; yield' (the yield being added only
to promote the function into a generator function).
"""
body = func.body.body
return (
len(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 builtin_item_type(tp: Type) -> Type | None:
"""Get the item type of a builtin container.

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 testIntentionallyEmptyGeneratorFunction]
# 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 testIntentionallyEmptyGeneratorFunction_None]
# flags: --warn-unreachable
from typing import Generator

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