Skip to content

Commit 2ede35f

Browse files
authored
stubgen: Support yield from statements (#15271)
Resolves #10744
1 parent c2d02a3 commit 2ede35f

File tree

3 files changed

+133
-12
lines changed

3 files changed

+133
-12
lines changed

mypy/stubgen.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,12 @@
126126
report_missing,
127127
walk_packages,
128128
)
129-
from mypy.traverser import all_yield_expressions, has_return_statement, has_yield_expression
129+
from mypy.traverser import (
130+
all_yield_expressions,
131+
has_return_statement,
132+
has_yield_expression,
133+
has_yield_from_expression,
134+
)
130135
from mypy.types import (
131136
OVERLOAD_NAMES,
132137
TPDICT_NAMES,
@@ -774,18 +779,22 @@ def visit_func_def(self, o: FuncDef) -> None:
774779
retname = None # implicit Any
775780
elif o.name in KNOWN_MAGIC_METHODS_RETURN_TYPES:
776781
retname = KNOWN_MAGIC_METHODS_RETURN_TYPES[o.name]
777-
elif has_yield_expression(o):
782+
elif has_yield_expression(o) or has_yield_from_expression(o):
778783
self.add_typing_import("Generator")
779784
yield_name = "None"
780785
send_name = "None"
781786
return_name = "None"
782-
for expr, in_assignment in all_yield_expressions(o):
783-
if expr.expr is not None and not self.is_none_expr(expr.expr):
784-
self.add_typing_import("Incomplete")
785-
yield_name = self.typing_name("Incomplete")
786-
if in_assignment:
787-
self.add_typing_import("Incomplete")
788-
send_name = self.typing_name("Incomplete")
787+
if has_yield_from_expression(o):
788+
self.add_typing_import("Incomplete")
789+
yield_name = send_name = self.typing_name("Incomplete")
790+
else:
791+
for expr, in_assignment in all_yield_expressions(o):
792+
if expr.expr is not None and not self.is_none_expr(expr.expr):
793+
self.add_typing_import("Incomplete")
794+
yield_name = self.typing_name("Incomplete")
795+
if in_assignment:
796+
self.add_typing_import("Incomplete")
797+
send_name = self.typing_name("Incomplete")
789798
if has_return_statement(o):
790799
self.add_typing_import("Incomplete")
791800
return_name = self.typing_name("Incomplete")

mypy/traverser.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,21 @@ def has_yield_expression(fdef: FuncBase) -> bool:
873873
return seeker.found
874874

875875

876+
class YieldFromSeeker(FuncCollectorBase):
877+
def __init__(self) -> None:
878+
super().__init__()
879+
self.found = False
880+
881+
def visit_yield_from_expr(self, o: YieldFromExpr) -> None:
882+
self.found = True
883+
884+
885+
def has_yield_from_expression(fdef: FuncBase) -> bool:
886+
seeker = YieldFromSeeker()
887+
fdef.accept(seeker)
888+
return seeker.found
889+
890+
876891
class AwaitSeeker(TraverserVisitor):
877892
def __init__(self) -> None:
878893
super().__init__()
@@ -922,3 +937,24 @@ def all_yield_expressions(node: Node) -> list[tuple[YieldExpr, bool]]:
922937
v = YieldCollector()
923938
node.accept(v)
924939
return v.yield_expressions
940+
941+
942+
class YieldFromCollector(FuncCollectorBase):
943+
def __init__(self) -> None:
944+
super().__init__()
945+
self.in_assignment = False
946+
self.yield_from_expressions: list[tuple[YieldFromExpr, bool]] = []
947+
948+
def visit_assignment_stmt(self, stmt: AssignmentStmt) -> None:
949+
self.in_assignment = True
950+
super().visit_assignment_stmt(stmt)
951+
self.in_assignment = False
952+
953+
def visit_yield_from_expr(self, expr: YieldFromExpr) -> None:
954+
self.yield_from_expressions.append((expr, self.in_assignment))
955+
956+
957+
def all_yield_from_expressions(node: Node) -> list[tuple[YieldFromExpr, bool]]:
958+
v = YieldFromCollector()
959+
node.accept(v)
960+
return v.yield_from_expressions

test-data/unit/stubgen.test

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1231,6 +1231,9 @@ def h1():
12311231
def h2():
12321232
yield
12331233
return "abc"
1234+
def h3():
1235+
yield
1236+
return None
12341237
def all():
12351238
x = yield 123
12361239
return "abc"
@@ -1242,6 +1245,7 @@ def f() -> Generator[Incomplete, None, None]: ...
12421245
def g() -> Generator[None, Incomplete, None]: ...
12431246
def h1() -> Generator[None, None, None]: ...
12441247
def h2() -> Generator[None, None, Incomplete]: ...
1248+
def h3() -> Generator[None, None, None]: ...
12451249
def all() -> Generator[Incomplete, Incomplete, Incomplete]: ...
12461250

12471251
[case testFunctionYieldsNone]
@@ -1270,6 +1274,69 @@ class Generator: ...
12701274

12711275
def f() -> _Generator[Incomplete, None, None]: ...
12721276

1277+
[case testGeneratorYieldFrom]
1278+
def g1():
1279+
yield from x
1280+
def g2():
1281+
y = yield from x
1282+
def g3():
1283+
yield from x
1284+
return
1285+
def g4():
1286+
yield from x
1287+
return None
1288+
def g5():
1289+
yield from x
1290+
return z
1291+
1292+
[out]
1293+
from _typeshed import Incomplete
1294+
from collections.abc import Generator
1295+
1296+
def g1() -> Generator[Incomplete, Incomplete, None]: ...
1297+
def g2() -> Generator[Incomplete, Incomplete, None]: ...
1298+
def g3() -> Generator[Incomplete, Incomplete, None]: ...
1299+
def g4() -> Generator[Incomplete, Incomplete, None]: ...
1300+
def g5() -> Generator[Incomplete, Incomplete, Incomplete]: ...
1301+
1302+
[case testGeneratorYieldAndYieldFrom]
1303+
def g1():
1304+
yield x1
1305+
yield from x2
1306+
def g2():
1307+
yield x1
1308+
y = yield from x2
1309+
def g3():
1310+
y = yield x1
1311+
yield from x2
1312+
def g4():
1313+
yield x1
1314+
yield from x2
1315+
return
1316+
def g5():
1317+
yield x1
1318+
yield from x2
1319+
return None
1320+
def g6():
1321+
yield x1
1322+
yield from x2
1323+
return z
1324+
def g7():
1325+
yield None
1326+
yield from x2
1327+
1328+
[out]
1329+
from _typeshed import Incomplete
1330+
from collections.abc import Generator
1331+
1332+
def g1() -> Generator[Incomplete, Incomplete, None]: ...
1333+
def g2() -> Generator[Incomplete, Incomplete, None]: ...
1334+
def g3() -> Generator[Incomplete, Incomplete, None]: ...
1335+
def g4() -> Generator[Incomplete, Incomplete, None]: ...
1336+
def g5() -> Generator[Incomplete, Incomplete, None]: ...
1337+
def g6() -> Generator[Incomplete, Incomplete, Incomplete]: ...
1338+
def g7() -> Generator[Incomplete, Incomplete, None]: ...
1339+
12731340
[case testCallable]
12741341
from typing import Callable
12751342

@@ -2977,13 +3044,17 @@ def func(*, non_default_kwarg: bool, default_kwarg: bool = True): ...
29773044
def func(*, non_default_kwarg: bool, default_kwarg: bool = ...): ...
29783045

29793046
[case testNestedGenerator]
2980-
def f():
3047+
def f1():
29813048
def g():
29823049
yield 0
2983-
3050+
return 0
3051+
def f2():
3052+
def g():
3053+
yield from [0]
29843054
return 0
29853055
[out]
2986-
def f(): ...
3056+
def f1(): ...
3057+
def f2(): ...
29873058

29883059
[case testKnownMagicMethodsReturnTypes]
29893060
class Some:
@@ -3193,6 +3264,10 @@ def gen():
31933264
y = yield x
31943265
return z
31953266

3267+
def gen2():
3268+
y = yield from x
3269+
return z
3270+
31963271
class X(unknown_call("X", "a b")): ...
31973272
class Y(collections.namedtuple("Y", xx)): ...
31983273
[out]
@@ -3227,6 +3302,7 @@ TD2: _Incomplete
32273302
TD3: _Incomplete
32283303

32293304
def gen() -> _Generator[_Incomplete, _Incomplete, _Incomplete]: ...
3305+
def gen2() -> _Generator[_Incomplete, _Incomplete, _Incomplete]: ...
32303306

32313307
class X(_Incomplete): ...
32323308
class Y(_Incomplete): ...

0 commit comments

Comments
 (0)