python / python/mypy

Regression since mypy 1.7 with functions that return a generic protocol

Open
#17,191 3 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

EDIT: I found a simpler example reproducing the regression, see below

The following code used to pass with mypy 1.6.1

from typing import (
    Protocol,
    TypeVar,
    Callable,
    Concatenate,
    ParamSpec,
    reveal_type,
)


X = TypeVar("X")
Y = TypeVar("Y")
P = ParamSpec("P")
I = TypeVar("I", contravariant=True)
O = TypeVar("O", covariant=True)


# A callable converting I to O with parameters P
ConvertorCallable = Callable[Concatenate[I, P], O]


# An object that can convert I to O with parameters P
class ConvertorProtocol(Protocol[I, P, O]):
    def convert(self, __source: I, *args: P.args, **kwargs: P.kwargs) -> O:
        ...


# Decorator to convert a callable to a convertor
def convertor(
    func: ConvertorCallable[X, P, Y],
) -> ConvertorProtocol[X, P, Y]:
    class Convertor:
        def convert(self, source: X, /, *args: P.args, **kwargs: P.kwargs) -> Y:
            return func(source, *args, **kwargs)

    return Convertor()


# A convertor that converts X to a list of X
@convertor
def as_list(source: X, repeat: int = 1) -> list[X]:
    return [source] * repeat


if __name__ == "__main__":
    result = as_list.convert(1, repeat=3)
    reveal_type(result)
    print(result)

However, it no longer passes with mypy 1.7.0 and later:

error: Argument 1 to "convert" of "ConvertorProtocol" has incompatible type "int"; expected Never  [arg-type]

Try it in the playground.

Note that this sample works in the pyright playground.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the self-contained reproducer in the issue and run it against mypy 1.6.1 and 1.7.0 or later to confirm the regression. Trace generic Protocol inference involving ParamSpec, TypeVar, and Concatenate; done means the example no longer reports Never for the argument and the regression is covered.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.