Allow yielding result of function that returns None

Open
#1,933 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Feature
Clarity
Needs clarification
Activity status
Stale
Tech stack
python
Domain
compilers

Research direction

Start with the current-status comment linked in the issue, then inspect mypy/checker.py around the generator yield, receive, and return defaults and typeshed/stdlib/3/typing.pyi around Generator variance. Review PR #1808 and the disagreement over None, Void, and Any; done requires an agreed default behavior and corresponding type-checking coverage.

Written by the indexing model from the issue text.

Description

bug false-positive priority-1-normal

UPDATE: see below for current status https://github.com/python/mypy/issues/1933#issuecomment-390018636

This is a spin-off from PR #1808. @ddfisher and I cannot agree on what the default type should be in the three methods get_generator_yield_type(), get_generator_receive_type(), get_generator_return_type().

The setting: when the user defines a generator function, they can specify a return type. The return type is required to be a supertype of Generator; this constrains it to Iterator, Iterable, object (and Any, which is special in other ways).

The Generator type has three type parameters:

  • ty, the yield type: the type of y in yield y
  • tc, the receive type: the type of c in c = yield ...
  • tr, the return type: the type of r in return r

Note that tr is fairly new -- it was introduced in Python 3.3 by PEP 380 for the benefit of yield from. tc is older, it dates back to Python 2.5 and PEP 342 (it is the value sent to the generator using the .send() method). ty is the oldest; when yield was originally introduced in Python 2.2 by PEP 255, it was a statement. Before PEP 380, return in a generator function was not allowed to return a value.

When a generator function is just intended to be used as an iterator, there's no need to specify tc and tr, and the recommended type is Iterator[ty]. (Iterable[ty] is also allowed and means roughly the same thing.) In this case the user of the iterator has no access to the .send() method. Hence, yield might as well be considered a statement, since it's never going to return anything other than None. Likewise, there's no point is returning a value with a return statement since the user of the iterator in all likelihood is just going to iterate over it until it raises StopIteration (since that's all that regular iterators do).

This is where @ddfisher and I disagree -- I find it useful if the defaults for tc and tr are None (or perhaps Void), so that mypy will flag an error in the generator body if you either try to use yield ... as an expression, or try to return a value using return <expr>. David (IIUC) believes that both should be Any, giving the reason that "it should be allow to pointless but harmless things." (The code currently has tc default to Void and tr default to Any. Go figure.)

My problem with David's position is that I want mypy to be strict about pointless things that are likely the result of either a misunderstanding or a refactoring gone bad. I have seen enough people make the mistake of accidentally writing return v instead of yield v in a generator that I want this to be flagged as an error. There's also the somewhat analogous case of a regular function declared to return None -- it would be pointless but harmless to return some value here, so maybe David believes that we shouldn't flag that as an error either?

I'm not sure why the existing code (written by David) has Void as the default for tc, but I think it may be because this parameter of Generator is contravariant. So hopefully we at least agree here. :-)

David, please feel free to explain your position more fully (I think you were calling on the formal type system but we ran out of time in our in-person discussion at that point).

Dominant language
Python
Stars
20.6k
Forks
3.3k
Avg merge
1d 18h
Merged PRs (30d)
54

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from python/mypy

All issues in python/mypy

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.