Skip to content

Never inferred in complex situation with variadic callable protocol #16522

Open

Description

@TeamSpen210

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

Activity

  1. ilevkivskyi commented on Nov 22, 2023

    @ilevkivskyi
    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.

  2. Zac-HD commented on Jun 25, 2024

    @Zac-HD
    Contributor

    @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!

  3. ilevkivskyi commented on Jun 25, 2024

    @ilevkivskyi
    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.

  4. Zac-HD commented on Jun 25, 2024

    @Zac-HD
    Contributor

    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrongtopic-pep-646PEP 646 (TypeVarTuple, Unpack)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions