Skip to content

Implement async generators (PEP 525) - #2711

Merged
JukkaL merged 11 commits into
python:masterfrom
JelleZijlstra:asyncgen
Mar 18, 2017
Merged

JukkaL merged 11 commits into
python:masterfrom
JelleZijlstra:asyncgen

Conversation

@JelleZijlstra

@JelleZijlstra JelleZijlstra commented Jan 19, 2017 •

Copy link
Copy Markdown
Member

This turned out to be pretty straightforward.

Two issues that may need more discussion:

  • 3.6.0 was released without typing.AsyncGenerator; it was added in add AsyncGenerator typing#346 and should be in 3.6.1. But this patch assumes that typing.AsyncGenerator exists, so if you run mypy in 3.6.0 on a file with an async generator, it says that typing.AsyncGenerator doesn't exist. I originally put AsyncGenerator in mypy_extensions to work around this, but maybe we need some other solution.
  • PEP 525 says that it's a RuntimeError to yield inside a finally block in an async generator, but that doesn't appear to be true for 3.6.0 as implemented, so I didn't add a check in mypy for this.

This closes #2616

@JelleZijlstra JelleZijlstra changed the title Implement async generators (PEP 525) [WIP] Implement async generators (PEP 525) Jan 19, 2017
@JelleZijlstra JelleZijlstra changed the title [WIP] Implement async generators (PEP 525) Implement async generators (PEP 525) Jan 19, 2017
@gvanrossum

Copy link
Copy Markdown
Member
  • Re: the absence of typing.AsyncGenerator in Python 3.6.0, I don't understand why that would cause an internal error in mypy, since mypy doesn't look up types needed for type checking the target program in the runtime typing.py module -- it looks in typeshed. Assuming that what you're really seeing is what happens when typeshed doesn't have typing.AsyncGenerator (assuming mypy --python-version 3.6 is used), we could just guard for that by syncing the typeshed submodule past the commit that has it. Or you could check whether the lookup failed and report an error instead.

  • I don't quite follow your second bullet (maybe you can show a complete test program with expected and actual behavior?) but @1st1 should be able to help you understand whatever it is you are seeing.

@1st1

1st1 commented Jan 19, 2017

Copy link
Copy Markdown
Member

PEP 525 says that it's a RuntimeError to yield inside a finally block in an async generator, but that doesn't appear to be true for 3.6.0 as implemented, so I didn't add a check in mypy for this.

It's a RuntimeError when you yield inside a finally block during AG finalization, i.e. when its .aclose() method is called or when it is GCed. An exact same rule exists for regular synchronous generators.

@1st1

1st1 commented Jan 19, 2017

Copy link
Copy Markdown
Member

IOW it's OK to have yield expressions in finally blocks in asynchronous generators, and I don't think mypy should care about that.

@JelleZijlstra

Copy link
Copy Markdown
Member Author

Thanks for clarifying the yield/finally issue! An earlier version of my patch did have an internal error when typing.AsyncGenerator didn't exist, but that is fixed now (I amended my message above yesterday to reflect that). People using 3.6.0 will just have to guard their from typing import AsyncGenerator in if TYPE_CHECKING:.

In other words, both of the issues I flagged should be resolved.

@ilevkivskyi

Copy link
Copy Markdown
Member

@JelleZijlstra What is the status of this PR? I am asking just to see whether it would be possible to have full Python 3.6 support in 0.480 (that should be released in around one week if I understand correctly the current schedule).

@JelleZijlstra

Copy link
Copy Markdown
Member Author

@ilevkivskyi I think the code is ready, but it hasn't been reviewed yet. Also, there is a merge conflict now—I'll fix that.

@ilevkivskyi ilevkivskyi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mostly looks good, here are some minor comments.

Comment thread mypy/checker.py
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)):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't comment there, but you might update the comment after elif return_type.args: below (add AsyncGenerator).

Comment thread mypy/checker.py Outdated
return

if self.is_unusable_type(return_type):
if self.is_unusable_type(return_type) or defn.is_async_generator:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer a separate (more clear) error message for async generators, like: "return statement with value in async generator is prohibited". Maybe it could be even blocking (since formally speaking this is a SyntaxError).

Also, I would probably move this check to semanal.py.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think putting this in semanal.py would be hard because we only discover that the function is a generator during semantic analysis. I suppose we could do it in ThirdPass, but there are some code comments suggesting that ThirdPass may be removed in the future.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(But I'll change the error message.)

Comment thread test-data/unit/check-async-await.test Outdated

async def gen() -> AsyncGenerator[int, None]:
yield 1
return 42 # E: No return value expected

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add more tests here, both return None and return f() (for f that returns None) should fail (both with and without --strict-optional), since these are syntax errors.

Comment thread test-data/unit/check-async-await.test Outdated
-- ---------------------------------------------------------------------

[case testAsyncGenerator]
# flags: --fast-parser --python-version 3.6

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--fast-parser flag is not necessary anymore.

Comment thread mypy/semanal.py
self.fail("'yield' in async function", expr, True, blocker=True)
else:
self.function_stack[-1].is_generator = True
self.function_stack[-1].is_async_generator = True

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not important, but it looks a bit strange that you need to set both flags here.

async def f() -> AsyncGenerator[int, None]:
pass

async def gen() -> AsyncGenerator[int, None]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add some tests with AsyncIterator[int]?


async def genfunc() -> AsyncGenerator[int, None]:
yield 1
yield 2

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also add a test where you yield a wrong type?

@JelleZijlstra

Copy link
Copy Markdown
Member Author

Thanks for the comments! I will have to rebase the PR because it now relies on an obsolete version of typed_ast.

@JukkaL JukkaL left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM -- only one idea for an additional test case.

Comment thread mypy/messages.py
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 = \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There doesn't seem to be a test for this error message. Can you add one?

@JukkaL
JukkaL merged commit baadb04 into python:master Mar 18, 2017
@JukkaL

JukkaL commented Mar 18, 2017

Copy link
Copy Markdown
Collaborator

🎉

@JelleZijlstra
JelleZijlstra deleted the asyncgen branch May 4, 2017 04:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for PEP 525 -- Asynchronous Generators

5 participants