From c741a97f40345e67ccb5d9221295afdb1a3e6051 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 17 Jan 2017 05:26:58 -0800 Subject: [PATCH 01/11] asyncgen: progress --- extensions/mypy_extensions.py | 24 +++++++++++++++++++++ mypy/checker.py | 8 +++++-- mypy/messages.py | 2 ++ mypy/nodes.py | 5 +++-- mypy/semanal.py | 21 ++++++++++++------ test-data/unit/check-async-await.test | 19 ++++++++++++++++ test-data/unit/lib-stub/mypy_extensions.pyi | 2 +- test-data/unit/lib-stub/typing.pyi | 16 ++++++++++++++ 8 files changed, 86 insertions(+), 11 deletions(-) diff --git a/extensions/mypy_extensions.py b/extensions/mypy_extensions.py index 26e568cc0a273..421cede1d43c6 100644 --- a/extensions/mypy_extensions.py +++ b/extensions/mypy_extensions.py @@ -6,12 +6,36 @@ """ # NOTE: This module must support Python 2.7 in addition to Python 3.x +__all__ = ['TypedDict', 'AsyncGenerator'] import sys # _type_check is NOT a part of public typing API, it is used here only to mimic # the (convenient) behavior of types provided by typing module. from typing import _type_check # type: ignore +if sys.version_info >= (3, 6): + # if our typing version doesn't have https://github.com/python/typing/pull/346, emulate it + try: + from typing import AsyncGenerator + except ImportError: + import collections + from typing import AsyncIterator, Generic, _generic_new, T_co, T_contra + + _AG_base = collections.AsyncGenerator + + # need exec because the class keyword arg is a syntax error in 2.7 + exec(""" +class AsyncGenerator(AsyncIterator[T_co], Generic[T_co, T_contra], + extra=_AG_base): + __slots__ = () + + def __new__(cls, *args, **kwds): + if _geqv(cls, AsyncGenerator): + raise TypeError("Type AsyncGenerator cannot be instantiated; " + "create a subclass instead") + return _generic_new(_AG_base, cls, *args, **kwds) +""") + def _check_fails(cls, other): try: diff --git a/mypy/checker.py b/mypy/checker.py index 307afe8568d59..98246e8a22651 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -537,8 +537,12 @@ def is_implicit_any(t: Type) -> bool: # Check that Generator functions have the appropriate return type. if defn.is_generator: - if not self.is_generator_return_type(typ.ret_type, defn.is_coroutine): - self.fail(messages.INVALID_RETURN_TYPE_FOR_GENERATOR, typ) + if defn.is_coroutine: + if not self.is_async_generator_return_type(typ.ret_type): + pass + else: + if not self.is_generator_return_type(typ.ret_type, defn.is_coroutine): + self.fail(messages.INVALID_RETURN_TYPE_FOR_GENERATOR, typ) # Python 2 generators aren't allowed to return values. if (self.options.python_version[0] == 2 and diff --git a/mypy/messages.py b/mypy/messages.py index 782df03f5bd64..6c6937070c147 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -34,6 +34,8 @@ INVALID_EXCEPTION_TYPE = 'Exception type must be derived from BaseException' INVALID_RETURN_TYPE_FOR_GENERATOR = \ 'The return type of a generator function should be "Generator" or one of its supertypes' +INVALID_RETURN_TYPE_FOR_ASYNC_GENERATOR = \ + 'The return type of an async generator function should be "AsyncGenerator" or one of its supertypes' INVALID_GENERATOR_RETURN_ITEM_TYPE = \ 'The return type of a generator function must be None in its third type parameter in Python 2' YIELD_VALUE_EXPECTED = 'Yield value expected' diff --git a/mypy/nodes.py b/mypy/nodes.py index 1346581ef8d3b..abbcb8aa6a902 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -481,6 +481,7 @@ class FuncItem(FuncBase): is_overload = False is_generator = False # Contains a yield statement? is_coroutine = False # Defined using 'async def' syntax? + is_coroutine_generator = False # Is an async def generator? is_awaitable_coroutine = False # Decorated with '@{typing,asyncio}.coroutine'? is_static = False # Uses @staticmethod? is_class = False # Uses @classmethod? @@ -488,8 +489,8 @@ class FuncItem(FuncBase): expanded = None # type: List[FuncItem] FLAGS = [ - 'is_overload', 'is_generator', 'is_coroutine', 'is_awaitable_coroutine', - 'is_static', 'is_class', + 'is_overload', 'is_generator', 'is_coroutine', 'is_coroutine_generator', + 'is_awaitable_coroutine', 'is_static', 'is_class', ] def __init__(self, arguments: List[Argument], body: 'Block', diff --git a/mypy/semanal.py b/mypy/semanal.py index 0236bddf52709..25702e9bbf538 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -324,11 +324,15 @@ def visit_func_def(self, defn: FuncDef) -> None: self.errors.push_function(defn.name()) self.analyze_function(defn) if defn.is_coroutine and isinstance(defn.type, CallableType): - # A coroutine defined as `async def foo(...) -> T: ...` - # has external return type `Awaitable[T]`. - defn.type = defn.type.copy_modified( - ret_type = self.named_type_or_none('typing.Awaitable', - [defn.type.ret_type])) + if defn.is_coroutine_generator: + # Async generator types are handled elsewhere + pass + else: + # A coroutine defined as `async def foo(...) -> T: ...` + # has external return type `Awaitable[T]`. + defn.type = defn.type.copy_modified( + ret_type = self.named_type_or_none('typing.Awaitable', + [defn.type.ret_type])) self.errors.pop_function() def prepare_method_signature(self, func: FuncDef) -> None: @@ -1290,6 +1294,7 @@ def normalize_type_alias(self, node: SymbolTableNode, normalized = False if node.fullname in type_aliases: # Node refers to an aliased type such as typing.List; normalize. + old_node = node node = self.lookup_qualified(type_aliases[node.fullname], ctx) normalized = True if node.fullname in collections_type_aliases: @@ -2842,7 +2847,11 @@ def visit_yield_expr(self, expr: YieldExpr) -> None: self.fail("'yield' outside function", expr, True, blocker=True) else: if self.function_stack[-1].is_coroutine: - self.fail("'yield' in async function", expr, True, blocker=True) + if self.options.python_version < (3, 6): + self.fail("'yield' in async function", expr, True, blocker=True) + else: + self.function_stack[-1].is_generator = True + self.function_stack[-1].is_coroutine_generator = True else: self.function_stack[-1].is_generator = True if expr.expr: diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index ac2ada81d7d73..cebc666c43a1b 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -410,6 +410,25 @@ def f() -> Generator[int, str, int]: [builtins fixtures/async_await.pyi] [out] +[case testAsyncGenerator] +# flags: --fast-parser --python-version 3.6 +from mypy_extensions import AsyncGenerator + +async def f() -> int: + return 42 + +async def g() -> AsyncGenerator[int, str]: + value = await f() + reveal_type(value) # E: Revealed type is 'builtins.int' + x = yield value + reveal_type(x) # E: Revealed type is 'builtins.str' + +async def h() -> None: + async for item in g(): + reveal_type(item) # E: Revealed type is 'builtins.int' + +[builtins fixtures/dict.pyi] + -- The full matrix of coroutine compatibility -- ------------------------------------------ diff --git a/test-data/unit/lib-stub/mypy_extensions.pyi b/test-data/unit/lib-stub/mypy_extensions.pyi index 6e1e3b0ed2853..72b96170abe52 100644 --- a/test-data/unit/lib-stub/mypy_extensions.pyi +++ b/test-data/unit/lib-stub/mypy_extensions.pyi @@ -1,4 +1,4 @@ -from typing import Dict, Type, TypeVar +from typing import Dict, Type, TypeVar, AsyncGenerator as AsyncGenerator T = TypeVar('T') diff --git a/test-data/unit/lib-stub/typing.pyi b/test-data/unit/lib-stub/typing.pyi index 0377f19a1e5d8..3a1dea205b125 100644 --- a/test-data/unit/lib-stub/typing.pyi +++ b/test-data/unit/lib-stub/typing.pyi @@ -59,6 +59,22 @@ class Generator(Iterator[T], Generic[T, U, V]): @abstractmethod def __iter__(self) -> 'Generator[T, U, V]': pass +class AsyncGenerator(AsyncIterator[T], Generic[T, U]): + @abstractmethod + def __anext__(self) -> Awaitable[T]: pass + + @abstractmethod + def asend(self, value: U) -> Awaitable[T]: pass + + @abstractmethod + def athrow(self, typ: Any, val: Any=None, tb: Any=None) -> Awaitable[None]: pass + + @abstractmethod + def aclose(self) -> Awaitable[None]: pass + + @abstractmethod + def __aiter__(self) -> 'AsyncGenerator[T, U]': pass + class Awaitable(Generic[T]): @abstractmethod def __await__(self) -> Generator[Any, Any, T]: pass From 4dc62b6512806e4f65f23ea9732e5bd6edda3550 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 17 Jan 2017 20:08:30 -0800 Subject: [PATCH 02/11] rename is_coroutine_generator -> is_async_generator, implement is_async_generator_return_type --- mypy/checker.py | 19 +++++++++++++++++-- mypy/nodes.py | 4 ++-- mypy/semanal.py | 4 ++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 98246e8a22651..c9cfdf9982f48 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -311,12 +311,19 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: # for functions decorated with `@types.coroutine` or # `@asyncio.coroutine`. Its single parameter corresponds to tr. # + # PEP 525 adds a new type, the asynchronous generator, which was + # first released in Python 3.6. Async generators are `async def` + # functions that can also `yield` values. They can be parameterized + # with two types, ty and tc, because they cannot return a value. + # # There are several useful methods, each taking a type t and a # flag c indicating whether it's for a generator or coroutine: # # - is_generator_return_type(t, c) returns whether t is a Generator, # Iterator, Iterable (if not c), or Awaitable (if c), or # AwaitableGenerator (regardless of c). + # - is_async_generator_return_type(t) returns whether t is an + # AsyncGenerator. # - get_generator_yield_type(t, c) returns ty. # - get_generator_receive_type(t, c) returns tc. # - get_generator_return_type(t, c) returns tr. @@ -338,6 +345,14 @@ def is_generator_return_type(self, typ: Type, is_coroutine: bool) -> bool: return True return isinstance(typ, Instance) and typ.type.fullname() == 'typing.AwaitableGenerator' + def is_async_generator_return_type(self, typ: Type) -> bool: + """Is `typ` a valid type for an async generator? + + True if `typ` is a supertype of AsyncGenerator. + """ + agt = self.named_generic_type('typing.AsyncGenerator', [AnyType(), AnyType()]) + return is_subtype(agt, typ) + def get_generator_yield_type(self, return_type: Type, is_coroutine: bool) -> Type: """Given the declared return type of a generator (t), return the type it yields (ty).""" if isinstance(return_type, AnyType): @@ -537,9 +552,9 @@ def is_implicit_any(t: Type) -> bool: # Check that Generator functions have the appropriate return type. if defn.is_generator: - if defn.is_coroutine: + if defn.is_async_generator: if not self.is_async_generator_return_type(typ.ret_type): - pass + self.fail(messages.INVALID_RETURN_TYPE_FOR_ASYNC_GENERATOR, typ) else: if not self.is_generator_return_type(typ.ret_type, defn.is_coroutine): self.fail(messages.INVALID_RETURN_TYPE_FOR_GENERATOR, typ) diff --git a/mypy/nodes.py b/mypy/nodes.py index abbcb8aa6a902..f9599c13cf4ba 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -481,7 +481,7 @@ class FuncItem(FuncBase): is_overload = False is_generator = False # Contains a yield statement? is_coroutine = False # Defined using 'async def' syntax? - is_coroutine_generator = False # Is an async def generator? + is_async_generator = False # Is an async def generator? is_awaitable_coroutine = False # Decorated with '@{typing,asyncio}.coroutine'? is_static = False # Uses @staticmethod? is_class = False # Uses @classmethod? @@ -489,7 +489,7 @@ class FuncItem(FuncBase): expanded = None # type: List[FuncItem] FLAGS = [ - 'is_overload', 'is_generator', 'is_coroutine', 'is_coroutine_generator', + 'is_overload', 'is_generator', 'is_coroutine', 'is_async_generator', 'is_awaitable_coroutine', 'is_static', 'is_class', ] diff --git a/mypy/semanal.py b/mypy/semanal.py index 25702e9bbf538..1c04584a66740 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -324,7 +324,7 @@ def visit_func_def(self, defn: FuncDef) -> None: self.errors.push_function(defn.name()) self.analyze_function(defn) if defn.is_coroutine and isinstance(defn.type, CallableType): - if defn.is_coroutine_generator: + if defn.is_async_generator: # Async generator types are handled elsewhere pass else: @@ -2851,7 +2851,7 @@ def visit_yield_expr(self, expr: YieldExpr) -> None: self.fail("'yield' in async function", expr, True, blocker=True) else: self.function_stack[-1].is_generator = True - self.function_stack[-1].is_coroutine_generator = True + self.function_stack[-1].is_async_generator = True else: self.function_stack[-1].is_generator = True if expr.expr: From c64e7fc63096b317c97ca5911c92ebabaebe48d8 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 17 Jan 2017 20:51:19 -0800 Subject: [PATCH 03/11] working implementation --- mypy/checker.py | 9 ++- mypy/semanal.py | 1 - test-data/unit/check-async-await.test | 110 ++++++++++++++++++++++++-- test-data/unit/fixtures/dict.pyi | 2 + 4 files changed, 114 insertions(+), 8 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index c9cfdf9982f48..1b51551405ad1 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3,6 +3,7 @@ import itertools import fnmatch from contextlib import contextmanager +import sys from typing import ( Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator @@ -357,7 +358,8 @@ def get_generator_yield_type(self, return_type: Type, is_coroutine: bool) -> Typ """Given the declared return type of a generator (t), return the type it yields (ty).""" if isinstance(return_type, AnyType): return AnyType() - elif not self.is_generator_return_type(return_type, is_coroutine): + elif (not self.is_generator_return_type(return_type, is_coroutine) + and not self.is_async_generator_return_type(return_type)): # If the function doesn't have a proper Generator (or # Awaitable) return type, anything is permissible. return AnyType() @@ -388,7 +390,8 @@ def get_generator_receive_type(self, return_type: Type, is_coroutine: bool) -> T """Given a declared generator return type (t), return the type its yield receives (tc).""" if isinstance(return_type, AnyType): return AnyType() - elif not self.is_generator_return_type(return_type, is_coroutine): + elif (not self.is_generator_return_type(return_type, is_coroutine) + and not self.is_async_generator_return_type(return_type)): # If the function doesn't have a proper Generator (or # Awaitable) return type, anything is permissible. return AnyType() @@ -402,6 +405,8 @@ def get_generator_receive_type(self, return_type: Type, is_coroutine: bool) -> T and len(return_type.args) >= 3): # Generator: tc is args[1]. return return_type.args[1] + elif return_type.type.fullname() == 'typing.AsyncGenerator' and len(return_type.args) >= 2: + return return_type.args[1] else: # `return_type` is a supertype of Generator, so callers won't be able to send it # values. IOW, tc is None. diff --git a/mypy/semanal.py b/mypy/semanal.py index 1c04584a66740..e11b89574aaab 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1294,7 +1294,6 @@ def normalize_type_alias(self, node: SymbolTableNode, normalized = False if node.fullname in type_aliases: # Node refers to an aliased type such as typing.List; normalize. - old_node = node node = self.lookup_qualified(type_aliases[node.fullname], ctx) normalized = True if node.fullname in collections_type_aliases: diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index cebc666c43a1b..5bba48d9a5522 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -410,6 +410,9 @@ def f() -> Generator[int, str, int]: [builtins fixtures/async_await.pyi] [out] +-- Async generators (PEP 525), some test cases adapted from the PEP text +-- --------------------------------------------------------------------- + [case testAsyncGenerator] # flags: --fast-parser --python-version 3.6 from mypy_extensions import AsyncGenerator @@ -417,18 +420,115 @@ from mypy_extensions import AsyncGenerator async def f() -> int: return 42 -async def g() -> AsyncGenerator[int, str]: +async def g() -> AsyncGenerator[int, None]: value = await f() - reveal_type(value) # E: Revealed type is 'builtins.int' - x = yield value - reveal_type(x) # E: Revealed type is 'builtins.str' + reveal_type(value) # E: Revealed type is 'builtins.int*' + yield value + # return without a value is fine + return +reveal_type(g) # E: Revealed type is 'def () -> typing.AsyncGenerator[builtins.int, void]' +reveal_type(g()) # E: Revealed type is 'typing.AsyncGenerator[builtins.int, void]' async def h() -> None: async for item in g(): - reveal_type(item) # E: Revealed type is 'builtins.int' + reveal_type(item) # E: Revealed type is 'builtins.int*' + +[builtins fixtures/dict.pyi] + +[case testAsyncGeneratorManualIter] +# flags: --fast-parser --python-version 3.6 +from mypy_extensions import AsyncGenerator + +async def genfunc() -> AsyncGenerator[int, None]: + yield 1 + yield 2 + +async def user() -> None: + gen = genfunc() + + reveal_type(gen.__aiter__()) # E: Revealed type is 'typing.AsyncGenerator[builtins.int*, void]' + + reveal_type(await gen.__anext__()) # E: Revealed type is 'builtins.int*' + +[builtins fixtures/dict.pyi] + +[case testAsyncGeneratorAsend] +# flags: --fast-parser --python-version 3.6 +from mypy_extensions import AsyncGenerator + +async def f() -> None: + pass + +async def gen() -> AsyncGenerator[int, str]: + await f() + v = yield 42 + reveal_type(v) # E: Revealed type is 'builtins.str' + await f() + +async def h() -> None: + g = gen() + await g.asend(()) # E: Argument 1 to "asend" of "AsyncGenerator" has incompatible type "Tuple[]"; expected "str" + reveal_type(await g.asend('hello')) # E: Revealed type is 'builtins.int*' [builtins fixtures/dict.pyi] +[case testAsyncGeneratorAthrow] +# flags: --fast-parser --python-version 3.6 +from mypy_extensions import AsyncGenerator + +async def gen() -> AsyncGenerator[str, int]: + try: + yield 'hello' + except BaseException: + yield 'world' + +async def h() -> None: + g = gen() + v = await g.asend(1) + reveal_type(v) # E: Revealed type is 'builtins.str*' + await g.athrow(BaseException) + +[builtins fixtures/dict.pyi] + +[case testAsyncGeneratorNoSyncIteration] +# flags: --fast-parser --python-version 3.6 +from mypy_extensions import AsyncGenerator + +async def gen() -> AsyncGenerator[int, None]: + for i in (1, 2, 3): + yield i + +def h() -> None: + for i in gen(): + pass + +[builtins fixtures/dict.pyi] + +[out] +main:9: error: Iterable expected +main:9: error: AsyncGenerator[int, None] has no attribute "__iter__"; maybe "__aiter__"? + +[case testAsyncGeneratorNoYieldFrom] +# flags: --fast-parser --python-version 3.6 +from mypy_extensions import AsyncGenerator + +async def f() -> AsyncGenerator[int, None]: + pass + +async def gen() -> AsyncGenerator[int, None]: + yield from f() # E: 'yield from' in async function + +[builtins fixtures/dict.pyi] + +[case testAsyncGeneratorNoReturnWithValue] +# flags: --fast-parser --python-version 3.6 +from mypy_extensions import AsyncGenerator + +async def gen() -> AsyncGenerator[int, None]: + yield 1 + return 42 + +[builtins fixtures/dict.pyi] -- The full matrix of coroutine compatibility -- ------------------------------------------ diff --git a/test-data/unit/fixtures/dict.pyi b/test-data/unit/fixtures/dict.pyi index d36145fa06541..dc89366c11337 100644 --- a/test-data/unit/fixtures/dict.pyi +++ b/test-data/unit/fixtures/dict.pyi @@ -38,3 +38,5 @@ class tuple: pass class function: pass class float: pass class bool: pass + +class BaseException: pass From 0117379f7fc7dfad500b13fcfcc10afd4590d1f5 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 18 Jan 2017 20:33:58 -0800 Subject: [PATCH 04/11] fix return types of athrow and aclose, error when async generator returns a value --- mypy/checker.py | 2 +- test-data/unit/check-async-await.test | 4 ++-- test-data/unit/lib-stub/typing.pyi | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 1b51551405ad1..c7b184d881fd5 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1775,7 +1775,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None: self.warn(messages.RETURN_ANY.format(return_type), s) return - if self.is_unusable_type(return_type): + if self.is_unusable_type(return_type) or defn.is_async_generator: # Lambdas are allowed to have a unusable returns. # Functions returning a value of type None are allowed to have a Void return. if isinstance(self.scope.top_function(), FuncExpr) or isinstance(typ, NoneTyp): diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index 5bba48d9a5522..0b95de5c92e0c 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -486,7 +486,7 @@ async def h() -> None: g = gen() v = await g.asend(1) reveal_type(v) # E: Revealed type is 'builtins.str*' - await g.athrow(BaseException) + reveal_type(await g.athrow(BaseException)) # E: Revealed type is 'builtins.str*' [builtins fixtures/dict.pyi] @@ -526,7 +526,7 @@ from mypy_extensions import AsyncGenerator async def gen() -> AsyncGenerator[int, None]: yield 1 - return 42 + return 42 # E: No return value expected [builtins fixtures/dict.pyi] diff --git a/test-data/unit/lib-stub/typing.pyi b/test-data/unit/lib-stub/typing.pyi index 3a1dea205b125..8d252dbddf2ec 100644 --- a/test-data/unit/lib-stub/typing.pyi +++ b/test-data/unit/lib-stub/typing.pyi @@ -67,10 +67,10 @@ class AsyncGenerator(AsyncIterator[T], Generic[T, U]): def asend(self, value: U) -> Awaitable[T]: pass @abstractmethod - def athrow(self, typ: Any, val: Any=None, tb: Any=None) -> Awaitable[None]: pass + def athrow(self, typ: Any, val: Any=None, tb: Any=None) -> Awaitable[T]: pass @abstractmethod - def aclose(self) -> Awaitable[None]: pass + def aclose(self) -> Awaitable[T]: pass @abstractmethod def __aiter__(self) -> 'AsyncGenerator[T, U]': pass From 01b50dec12b88d2cb67e03e959495c3ed87767f9 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 18 Jan 2017 20:37:05 -0800 Subject: [PATCH 05/11] use AsyncGenerator from typing --- test-data/unit/check-async-await.test | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index 0b95de5c92e0c..2e084bacc235b 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -415,7 +415,7 @@ def f() -> Generator[int, str, int]: [case testAsyncGenerator] # flags: --fast-parser --python-version 3.6 -from mypy_extensions import AsyncGenerator +from typing import AsyncGenerator async def f() -> int: return 42 @@ -437,7 +437,7 @@ async def h() -> None: [case testAsyncGeneratorManualIter] # flags: --fast-parser --python-version 3.6 -from mypy_extensions import AsyncGenerator +from typing import AsyncGenerator async def genfunc() -> AsyncGenerator[int, None]: yield 1 @@ -454,7 +454,7 @@ async def user() -> None: [case testAsyncGeneratorAsend] # flags: --fast-parser --python-version 3.6 -from mypy_extensions import AsyncGenerator +from typing import AsyncGenerator async def f() -> None: pass @@ -474,7 +474,7 @@ async def h() -> None: [case testAsyncGeneratorAthrow] # flags: --fast-parser --python-version 3.6 -from mypy_extensions import AsyncGenerator +from typing import AsyncGenerator async def gen() -> AsyncGenerator[str, int]: try: @@ -492,7 +492,7 @@ async def h() -> None: [case testAsyncGeneratorNoSyncIteration] # flags: --fast-parser --python-version 3.6 -from mypy_extensions import AsyncGenerator +from typing import AsyncGenerator async def gen() -> AsyncGenerator[int, None]: for i in (1, 2, 3): @@ -510,7 +510,7 @@ main:9: error: AsyncGenerator[int, None] has no attribute "__iter__"; maybe "__a [case testAsyncGeneratorNoYieldFrom] # flags: --fast-parser --python-version 3.6 -from mypy_extensions import AsyncGenerator +from typing import AsyncGenerator async def f() -> AsyncGenerator[int, None]: pass @@ -522,7 +522,7 @@ async def gen() -> AsyncGenerator[int, None]: [case testAsyncGeneratorNoReturnWithValue] # flags: --fast-parser --python-version 3.6 -from mypy_extensions import AsyncGenerator +from typing import AsyncGenerator async def gen() -> AsyncGenerator[int, None]: yield 1 From feaf0512833f4be274621135fa79c20de5adb67e Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 18 Jan 2017 20:38:37 -0800 Subject: [PATCH 06/11] remove AsyncGenerator from mypy_extensions Probably not worth it --- extensions/mypy_extensions.py | 24 --------------------- test-data/unit/lib-stub/mypy_extensions.pyi | 2 +- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/extensions/mypy_extensions.py b/extensions/mypy_extensions.py index 421cede1d43c6..26e568cc0a273 100644 --- a/extensions/mypy_extensions.py +++ b/extensions/mypy_extensions.py @@ -6,36 +6,12 @@ """ # NOTE: This module must support Python 2.7 in addition to Python 3.x -__all__ = ['TypedDict', 'AsyncGenerator'] import sys # _type_check is NOT a part of public typing API, it is used here only to mimic # the (convenient) behavior of types provided by typing module. from typing import _type_check # type: ignore -if sys.version_info >= (3, 6): - # if our typing version doesn't have https://github.com/python/typing/pull/346, emulate it - try: - from typing import AsyncGenerator - except ImportError: - import collections - from typing import AsyncIterator, Generic, _generic_new, T_co, T_contra - - _AG_base = collections.AsyncGenerator - - # need exec because the class keyword arg is a syntax error in 2.7 - exec(""" -class AsyncGenerator(AsyncIterator[T_co], Generic[T_co, T_contra], - extra=_AG_base): - __slots__ = () - - def __new__(cls, *args, **kwds): - if _geqv(cls, AsyncGenerator): - raise TypeError("Type AsyncGenerator cannot be instantiated; " - "create a subclass instead") - return _generic_new(_AG_base, cls, *args, **kwds) -""") - def _check_fails(cls, other): try: diff --git a/test-data/unit/lib-stub/mypy_extensions.pyi b/test-data/unit/lib-stub/mypy_extensions.pyi index 72b96170abe52..6e1e3b0ed2853 100644 --- a/test-data/unit/lib-stub/mypy_extensions.pyi +++ b/test-data/unit/lib-stub/mypy_extensions.pyi @@ -1,4 +1,4 @@ -from typing import Dict, Type, TypeVar, AsyncGenerator as AsyncGenerator +from typing import Dict, Type, TypeVar T = TypeVar('T') From 2273ac8dd135785c1faf0992130ca340c1550810 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 18 Jan 2017 21:18:22 -0800 Subject: [PATCH 07/11] fix lint and make it crash less often on typing without AsyncGenerator --- mypy/checker.py | 6 +++++- mypy/messages.py | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index c7b184d881fd5..df410074c50d3 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -351,7 +351,11 @@ def is_async_generator_return_type(self, typ: Type) -> bool: True if `typ` is a supertype of AsyncGenerator. """ - agt = self.named_generic_type('typing.AsyncGenerator', [AnyType(), AnyType()]) + try: + agt = self.named_generic_type('typing.AsyncGenerator', [AnyType(), AnyType()]) + except KeyError: + # we're running on a version of typing that doesn't have AsyncGenerator yet + return False return is_subtype(agt, typ) def get_generator_yield_type(self, return_type: Type, is_coroutine: bool) -> Type: diff --git a/mypy/messages.py b/mypy/messages.py index 6c6937070c147..88edba48a38c5 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -35,7 +35,8 @@ INVALID_RETURN_TYPE_FOR_GENERATOR = \ 'The return type of a generator function should be "Generator" or one of its supertypes' INVALID_RETURN_TYPE_FOR_ASYNC_GENERATOR = \ - 'The return type of an async generator function should be "AsyncGenerator" or one of its supertypes' + 'The return type of an async generator function should be "AsyncGenerator" or one of its ' \ + 'supertypes' INVALID_GENERATOR_RETURN_ITEM_TYPE = \ 'The return type of a generator function must be None in its third type parameter in Python 2' YIELD_VALUE_EXPECTED = 'Yield value expected' From c255d84a6f95b99fa661931d723ac0be7a53868f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 18 Mar 2017 10:04:32 -0700 Subject: [PATCH 08/11] address Ivan's comments --- mypy/checker.py | 9 +++++-- test-data/unit/check-async-await.test | 34 ++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index df410074c50d3..efadddb2a4911 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -374,7 +374,7 @@ def get_generator_yield_type(self, return_type: Type, is_coroutine: bool) -> Typ # Awaitable: ty is Any. return AnyType() elif return_type.args: - # AwaitableGenerator, Generator, Iterator, or Iterable; ty is args[0]. + # AwaitableGenerator, Generator, AsyncGenerator, Iterator, or Iterable; ty is args[0]. ret_type = return_type.args[0] # TODO not best fix, better have dedicated yield token if isinstance(ret_type, NoneTyp): @@ -1771,6 +1771,11 @@ def check_return_stmt(self, s: ReturnStmt) -> None: if s.expr: # Return with a value. typ = self.expr_checker.accept(s.expr, return_type) + + if defn.is_async_generator: + self.fail("'return' with value in async generator is not allowed", s) + return + # Returning a value of type Any is always fine. if isinstance(typ, AnyType): # (Unless you asked to be warned in that case, and the @@ -1779,7 +1784,7 @@ def check_return_stmt(self, s: ReturnStmt) -> None: self.warn(messages.RETURN_ANY.format(return_type), s) return - if self.is_unusable_type(return_type) or defn.is_async_generator: + if self.is_unusable_type(return_type): # Lambdas are allowed to have a unusable returns. # Functions returning a value of type None are allowed to have a Void return. if isinstance(self.scope.top_function(), FuncExpr) or isinstance(typ, NoneTyp): diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index 2e084bacc235b..8945bde930b6f 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -414,7 +414,7 @@ def f() -> Generator[int, str, int]: -- --------------------------------------------------------------------- [case testAsyncGenerator] -# flags: --fast-parser --python-version 3.6 +# flags: --python-version 3.6 from typing import AsyncGenerator async def f() -> int: @@ -424,6 +424,8 @@ async def g() -> AsyncGenerator[int, None]: value = await f() reveal_type(value) # E: Revealed type is 'builtins.int*' yield value + + yield 'not an int' # return without a value is fine return reveal_type(g) # E: Revealed type is 'def () -> typing.AsyncGenerator[builtins.int, void]' @@ -435,8 +437,23 @@ async def h() -> None: [builtins fixtures/dict.pyi] +[case testAsyncGeneratorReturnIterator] +# flags: --python-version 3.6 +from typing import AsyncIterator + +async def gen() -> AsyncIterator[int]: + yield 3 + + yield 'not an int' + +async def use_gen() -> None: + async for item in gen(): + reveal_type(item) # E: Revealed type is 'builtins.int*' + +[builtins fixtures/dict.pyi] + [case testAsyncGeneratorManualIter] -# flags: --fast-parser --python-version 3.6 +# flags: --python-version 3.6 from typing import AsyncGenerator async def genfunc() -> AsyncGenerator[int, None]: @@ -524,10 +541,21 @@ async def gen() -> AsyncGenerator[int, None]: # flags: --fast-parser --python-version 3.6 from typing import AsyncGenerator -async def gen() -> AsyncGenerator[int, None]: +async def return_int() -> AsyncGenerator[int, None]: yield 1 return 42 # E: No return value expected +async def return_none() -> AsyncGenerator[int, None]: + yield 1 + return None + +def f() -> None: + return + +async def return_f() -> AsyncGenerator[int, None]: + yield 1 + return f() + [builtins fixtures/dict.pyi] -- The full matrix of coroutine compatibility From facc5ed1ae7af23f736a9235a81d8518be4b409a Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 18 Mar 2017 10:11:35 -0700 Subject: [PATCH 09/11] fix tests --- test-data/unit/check-async-await.test | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index 8945bde930b6f..d9c12096ebdb4 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -425,7 +425,7 @@ async def g() -> AsyncGenerator[int, None]: reveal_type(value) # E: Revealed type is 'builtins.int*' yield value - yield 'not an int' + yield 'not an int' # E: Incompatible types in yield (actual type "str", expected type "int") # return without a value is fine return reveal_type(g) # E: Revealed type is 'def () -> typing.AsyncGenerator[builtins.int, void]' @@ -444,7 +444,7 @@ from typing import AsyncIterator async def gen() -> AsyncIterator[int]: yield 3 - yield 'not an int' + yield 'not an int' # E: Incompatible types in yield (actual type "str", expected type "int") async def use_gen() -> None: async for item in gen(): @@ -543,18 +543,18 @@ from typing import AsyncGenerator async def return_int() -> AsyncGenerator[int, None]: yield 1 - return 42 # E: No return value expected + return 42 # E: 'return' with value in async generator is not allowed async def return_none() -> AsyncGenerator[int, None]: yield 1 - return None + return None # E: 'return' with value in async generator is not allowed def f() -> None: return async def return_f() -> AsyncGenerator[int, None]: yield 1 - return f() + return f() # E: 'return' with value in async generator is not allowed [builtins fixtures/dict.pyi] From f193e42024453295ffa85232b3c039572147da28 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 18 Mar 2017 10:18:34 -0700 Subject: [PATCH 10/11] fix obsolete test --- test-data/unit/check-async-await.test | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index d9c12096ebdb4..228d2cb786130 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -326,18 +326,15 @@ async def f() -> None: [builtins fixtures/async_await.pyi] [case testNoYieldInAsyncDef] +# flags: --python-version 3.5 async def f(): - yield None + yield None # E: 'yield' in async function async def g(): - yield + yield # E: 'yield' in async function async def h(): - x = yield + x = yield # E: 'yield' in async function [builtins fixtures/async_await.pyi] -[out] -main:3: error: 'yield' in async function -main:5: error: 'yield' in async function -main:7: error: 'yield' in async function [case testNoYieldFromInAsyncDef] From dc0206c8fc6822b120c2f1fa1cb2f7073256518e Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 18 Mar 2017 12:06:38 -0700 Subject: [PATCH 11/11] add test suggested by Jukka --- test-data/unit/check-async-await.test | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-async-await.test b/test-data/unit/check-async-await.test index 228d2cb786130..ebae58ee6b1d9 100644 --- a/test-data/unit/check-async-await.test +++ b/test-data/unit/check-async-await.test @@ -412,7 +412,7 @@ def f() -> Generator[int, str, int]: [case testAsyncGenerator] # flags: --python-version 3.6 -from typing import AsyncGenerator +from typing import AsyncGenerator, Generator async def f() -> int: return 42 @@ -432,6 +432,9 @@ async def h() -> None: async for item in g(): reveal_type(item) # E: Revealed type is 'builtins.int*' +async def wrong_return() -> Generator[int, None, None]: # E: The return type of an async generator function should be "AsyncGenerator" or one of its supertypes + yield 3 + [builtins fixtures/dict.pyi] [case testAsyncGeneratorReturnIterator]