Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

asynccontextmanager with TypeVar leads to new warnings (regression) #16451

Closed
DanielNoord opened this issue Nov 10, 2023 · 3 comments · Fixed by #16461
Closed

asynccontextmanager with TypeVar leads to new warnings (regression) #16451

DanielNoord opened this issue Nov 10, 2023 · 3 comments · Fixed by #16461

Comments

@DanielNoord
Copy link

Bug Report

Playground link:
https://mypy-play.net/?mypy=latest&python=3.11&gist=87e38e6e3483050ab69494a7086129cf

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import TypeVar

T = TypeVar("T")


@asynccontextmanager
async def func(arg: T) -> AsyncIterator[T | str]:
    if arg is None:
        yield arg
    else:
        yield ""


async def func2() -> None:
    async with func(arg="") as x:
        print(x)

This raises the new errors on master/1.7, but not on 1.6.1.

You can check it on the playground.

Running with --old-type-inference makes the errors go away so I'm tagging @ilevkivskyi as the blog post said they were involved in this new feature. Sorry if this is not appropriate.

Expected Behavior

No errors as I don't think is wrong. pyright also seems to think this is fine.

Actual Behavior

main.py:8: error: Argument 1 to "asynccontextmanager" has incompatible type "Callable[[T], AsyncIterator[T | str]]"; expected "Callable[[VarArg(Never), KwArg(Never)], AsyncIterator[Never]]"  [arg-type]
main.py:17: error: Need type annotation for "x"  [var-annotated]
main.py:17: error: Argument "arg" to "func" has incompatible type "str"; expected Never  [arg-type]
Found 3 errors in 1 file (checked 1 source file)

Your Environment

master

@DanielNoord DanielNoord added the bug mypy got something wrong label Nov 10, 2023
@DanielNoord DanielNoord mentioned this issue Nov 10, 2023
2 tasks
@Redoubts
Copy link

Redoubts commented Nov 10, 2023

I'm seeing something similar too, with a device like this:

from __future__ import annotations

from contextlib import asynccontextmanager
from typing import AsyncIterable

from typing_extensions import Self


class A:
    @classmethod
    @asynccontextmanager
    async def x(cls) -> AsyncIterable[Self]:
        yield cls()


class B(A):
    @classmethod
    @asynccontextmanager
    async def x(cls) -> AsyncIterable[Self]:
        async with super().x() as self:
            yield self
% mypy x.py
x.py:10: error: Argument 1 to "asynccontextmanager" has incompatible type "Callable[[type[Self]], AsyncIterable[Self]]"; expected "Callable[[type[Never]], AsyncIterator[Never]]"  [arg-type]
x.py:17: error: Argument 1 to "asynccontextmanager" has incompatible type "Callable[[type[Self]], AsyncIterable[Self]]"; expected "Callable[[type[Never]], AsyncIterator[Never]]"  [arg-type]
x.py:20: error: Need type annotation for "self"  [var-annotated]
Found 3 errors in 1 file (checked 1 source file)

-e- remove kwargs, wrote too quick

@ilevkivskyi
Copy link
Member

A simpler repro from another issue:

import typing_extensions as t

T = t.TypeVar("T")
deco: t.Callable[[t.Callable[..., T]], t.Callable[..., T]]

@deco  # mypy: Argument 1 has incompatible type "Callable[[T], T | int]"; expected "Callable[..., Never]" [arg-type]
def func(item: T, /) -> T | int:
    return 0

I actually have been working on this yesterday, and this is quite tricky: inference for unions is quite ad-hoc and it is not easy to adapt it to new inference algorithm.

@Redoubts you problem is different: mypy is correct, you should use AsyncIterator instead (iterable and iterator are not the same).

@Redoubts
Copy link

Sorry, I was in a rush to minimize my example. This looks similarly real tho:

from __future__ import annotations

from contextlib import asynccontextmanager
from typing import AsyncIterator

from typing_extensions import Self


class A:
    @classmethod
    @asynccontextmanager
    async def x(cls) -> AsyncIterator[Self]:
        yield cls()


class B(A):
    @classmethod
    @asynccontextmanager
    async def x(cls) -> AsyncIterator[Self]:
        async with super().x() as self:
            yield self
% mypy x.py                            
x.py:19: error: Return type "_AsyncGeneratorContextManager[_T_co]" of "x" incompatible with return type "_AsyncGeneratorContextManager[_T_co]" in supertype "A"  [override]
x.py:21: error: Incompatible types in "yield" (actual type "_T_co", expected type "Self")  [misc]
Found 2 errors in 1 file (checked 1 source file)

toofar added a commit to qutebrowser/qutebrowser that referenced this issue Nov 13, 2023
I believe we are being afflicted by this issue: python/mypy#16451
Although I'm not 100% sure because there is a lot going on in this
function and I haven't managed to grok it.

The mypy 1.7 release [notes][1.7] say you can disable the new type
inference by running `tox -e mypy-pyqt6 -- --old-type-inference` and
indeed mypy passes with that. So either our type hints are incorrect or
we are hitting a bug. Considering the inferred type hint has a `Never`
in it I'm leading toward it being a bug. So I'll bump the mypy version
down and hopefully next week the issue will be resolved.

The mypy output before this commit was:

    mypy-pyqt6: commands[0]> .tox/mypy-pyqt6/bin/python -m mypy --always-true=USE_PYQT6 --always-false=USE_PYQT5 --always-false=USE_PYSIDE6 --always-false=IS_QT5 --always-true=IS_QT6 --always-true=IS_PYQT --always-false=IS_PYSIDE qutebrowser
    qutebrowser/utils/qtutils.py:239: error: Argument 1 to "contextmanager" has incompatible type
    "Callable[[str, bool, str], Iterator[IO[AnyStr]]]"; expected "Callable[[str, bool, str], Iterator[IO[Never]]]"  [arg-type]
        @contextlib.contextmanager
         ^
    qutebrowser/misc/lineparser.py: note: In member "save" of class "LineParser":
    qutebrowser/misc/lineparser.py:168: error: Need type annotation for "f"  [var-annotated]
                    with qtutils.savefile_open(self._configfile, self._binary) as f:
                         ^
    qutebrowser/misc/lineparser.py: note: In member "save" of class "LimitLineParser":
    qutebrowser/misc/lineparser.py:226: error: Need type annotation for "f"  [var-annotated]
                with qtutils.savefile_open(self._configfile, self._binary) as f:
                     ^
    qutebrowser/config/configfiles.py: note: In member "_save" of class "YamlConfig":
    qutebrowser/config/configfiles.py:292: error: Need type annotation for "f"  [var-annotated]
                with qtutils.savefile_open(self._filename) as f:
                     ^
    qutebrowser/misc/sessions.py: note: In member "save" of class "SessionManager":
    qutebrowser/misc/sessions.py:343: error: Need type annotation for "f"  [var-annotated]
                    with qtutils.savefile_open(path) as f:

[1.7]: https://mypy-lang.blogspot.com/2023/11/mypy-17-released.html
ilevkivskyi added a commit that referenced this issue Nov 13, 2023
Fixes #16451

This special-casing is unfortunate, but this is the best I came up so
far.
daerich pushed a commit to daerich/qutebrowser that referenced this issue Nov 16, 2023
I believe we are being afflicted by this issue: python/mypy#16451
Although I'm not 100% sure because there is a lot going on in this
function and I haven't managed to grok it.

The mypy 1.7 release [notes][1.7] say you can disable the new type
inference by running `tox -e mypy-pyqt6 -- --old-type-inference` and
indeed mypy passes with that. So either our type hints are incorrect or
we are hitting a bug. Considering the inferred type hint has a `Never`
in it I'm leading toward it being a bug. So I'll bump the mypy version
down and hopefully next week the issue will be resolved.

The mypy output before this commit was:

    mypy-pyqt6: commands[0]> .tox/mypy-pyqt6/bin/python -m mypy --always-true=USE_PYQT6 --always-false=USE_PYQT5 --always-false=USE_PYSIDE6 --always-false=IS_QT5 --always-true=IS_QT6 --always-true=IS_PYQT --always-false=IS_PYSIDE qutebrowser
    qutebrowser/utils/qtutils.py:239: error: Argument 1 to "contextmanager" has incompatible type
    "Callable[[str, bool, str], Iterator[IO[AnyStr]]]"; expected "Callable[[str, bool, str], Iterator[IO[Never]]]"  [arg-type]
        @contextlib.contextmanager
         ^
    qutebrowser/misc/lineparser.py: note: In member "save" of class "LineParser":
    qutebrowser/misc/lineparser.py:168: error: Need type annotation for "f"  [var-annotated]
                    with qtutils.savefile_open(self._configfile, self._binary) as f:
                         ^
    qutebrowser/misc/lineparser.py: note: In member "save" of class "LimitLineParser":
    qutebrowser/misc/lineparser.py:226: error: Need type annotation for "f"  [var-annotated]
                with qtutils.savefile_open(self._configfile, self._binary) as f:
                     ^
    qutebrowser/config/configfiles.py: note: In member "_save" of class "YamlConfig":
    qutebrowser/config/configfiles.py:292: error: Need type annotation for "f"  [var-annotated]
                with qtutils.savefile_open(self._filename) as f:
                     ^
    qutebrowser/misc/sessions.py: note: In member "save" of class "SessionManager":
    qutebrowser/misc/sessions.py:343: error: Need type annotation for "f"  [var-annotated]
                    with qtutils.savefile_open(path) as f:

[1.7]: https://mypy-lang.blogspot.com/2023/11/mypy-17-released.html
JukkaL pushed a commit that referenced this issue Nov 22, 2023
Fixes #16451

This special-casing is unfortunate, but this is the best I came up so
far.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants