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

Never inferred in complex situation with variadic callable protocol #16522

Open
TeamSpen210 opened this issue Nov 19, 2023 · 4 comments · May be fixed by #17512
Open

Never inferred in complex situation with variadic callable protocol #16522

TeamSpen210 opened this issue Nov 19, 2023 · 4 comments · May be fixed by #17512
Labels
bug mypy got something wrong topic-pep-646 PEP 646 (TypeVarTuple, Unpack)

Comments

@TeamSpen210
Copy link
Contributor

Bug Report

Mypy is inferring a Never in a bit of a complicated setup involving TypeVarTuple, __call__ protocols and a regular TypeVar. If an annotated assignment is present it is able to check that they match. But when a bare call is done, it infers Never and always produces an error. This is a simplified version of Trio's Nursery.start() async spawn method, which I'm trying to type. (See src/trio/_core/_run.py.) I simplified it a little, removing an overload and making it synchronous.

To Reproduce

from typing import Generic, TypeVar, Protocol
from typing_extensions import TypeVarTuple, Unpack

PosArgT = TypeVarTuple("PosArgT")
StatusT = TypeVar("StatusT")
StatusT_co = TypeVar("StatusT_co", covariant=True)
StatusT_contra = TypeVar("StatusT_contra", contravariant=True)

class TaskStatus(Generic[StatusT_contra]):
    def started(self, value: StatusT_contra) -> None: ...

class NurseryStartFunc(Protocol[Unpack[PosArgT], StatusT_co]):
    def __call__(
        self,
        *args: Unpack[PosArgT],
        task_status: TaskStatus[StatusT_co],
    ) -> object: ...

def nursery_start(
    async_fn: NurseryStartFunc[Unpack[PosArgT], StatusT],
    *args: Unpack[PosArgT],
) -> StatusT:  ...

def task(a: int, b: str, *, task_status: TaskStatus[set[str]]) -> None: ...

def test() -> None:
    a_task: NurseryStartFunc[int, str, set[str]] = task  # task implements the protocol
    result: set[str] = nursery_start(task, 1, "b")  # If result is annotated this works.
    result = nursery_start(task, "a", 2)  # Detects invalid call correctly
    # Without assignment or if unnanotated, infers Never as return type?
    nursery_start(task, 1, "b")

https://mypy-play.net/?mypy=latest&python=3.11&gist=31be4208e294eb6c5cbc7390f4a836f4

Expected Behavior

Ideally Mypy would be able to propagate the typevar in TaskStatus to determine the return type, and verify that appropriate types were passed for *args.

Actual Behavior

(Last two statements)
main.py:39: error: Argument 1 to "nursery_start" has incompatible type "Callable[[int, str, NamedArg(TaskStatus[set[str]], 'task_status')], None]"; expected "NurseryStartFunc[str, int, set[str]]"  [arg-type]
main.py:41: error: Argument 1 to "nursery_start" has incompatible type "Callable[[int, str, NamedArg(TaskStatus[set[str]], 'task_status')], None]"; expected "NurseryStartFunc[int, str, Never]"  [arg-type]

The first error is correct, showing that Mypy can understand the types somewhat. But in the second case it's strangely producing Never, when it should really be effectively Any - the type is unused so it doesn't matter what it is.

Your Environment

  • Mypy version used: 1.7.0, also tried master
  • Mypy command-line flags: None
  • Mypy configuration options from mypy.ini (and other config files): None
  • Python version used: 3.8, 3.11
@ilevkivskyi
Copy link
Member

This is surprisingly tedious to fix (since mixing TypeVarTuple and named arguments was not considered an important use case). I will therefore wait a bit until we have some more experience about common use cases for this pattern.

@Zac-HD
Copy link
Contributor

Zac-HD commented Jun 25, 2024

@ilevkivskyi any update on this?

I would really, really like Trio and AnyIO to give precise types for their .start() methods - currently that would work with everything except mypy. For now supporting mypy is winning out over improvements for all other users, but I'd rather not have to make that tradeoff!

@ilevkivskyi
Copy link
Member

OK, I will make this easier for you. You could have just said something like: "As a data point this use case is very important for Trio and AnyIO" and I would have gladly raised priority, but no, for some reason you are using except. I am therefore not going to work on this until this will be last open issue on the tracker.

@Zac-HD
Copy link
Contributor

Zac-HD commented Jun 25, 2024

I'm sorry Ivan; clearly I've miscommunicated pretty badly.

This issue is indeed pretty important to Trio, e.g. it's the last thing keeping our stubs package alive and that's all I meant by "except". Supporting mypy matters enough to me to keep paying that maintenance cost if needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-pep-646 PEP 646 (TypeVarTuple, Unpack)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants