Skip to content

Commit 01c6994

Browse files
ikonstAlexWaygood
andauthored
Don't flag intentionally empty generators unreachable (#15722)
Co-authored-by: Alex Waygood <[email protected]>
1 parent d2022a0 commit 01c6994

File tree

3 files changed

+39
-10
lines changed

3 files changed

+39
-10
lines changed

mypy/binder.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,6 @@ def __init__(self, id: int, conditional_frame: bool = False) -> None:
4242
self.types: dict[Key, Type] = {}
4343
self.unreachable = False
4444
self.conditional_frame = conditional_frame
45-
46-
# Should be set only if we're entering a frame where it's not
47-
# possible to accurately determine whether or not contained
48-
# statements will be unreachable or not.
49-
#
50-
# Long-term, we should improve mypy to the point where we no longer
51-
# need this field.
5245
self.suppress_unreachable_warnings = False
5346

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

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

180172
def cleanse(self, expr: Expression) -> None:

mypy/checker.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
Var,
133133
WhileStmt,
134134
WithStmt,
135+
YieldExpr,
135136
is_final_node,
136137
)
137138
from mypy.options import Options
@@ -1241,13 +1242,17 @@ def check_func_def(
12411242
new_frame.types[key] = narrowed_type
12421243
self.binder.declarations[key] = old_binder.declarations[key]
12431244
with self.scope.push_function(defn):
1244-
# We suppress reachability warnings when we use TypeVars with value
1245+
# We suppress reachability warnings for empty generator functions
1246+
# (return; yield) which have a "yield" that's unreachable by definition
1247+
# since it's only there to promote the function into a generator function.
1248+
#
1249+
# We also suppress reachability warnings when we use TypeVars with value
12451250
# restrictions: we only want to report a warning if a certain statement is
12461251
# marked as being suppressed in *all* of the expansions, but we currently
12471252
# have no good way of doing this.
12481253
#
12491254
# TODO: Find a way of working around this limitation
1250-
if len(expanded) >= 2:
1255+
if _is_empty_generator_function(item) or len(expanded) >= 2:
12511256
self.binder.suppress_unreachable_warnings()
12521257
self.accept(item.body)
12531258
unreachable = self.binder.is_unreachable()
@@ -6968,6 +6973,22 @@ def is_literal_not_implemented(n: Expression) -> bool:
69686973
return isinstance(n, NameExpr) and n.fullname == "builtins.NotImplemented"
69696974

69706975

6976+
def _is_empty_generator_function(func: FuncItem) -> bool:
6977+
"""
6978+
Checks whether a function's body is 'return; yield' (the yield being added only
6979+
to promote the function into a generator function).
6980+
"""
6981+
body = func.body.body
6982+
return (
6983+
len(body) == 2
6984+
and isinstance(ret_stmt := body[0], ReturnStmt)
6985+
and (ret_stmt.expr is None or is_literal_none(ret_stmt.expr))
6986+
and isinstance(expr_stmt := body[1], ExpressionStmt)
6987+
and isinstance(yield_expr := expr_stmt.expr, YieldExpr)
6988+
and (yield_expr.expr is None or is_literal_none(yield_expr.expr))
6989+
)
6990+
6991+
69716992
def builtin_item_type(tp: Type) -> Type | None:
69726993
"""Get the item type of a builtin container.
69736994

test-data/unit/check-unreachable-code.test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,3 +1446,19 @@ def f() -> None:
14461446
Foo()['a'] = 'a'
14471447
x = 0 # This should not be reported as unreachable
14481448
[builtins fixtures/exception.pyi]
1449+
1450+
[case testIntentionallyEmptyGeneratorFunction]
1451+
# flags: --warn-unreachable
1452+
from typing import Generator
1453+
1454+
def f() -> Generator[None, None, None]:
1455+
return
1456+
yield
1457+
1458+
[case testIntentionallyEmptyGeneratorFunction_None]
1459+
# flags: --warn-unreachable
1460+
from typing import Generator
1461+
1462+
def f() -> Generator[None, None, None]:
1463+
return None
1464+
yield None

0 commit comments

Comments
 (0)