From 0fc54b621edf2cab0f5304d0eaacee5354dac322 Mon Sep 17 00:00:00 2001 From: David Fisher Date: Thu, 14 Jan 2016 12:50:27 -0800 Subject: [PATCH 1/3] Generalize yield statement function type Functions containing yield statements can now return any supertype of generator. This fixes #1011. --- mypy/checker.py | 12 ++++++++++-- mypy/messages.py | 2 +- mypy/test/data/check-statements.test | 24 ++++++++++++++++++++++-- mypy/test/data/fixtures/for.py | 2 +- mypy/test/data/lib-stub/typing.py | 12 ++++++++++++ 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index dd2183b431d4d..2086275023763 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1458,10 +1458,18 @@ def count_nested_types(self, typ: Instance, check_type: str) -> int: def visit_yield_stmt(self, s: YieldStmt) -> Type: return_type = self.return_types[-1] if isinstance(return_type, Instance): - if return_type.type.fullname() != 'typing.Iterator': + if not is_subtype(self.named_generic_type('typing.Generator', + [AnyType(), AnyType(), AnyType()]), + return_type): self.fail(messages.INVALID_RETURN_TYPE_FOR_YIELD, s) return None - expected_item_type = return_type.args[0] + if return_type.args: + expected_item_type = return_type.args[0] + else: + # if the declared supertype of `Generator` has no type + # parameters (i.e. is `object`), then the yielded values can't + # be accessed so any type is acceptable. + expected_item_type = AnyType() elif isinstance(return_type, AnyType): expected_item_type = AnyType() else: diff --git a/mypy/messages.py b/mypy/messages.py index 1b38ecdbe589d..702f44becdc53 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -30,7 +30,7 @@ INVALID_EXCEPTION = 'Exception must be derived from BaseException' INVALID_EXCEPTION_TYPE = 'Exception type must be derived from BaseException' INVALID_RETURN_TYPE_FOR_YIELD = \ - 'Iterator function return type expected for "yield"' + 'Generator or supertype function return type expected for "yield"' INVALID_RETURN_TYPE_FOR_YIELD_FROM = \ 'Iterable function return type expected for "yield from"' INCOMPATIBLE_TYPES = 'Incompatible types' diff --git a/mypy/test/data/check-statements.test b/mypy/test/data/check-statements.test index 26efeba197413..0158bd70807d4 100644 --- a/mypy/test/data/check-statements.test +++ b/mypy/test/data/check-statements.test @@ -647,6 +647,26 @@ def f() -> Iterator[int]: [out] main: note: In function "f": +[case testYieldInFunctionReturningGenerator] +from typing import Generator +def f() -> Generator[int, None, None]: + yield 1 +[builtins fixtures/for.py] +[out] + +[case testYieldInFunctionReturningIterable] +from typing import Iterable +def f() -> Iterable[int]: + yield 1 +[builtins fixtures/for.py] +[out] + +[case testYieldInFunctionReturningObject] +def f() -> object: + yield 1 +[builtins fixtures/for.py] +[out] + [case testYieldInFunctionReturningAny] from typing import Any def f() -> Any: @@ -656,7 +676,7 @@ def f() -> Any: [case testYieldInFunctionReturningFunction] from typing import Callable def f() -> Callable[[], None]: - yield object() # E: Iterator function return type expected for "yield" + yield object() # E: Generator or supertype function return type expected for "yield" [out] main: note: In function "f": @@ -668,7 +688,7 @@ def f(): [case testWithInvalidInstanceReturnType] import typing def f() -> int: - yield 1 # E: Iterator function return type expected for "yield" + yield 1 # E: Generator or supertype function return type expected for "yield" [builtins fixtures/for.py] [out] main: note: In function "f": diff --git a/mypy/test/data/fixtures/for.py b/mypy/test/data/fixtures/for.py index d8ee42a1a83b4..47628062f1184 100644 --- a/mypy/test/data/fixtures/for.py +++ b/mypy/test/data/fixtures/for.py @@ -1,6 +1,6 @@ # builtins stub used in for statement test cases -from typing import TypeVar, Generic, Iterable, Iterator +from typing import TypeVar, Generic, Iterable, Iterator, Generator from abc import abstractmethod, ABCMeta t = TypeVar('t') diff --git a/mypy/test/data/lib-stub/typing.py b/mypy/test/data/lib-stub/typing.py index b185675ed1e67..2ca3d8fdac124 100644 --- a/mypy/test/data/lib-stub/typing.py +++ b/mypy/test/data/lib-stub/typing.py @@ -23,6 +23,8 @@ Set = 0 T = TypeVar('T') +U = TypeVar('U') +V = TypeVar('V') class Container(Generic[T]): @abstractmethod @@ -41,6 +43,16 @@ class Iterator(Iterable[T], Generic[T]): @abstractmethod def __next__(self) -> T: pass +class Generator(Iterator[T], Generic[T, U, V]): + @abstractmethod + def send(self, value: U) -> T: pass + + @abstractmethod + def throw(self, typ: Any, val: Any=None, tb=None) -> None: pass + + @abstractmethod + def close(self) -> None: pass + class Sequence(Generic[T]): @abstractmethod def __getitem__(self, n: Any) -> T: pass From eec57e6f178d82fabbd62ad1f0c1aabd818783ec Mon Sep 17 00:00:00 2001 From: David Fisher Date: Thu, 14 Jan 2016 16:08:25 -0800 Subject: [PATCH 2/3] Clarify generator function error message --- mypy/messages.py | 2 +- mypy/test/data/check-statements.test | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 702f44becdc53..96ad23f3aeea1 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -30,7 +30,7 @@ INVALID_EXCEPTION = 'Exception must be derived from BaseException' INVALID_EXCEPTION_TYPE = 'Exception type must be derived from BaseException' INVALID_RETURN_TYPE_FOR_YIELD = \ - 'Generator or supertype function return type expected for "yield"' + 'The return type of a generator function should be "Generator" or a supertype thereof' INVALID_RETURN_TYPE_FOR_YIELD_FROM = \ 'Iterable function return type expected for "yield from"' INCOMPATIBLE_TYPES = 'Incompatible types' diff --git a/mypy/test/data/check-statements.test b/mypy/test/data/check-statements.test index 0158bd70807d4..823078abf3ed0 100644 --- a/mypy/test/data/check-statements.test +++ b/mypy/test/data/check-statements.test @@ -676,7 +676,7 @@ def f() -> Any: [case testYieldInFunctionReturningFunction] from typing import Callable def f() -> Callable[[], None]: - yield object() # E: Generator or supertype function return type expected for "yield" + yield object() # E: The return type of a generator function should be "Generator" or a supertype thereof [out] main: note: In function "f": @@ -688,7 +688,7 @@ def f(): [case testWithInvalidInstanceReturnType] import typing def f() -> int: - yield 1 # E: Generator or supertype function return type expected for "yield" + yield 1 # E: The return type of a generator function should be "Generator" or a supertype thereof [builtins fixtures/for.py] [out] main: note: In function "f": From fe8177281aaff1522244b8894c422db83f0ebc8c Mon Sep 17 00:00:00 2001 From: David Fisher Date: Thu, 14 Jan 2016 16:21:12 -0800 Subject: [PATCH 3/3] Further clarify generator function error message --- mypy/messages.py | 2 +- mypy/test/data/check-statements.test | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 96ad23f3aeea1..a4cc2afd04e40 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -30,7 +30,7 @@ INVALID_EXCEPTION = 'Exception must be derived from BaseException' INVALID_EXCEPTION_TYPE = 'Exception type must be derived from BaseException' INVALID_RETURN_TYPE_FOR_YIELD = \ - 'The return type of a generator function should be "Generator" or a supertype thereof' + 'The return type of a generator function should be "Generator" or one of its supertypes' INVALID_RETURN_TYPE_FOR_YIELD_FROM = \ 'Iterable function return type expected for "yield from"' INCOMPATIBLE_TYPES = 'Incompatible types' diff --git a/mypy/test/data/check-statements.test b/mypy/test/data/check-statements.test index 823078abf3ed0..3d4450e135698 100644 --- a/mypy/test/data/check-statements.test +++ b/mypy/test/data/check-statements.test @@ -676,7 +676,7 @@ def f() -> Any: [case testYieldInFunctionReturningFunction] from typing import Callable def f() -> Callable[[], None]: - yield object() # E: The return type of a generator function should be "Generator" or a supertype thereof + yield object() # E: The return type of a generator function should be "Generator" or one of its supertypes [out] main: note: In function "f": @@ -688,7 +688,7 @@ def f(): [case testWithInvalidInstanceReturnType] import typing def f() -> int: - yield 1 # E: The return type of a generator function should be "Generator" or a supertype thereof + yield 1 # E: The return type of a generator function should be "Generator" or one of its supertypes [builtins fixtures/for.py] [out] main: note: In function "f":