python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.18k stars 2.77k forks source link

mypy 0.750 regression with asyncio.wait #8051

Closed srittau closed 4 years ago

srittau commented 4 years ago

Please consider the following:

from asyncio import Future, wait
from typing import List

async def foo() -> None:
    f: List[Future[None]] = []
    await wait(f)

Checking this with mypy 0.740 (Python 3.8.0) without options succeeds. Checking it with mypy 0.750 (also Python 3.8.0, no options) prints the following error:

foo.py:6: error: Argument 1 to "wait" has incompatible type "List[Future[None]]"; expected "Iterable[Union[Future[<nothing>], Generator[Any, None, <nothing>], Awaitable[<nothing>]]]"
Found 1 error in 1 file (checked 1 source file)

The annotation of asyncio.wait() from typeshed has not changed between mypy versions:

_T = TypeVar('_T')
...
_FutureT = Union[Future[_T], Generator[Any, None, _T], Awaitable[_T]]
...
def wait(fs: Iterable[_FutureT[_T]], *, loop: Optional[AbstractEventLoop] = ..., timeout: Optional[float] = ...,
         return_when: str = ...) -> Future[Tuple[Set[Future[_T]], Set[Future[_T]]]]: ...
ilevkivskyi commented 4 years ago

FWIW this also fails on master and Python 3.7. I wasn't able to find a simple repro not-involving async/await.

ilevkivskyi commented 4 years ago

Here I have a simple repro:

from typing import Union, Generic, TypeVar

T = TypeVar('T')
T_co = TypeVar('T_co', covariant=True)

class Cov(Generic[T_co]): ...
class Inv(Cov[T]): ...

X = Union[Cov[T], Inv[T]]

def f(x: X[T]) -> T: ...
x: Inv[int]
f(x)

This test passes on v0.740, but fails on v0.750. The reason is subtle, but ultimately it is related to switch to the new type alias representation. The point is that expand_type() (called from freshen_function_type_vars()) calls make_simplified_union() when visiting union types, while type alias to union stays "unsimplified". Then later constraint inference fails, because it is generally more tricky for union types. I see two possible solutions here:

I am leaning towards first option. Also I don't really want to call get_proper_type() in expand_type() to trigger union simplification, since that looks a bit unprincipled and potentially dangerous.

JukkaL commented 4 years ago

I agree that using make_simplified_union in infer_constraints() sounds like the best approach. Type inference shouldn't be affected by whether union types have bee simplified.