Skip to content
New issue

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

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

Already on GitHub? # 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.

# for free to join this conversation on GitHub. Already have an account? # 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