From 39e69151bd3be373807552da344f1763579c310f Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 2 May 2023 12:14:25 +0200 Subject: [PATCH 01/10] Fix return type of yield from union of generators --- mypy/checkexpr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index fccbad7bb87e..7e65da47f774 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -5120,7 +5120,8 @@ def visit_yield_from_expr(self, e: YieldFromExpr, allow_none_return: bool = Fals # Determine the type of the entire yield from expression. iter_type = get_proper_type(iter_type) - if isinstance(iter_type, Instance) and iter_type.type.fullname == "typing.Generator": + if isinstance(iter_type, Instance) and iter_type.type.fullname == "typing.Generator" \ + or isinstance(iter_type, UnionType): expr_type = self.chk.get_generator_return_type(iter_type, False) else: # Non-Generators don't return anything from `yield from` expressions. From 872b780846823caec8e83f130129a30b8ac876d5 Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 2 May 2023 12:19:08 +0200 Subject: [PATCH 02/10] Add tests --- test-data/unit/check-statements.test | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 3cb8864f9207..cc3b74330e2c 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -2195,6 +2195,14 @@ class B: pass def foo(x: int) -> Union[Generator[A, None, None], Generator[B, None, None]]: yield x # E: Incompatible types in "yield" (actual type "int", expected type "Union[A, B]") +[case testReturnTypeOfGeneratorUnion] +from typing import Generator, Union + +class T: pass + +def foo(arg: Union[Generator[int, None, T], Generator[str, None, T]]) -> Generator[Union[int, str], None, T]: + return (yield from arg) + [case testNoCrashOnStarRightHandSide] x = *(1, 2, 3) # E: Can use starred expression only as assignment target [builtins fixtures/tuple.pyi] From b73ccdfe8b22ca94db904d6f9e48398df840cc8e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 10:23:28 +0000 Subject: [PATCH 03/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/checkexpr.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 7e65da47f774..b6c9e682200d 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -5120,8 +5120,11 @@ def visit_yield_from_expr(self, e: YieldFromExpr, allow_none_return: bool = Fals # Determine the type of the entire yield from expression. iter_type = get_proper_type(iter_type) - if isinstance(iter_type, Instance) and iter_type.type.fullname == "typing.Generator" \ - or isinstance(iter_type, UnionType): + if ( + isinstance(iter_type, Instance) + and iter_type.type.fullname == "typing.Generator" + or isinstance(iter_type, UnionType) + ): expr_type = self.chk.get_generator_return_type(iter_type, False) else: # Non-Generators don't return anything from `yield from` expressions. From 59d47629cb7550f95be5881f95c235bdd7df586a Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 2 May 2023 18:52:26 +0200 Subject: [PATCH 04/10] Add test for yield from union error --- test-data/unit/check-statements.test | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index cc3b74330e2c..ff95400e8a9c 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -2203,6 +2203,15 @@ class T: pass def foo(arg: Union[Generator[int, None, T], Generator[str, None, T]]) -> Generator[Union[int, str], None, T]: return (yield from arg) +[case testYieldFromError] +from typing import Generator, Union + +class T: pass +class R: pass + +def foo(arg: Union[T, R]) -> Generator[Union[int, str], None, T]: + return (yield from arg) # E: "yield from" cannot be applied to "Union[T, R]" + [case testNoCrashOnStarRightHandSide] x = *(1, 2, 3) # E: Can use starred expression only as assignment target [builtins fixtures/tuple.pyi] From daf4e1b4386e90c73821663c3c4949b953815491 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 3 May 2023 10:37:36 +0200 Subject: [PATCH 05/10] Add test for union of generator and iterable --- test-data/unit/check-statements.test | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index ff95400e8a9c..635414010d54 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -2212,6 +2212,15 @@ class R: pass def foo(arg: Union[T, R]) -> Generator[Union[int, str], None, T]: return (yield from arg) # E: "yield from" cannot be applied to "Union[T, R]" +[case testUnionOfGeneratorWithIterableStr] +from typing import Generator, Union, Iterable + +class T: pass + +def foo(arg: Union[Generator[int, None, T], Iterable[str]]) -> Generator[Union[int, str], None, T]: + return (yield from arg) +[builtins fixtures/tuple.pyi] + [case testNoCrashOnStarRightHandSide] x = *(1, 2, 3) # E: Can use starred expression only as assignment target [builtins fixtures/tuple.pyi] From 6ef3287da9195e06b20c0a15ff5c920e579307a6 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 2 Jun 2023 16:58:52 +0200 Subject: [PATCH 06/10] Make get_generator_return_type return NoneType for the supertype case instead of AnyType --- mypy/checker.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index c1c31538b7de..6f1705cf7544 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -952,8 +952,9 @@ def get_generator_return_type(self, return_type: Type, is_coroutine: bool) -> Ty # AwaitableGenerator, Generator: tr is args[2]. return return_type.args[2] else: - # Supertype of Generator (Iterator, Iterable, object): tr is any. - return AnyType(TypeOfAny.special_form) + # We have a supertype of Generator (Iterator, Iterable, object) + # Treat `Iterator[X]` as a shorthand for `Generator[X, Any, None]`. + return NoneType() def visit_func_def(self, defn: FuncDef) -> None: if not self.recurse_into_functions: From da35c5053655faab4208b2dedac7bc9fd31c6eb9 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 2 Jun 2023 17:02:00 +0200 Subject: [PATCH 07/10] Refactor method to call get_generator_return_type independently of iter_type --- mypy/checkexpr.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index b154b26fcc7d..34dcbdef0ff3 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -5177,21 +5177,7 @@ def visit_yield_from_expr(self, e: YieldFromExpr, allow_none_return: bool = Fals # Determine the type of the entire yield from expression. iter_type = get_proper_type(iter_type) - if ( - isinstance(iter_type, Instance) - and iter_type.type.fullname == "typing.Generator" - or isinstance(iter_type, UnionType) - ): - expr_type = self.chk.get_generator_return_type(iter_type, False) - else: - # Non-Generators don't return anything from `yield from` expressions. - # However special-case Any (which might be produced by an error). - actual_item_type = get_proper_type(actual_item_type) - if isinstance(actual_item_type, AnyType): - expr_type = AnyType(TypeOfAny.from_another_any, source_any=actual_item_type) - else: - # Treat `Iterator[X]` as a shorthand for `Generator[X, None, Any]`. - expr_type = NoneType() + expr_type = self.chk.get_generator_return_type(iter_type, False) if not allow_none_return and isinstance(get_proper_type(expr_type), NoneType): self.chk.msg.does_not_return_value(None, e) From f179b505abc398bfc875b7e3aef751ca2790090c Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 2 Jun 2023 17:11:07 +0200 Subject: [PATCH 08/10] Expect no return value when Iterator is a return type --- test-data/unit/check-statements.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 635414010d54..9da3cd677fbe 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -85,7 +85,7 @@ def f() -> Generator[int, None, None]: from typing import Iterator def f() -> Iterator[int]: yield 1 - return "foo" + return "foo" # E: No return value expected [out] From 6f278374293029543ca966913875800ceecb89c1 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 2 Jun 2023 21:35:13 +0200 Subject: [PATCH 09/10] Remove return statement from test --- mypyc/test-data/run-generators.test | 1 - 1 file changed, 1 deletion(-) diff --git a/mypyc/test-data/run-generators.test b/mypyc/test-data/run-generators.test index bcf9da1846ae..5bf8692dcc02 100644 --- a/mypyc/test-data/run-generators.test +++ b/mypyc/test-data/run-generators.test @@ -263,7 +263,6 @@ def generator() -> Iterable[int]: print('caught exception with value ' + s) else: print('caught exception without value') - return 0 def no_except() -> Iterable[int]: yield 1 From 530f5ce7433aa4b8e003ebf08a34d1cf435667dc Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 23 Jun 2023 22:13:39 +0200 Subject: [PATCH 10/10] Fix wording --- test-data/unit/check-statements.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index b038618affbe..f824a7f479ee 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -2210,7 +2210,7 @@ class T: pass class R: pass def foo(arg: Union[T, R]) -> Generator[Union[int, str], None, T]: - return (yield from arg) # E: "yield from" cannot be applied to "Union[T, R]" + return (yield from arg) # E: "yield from" can't be applied to "Union[T, R]" [case testUnionOfGeneratorWithIterableStr] from typing import Generator, Union, Iterable