False positive `InconsistentOverload` with generic class of tuple argument
- Dominant language
- Python
- Stars
- 15.6k
- Forks
- 1.8k
- Avg merge
- 12h 13m
- Merged PRs (30d)
- 52
Description
The example below type checks both with `mypy` and `pyrefly`, but for `pyright` we get the following error message on the last `def fork[X, Y]`:
```
error: Overloaded implementation is not consistent with signature of overload 3
Function return type "Fork[X@fork, tuple[Y1@fork, Y2@fork]]" is incompatible with type "Fork[X@fork, tuple[Y1@fork, ...]]"
"Fork[X@fork, tuple[Y1@fork, Y2@fork]]" is not assignable to "Fork[X@fork, tuple[Y1@fork, ...]]"
Type parameter "TupleOut@Fork" is covariant, but "tuple[Y1@fork, Y2@fork]" is not a subtype of "tuple[Y1@fork, ...]"
"tuple[Y1@fork, Y2@fork]" is not assignable to "tuple[Y1@fork, ...]"
Tuple entry 1 is incorrect type (reportInconsistentOverload)
```
I believe this is a bug, because:
1. The type ``Fork[X@fork, tuple[Y1@fork, ...]]`` does not make sense. Why does only `Y1` appear but not `Y2`?
2. If this were incorrect, why does it not complain already for the 4th overload, which has identical signature to the implementation?
3. What we should have is that the solver considers `tuple[Y1, Y2]` as a subtype of `tuple[Y, ...]`, where `Y1` and `Y2` come from the 3rd overload and `Y` is the type var bound to the implementation. But that just gives the constraint `Y1 <: Y` and `Y2 <: Y`, so `Y = Y1 | Y2` is a solution.
Code sample in [pyright playground](https://pyright-play.net/?pyrightVersion=1.1.405&pythonVersion=3.13&code=GYJw9gtgBALgngBwJYDsDmUkQWEMoCCKcANFAJIwCmIAhgEYA2VZAxrQM4xlgBuNjMLQAmAKFGtGnDlAAqdFB2C4IAbQAaZAJoBdAFyioRqMKrBYCpSoAUHKo2BkAHnqiaoAegCUUALQA%2BKC1XADow8UlpKAAxXABrDTJZAFcEZgB5ZJhXGFTmHWt5WkVlEDV3FLSqTJgdLwNjKBAAIlaCBDS4KAhkxhgkKotiqzKZGDBYAAsqKA5aCBnUBCyoYuEmqlyQFFXYPJmwczAs5ZgOENbm8UbTcwB9O9QkGAfVAFUyADUCw0bjOwcrmasRACQ%2Beyqqk%2BZDCIR0OmaZBgllKEA4rkoNAYzFURRKKneX3hZA8vyMPgCUAAcmAUFQGn8jADgCFkcNUeiITi8SNymQiHAdDCwjooABeLlUaxs-GjLzXYy3IayiC2eyOKAuNwkimBSoZLIMxkgTbJbZQdhcazNfXVLKIyXS1komxOHylWCYHbM53slQcLzy0QAAT4AiE6ygAGIoCgxQAGURK0oJdRigUFXUxeKJSWqaxeeGhcKh-ggQQiIwxuMARiTZigKdzums9LkLrKzaFnizINTSP2ql0%2BigsJDYfLEarsbFACZ6%2BYm%2B4tDXtLOClQa64eajmzXu1RZ9uO3yguudX5An3c7lISu10XRyWJxXI9X-PPkznlwUAFRUTkdwJH9e2-Ac72FOERzHL9QS7axf1oEA0EAk8u1AuD3FvHEtEgosyQ2LYdj7awkJQ%2BUgA)
```python
from typing import Any, Iterable, cast, overload
class Transform[X, Y]:
def transform(self, x: X, /) -> Y: ...
class Fork[X, TupleOut: tuple](Transform[X, TupleOut]):
r"""Apply multiple transforms to the same input and return a tuple of outputs."""
def __init__[U, V](
self: "Fork[U, tuple[V, ...]]", transforms: Iterable[Transform[U, V]], /
) -> None:
self.transforms: tuple[Transform[X, Any], ...] = tuple(transforms)
def transform(self, x: X, /) -> TupleOut:
return cast("TupleOut", tuple(t.transform(x) for t in self.transforms))
@overload # n=0
def fork[X=Any]() -> Fork[X, tuple[()]]: ...
@overload # n=1
def fork[X, Y](e: Transform[X, Y], /) -> Fork[X, tuple[Y]]: ...
@overload # n=2
def fork[X, Y1, Y2](e1: Transform[X, Y1], e2: Transform[X, Y2], /) -> Fork[X, tuple[Y1, Y2]]: ...
@overload # n>2
def fork[X, Y](*es: Transform[X, Y]) -> Fork[X, tuple[Y, ...]]: ...
def fork[X, Y](*args: Transform[X, Y]) -> Fork[X, tuple[Y, ...]]:
return Fork(args)
```
Contributor guide
Research direction
Start by reproducing the overload-checking error in the linked pyright playground using the provided Python sample, then trace how the third overload is compared with the implementation signature. Done means the valid example no longer reports InconsistentOverload while the overload consistency checks still handle the other overloads correctly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100