Skip to content

Commit

Permalink
Don't flag intentionally empty generators unreachable (#15722)
Browse files Browse the repository at this point in the history
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
  • Loading branch information
ikonst and AlexWaygood committed Jul 26, 2023
1 parent d2022a0 commit 01c6994
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 10 deletions.
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
yield

[case testIntentionallyEmptyGeneratorFunction_None]
# flags: --warn-unreachable
from typing import Generator

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

0 comments on commit 01c6994

Please sign in to comment.