Taking a slice of a class inherited from a tuple sometimes ignores the custom typing of the __getitem__ method
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 15.6k
- Forks
- 1.8k
- Avg merge
- 12h 13m
- Merged PRs (30d)
- 52
Description
Tested using the [Pyright Playground](https://pyright-play.net/?code=GYJw9gtgBALgngBwJYDsDmUkQWEMoCSMApiAIYBGANsQDRQDKxVw9DArgjngM4EoATYgA96YAG6kqYMgIBQCgMRQAgrE40oYYJhQweUAO5IYACygBjdjxiQowdigtIwKMlRNw5FqmR4H%2BGAAVDWIAChhQgG1UGHoAOkSAXQBKAC45KCyoAAEJKRl5bKghHQB9MpRiQwqwnx4UqABaAD5GZmA0qET4hWK8yRBpWUzs0qgKqpqyuqoeehNSShouoiXqYhi9VOa2phYunr6x4nLK6tr6hZJyDYBeMJT00eKskGJIkBQoHk5SMMCIQQNHo9RS8UmFxmV3UwPCi1uNCexyyAwKI2K4wqaA%2BiwgtR4HXoAGtiHAuhwuLh9PwhMIoAAfXQwRqtZmHRIo3L5IaFF4lU4TMo4mB4glEqCk8k-DwWYistoAIkBoUVHN6-KxwtxJHxM0JLBJZOer2ySB0SB4qBsZCc4Sl9DClO4NMEIgWeieGVNpven2%2BvwQ-3B2J1xD1YSljWUfvYX1giGIXViXOK5swVpQNrtkbJ9B4svl3p9xVj8ZVcLCgeDEO1ot1tSj0agZe%2B8CDqz0QJoqey5EtxCgQUTAFEQOAQGFlShxO4kAJJWTFSkuco1CIyNhNNofpBB1YbHYKMRTGRxC4QFodGZBxXND4-Dx%2BTkEOAg3gvJjBWeljiqx0FXsYYYGLX0PjjAN2Agf8WEaAB6KAaBQGDgBXBQyCgO5CC7UIwjCABGegACZ6AAZmRORX1iMIyHiH9yBxNCKEwqAyCiNIkgUKi9DCCg6MGMhGLkIA)
Pyright version: `1.1.407`
Python version: `3.14`
---
Consider the following code which defines a custom class inherited from a tuple:
```python
from typing import Iterable, Self, SupportsIndex, overload
# A tuple of ints with custom funcionality
class IntTuple(tuple[int, ...]):
@overload
def __new__(cls) -> Self: ...
@overload
def __new__(cls, iterable: Iterable[int]) -> Self: ...
def __new__(cls, iterable=()):
return super(IntTuple, cls).__new__(cls, tuple(iterable))
@overload
def __getitem__(self, key: SupportsIndex | int) -> int: ...
@overload
def __getitem__(self, key: slice) -> "IntTuple": ...
def __getitem__(self, key):
if isinstance(key, (SupportsIndex, int)):
return super().__getitem__(key) # return type: int
if isinstance(key, slice):
return IntTuple(super().__getitem__(key)) # return type: IntTuple
raise TypeError("Invalid key")
# A example of some custom behavior of the IntTuple class
@property
def average(self) -> float:
return sum(self) / len(self)
```
With this custom class in place if we let pyright analyze the following code it will conclude that there is a problem, even though the code runs without issues and produces the expected output:
```python
a = IntTuple((1, 2, 3))
print(a.average)
# Output: 2.0
b = a[:]
print(b.average) # Cannot access attribute "average" for class "tuple[int, ...]"
# Output: 2.0
```
Additionally, here's all the cases I've found:
```python
a[:] # Cannot access attribute "average" for class "tuple[int, ...]"
a[0:] # Cannot access attribute "average" for class "tuple[int, ...]"
a[:0] # Cannot access attribute "average" for class "tuple[()]"
a[1:] # Any number other than 0 works
a[:1] # Any number other than 0 works
a[slice(...)] # Any explicit use of slice works
```
In summary, it seems that pyright when slicing a inherited tuple will sometimes default to the typing of the base class (in this case `tuple` which becomes `tuple[int, ...]`) instead of the typing as defined by the explicit overload:
```python
@overload
def __getitem__(self, key: slice) -> "IntTuple": ...
```
Because it assumes the base `tuple` class and not the `IntTuple` class it thinks the attribute `average` does not exist in a `tuple[int, ...]` even though the code correctly returns a valid `IntTuple`.
Had the overload not been present it may have been to correct to assume a return type of `tuple[int, ...]` or possibly `tuple[Unknown]`.
I've tried the same code snippet in a [mypy playground](https://mypy-play.net/?mypy=1.18.2&python=3.14&gist=e86b6c30e600e692d29a98c57c68a0e0) and it found no issues.
As a final aside, I've also tried slicing a class inherited from a list and the inconsistencies described above did not manifest, suggesting this is a bug exclusive to inherited tuples.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the reported cases in the linked Pyright Playground, starting with the IntTuple.__getitem__ overload for slice and the expressions a[:], a[0:], a[:0], and a[slice(...)]. Compare the inferred types with the runtime IntTuple results; done means slicing the tuple-derived class consistently preserves the declared IntTuple return type and its average attribute without errors.
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
- Clearly specified
- Newbie friendliness
- 45/100