microsoft / microsoft/pyright

Taking a slice of a class inherited from a tuple sometimes ignores the custom typing of the __getitem__ method

Open
#11,144 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
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

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

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.