Skip to content

Commit 394d17b

Browse files
authored
Improve yield from inference for unions of generators (#16717)
Fixes #15141, closes #15168
1 parent a505e5f commit 394d17b

File tree

4 files changed

+55
-18
lines changed

4 files changed

+55
-18
lines changed

mypy/checker.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -989,8 +989,9 @@ def get_generator_return_type(self, return_type: Type, is_coroutine: bool) -> Ty
989989
# AwaitableGenerator, Generator: tr is args[2].
990990
return return_type.args[2]
991991
else:
992-
# Supertype of Generator (Iterator, Iterable, object): tr is any.
993-
return AnyType(TypeOfAny.special_form)
992+
# We have a supertype of Generator (Iterator, Iterable, object)
993+
# Treat `Iterator[X]` as a shorthand for `Generator[X, Any, None]`.
994+
return NoneType()
994995

995996
def visit_func_def(self, defn: FuncDef) -> None:
996997
if not self.recurse_into_functions:

mypy/checkexpr.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5963,17 +5963,7 @@ def visit_yield_from_expr(self, e: YieldFromExpr, allow_none_return: bool = Fals
59635963

59645964
# Determine the type of the entire yield from expression.
59655965
iter_type = get_proper_type(iter_type)
5966-
if isinstance(iter_type, Instance) and iter_type.type.fullname == "typing.Generator":
5967-
expr_type = self.chk.get_generator_return_type(iter_type, False)
5968-
else:
5969-
# Non-Generators don't return anything from `yield from` expressions.
5970-
# However special-case Any (which might be produced by an error).
5971-
actual_item_type = get_proper_type(actual_item_type)
5972-
if isinstance(actual_item_type, AnyType):
5973-
expr_type = AnyType(TypeOfAny.from_another_any, source_any=actual_item_type)
5974-
else:
5975-
# Treat `Iterator[X]` as a shorthand for `Generator[X, None, Any]`.
5976-
expr_type = NoneType()
5966+
expr_type = self.chk.get_generator_return_type(iter_type, is_coroutine=False)
59775967

59785968
if not allow_none_return and isinstance(get_proper_type(expr_type), NoneType):
59795969
self.chk.msg.does_not_return_value(None, e)

mypyc/test-data/run-generators.test

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,12 @@ assert run_generator(another_triple()()) == ((1,), None)
246246
assert run_generator(outer()) == ((0, 1, 2, 3, 4), None)
247247

248248
[case testYieldThrow]
249-
from typing import Generator, Iterable, Any
249+
from typing import Generator, Iterable, Any, Union
250250
from traceback import print_tb
251251
from contextlib import contextmanager
252252
import wrapsys
253253

254-
def generator() -> Iterable[int]:
254+
def generator() -> Generator[int, None, Union[int, None]]:
255255
try:
256256
yield 1
257257
yield 2
@@ -264,6 +264,7 @@ def generator() -> Iterable[int]:
264264
else:
265265
print('caught exception without value')
266266
return 0
267+
return None
267268

268269
def no_except() -> Iterable[int]:
269270
yield 1
@@ -355,11 +356,11 @@ with ctx_manager() as c:
355356
raise Exception
356357
File "native.py", line 10, in generator
357358
yield 3
358-
File "native.py", line 30, in wrapper
359+
File "native.py", line 31, in wrapper
359360
return (yield from x)
360361
File "native.py", line 9, in generator
361362
yield 2
362-
File "native.py", line 30, in wrapper
363+
File "native.py", line 31, in wrapper
363364
return (yield from x)
364365
caught exception without value
365366
caught exception with value some string

test-data/unit/check-statements.test

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def f() -> Generator[int, None, None]:
8585
from typing import Iterator
8686
def f() -> Iterator[int]:
8787
yield 1
88-
return "foo"
88+
return "foo" # E: No return value expected
8989
[out]
9090

9191

@@ -2231,6 +2231,51 @@ class B: pass
22312231
def foo(x: int) -> Union[Generator[A, None, None], Generator[B, None, None]]:
22322232
yield x # E: Incompatible types in "yield" (actual type "int", expected type "Union[A, B]")
22332233

2234+
[case testYieldFromUnionOfGenerators]
2235+
from typing import Generator, Union
2236+
2237+
class T: pass
2238+
2239+
def foo(arg: Union[Generator[int, None, T], Generator[str, None, T]]) -> Generator[Union[int, str], None, T]:
2240+
return (yield from arg)
2241+
2242+
[case testYieldFromInvalidUnionReturn]
2243+
from typing import Generator, Union
2244+
2245+
class A: pass
2246+
class B: pass
2247+
2248+
def foo(arg: Union[A, B]) -> Generator[Union[int, str], None, A]:
2249+
return (yield from arg) # E: "yield from" can't be applied to "Union[A, B]"
2250+
2251+
[case testYieldFromUnionOfGeneratorWithIterableStr]
2252+
from typing import Generator, Union, Iterable, Optional
2253+
2254+
def foo(arg: Union[Generator[int, None, bytes], Iterable[str]]) -> Generator[Union[int, str], None, Optional[bytes]]:
2255+
return (yield from arg)
2256+
2257+
def bar(arg: Generator[str, None, str]) -> Generator[str, None, str]:
2258+
return foo(arg) # E: Incompatible return value type (got "Generator[Union[int, str], None, Optional[bytes]]", expected "Generator[str, None, str]")
2259+
2260+
def launder(arg: Iterable[str]) -> Generator[Union[int, str], None, Optional[bytes]]:
2261+
return foo(arg)
2262+
2263+
def baz(arg: Generator[str, None, str]) -> Generator[Union[int, str], None, Optional[bytes]]:
2264+
# this is unsound, the Generator return type will actually be str
2265+
return launder(arg)
2266+
[builtins fixtures/tuple.pyi]
2267+
2268+
[case testYieldIteratorReturn]
2269+
from typing import Iterator
2270+
2271+
def get_strings(foo: bool) -> Iterator[str]:
2272+
if foo:
2273+
return ["foo1", "foo2"] # E: No return value expected
2274+
else:
2275+
yield "bar1"
2276+
yield "bar2"
2277+
[builtins fixtures/tuple.pyi]
2278+
22342279
[case testNoCrashOnStarRightHandSide]
22352280
x = *(1, 2, 3) # E: can't use starred expression here
22362281
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)