Skip to content

Commit 0007c52

Browse files
authored
Rely on typeshed/stubs for slice typing (#21401)
Fixes #2410 `slice` has been generic in typeshed for more than a year now. Also all builtin collections have precise `slice[SupportsIndex | None]` types for `__getitem__()`. So, I don't think we need to be "policing" `slice` anymore, it should be really responsibility of the stub maintainers now. After some searching, I see there are still many plain `slice` annotations in the wild, so this will be a trade-off of false positives to false negatives, but I think this is a right trade-off, since status quo causes issues for pandas dataframes and similar APIs. I also considered some "intermediate" solutions, where we would still enforce `SupportsIndex` in _some_ situations, but can't find anything that wouldn't be overly complicated.
1 parent d091daa commit 0007c52

8 files changed

Lines changed: 20 additions & 24 deletions

File tree

mypy/checkexpr.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@
141141
fix_instance,
142142
has_any_from_unimported_type,
143143
instantiate_type_alias,
144-
make_optional_type,
145144
set_any_tvars,
146145
validate_instance,
147146
)
@@ -5860,17 +5859,10 @@ def _super_arg_types(self, e: SuperExpr) -> Type | tuple[Type, Type]:
58605859
return type_type, instance_type
58615860

58625861
def visit_slice_expr(self, e: SliceExpr) -> Type:
5863-
try:
5864-
supports_index = self.chk.named_type("typing_extensions.SupportsIndex")
5865-
except KeyError:
5866-
supports_index = self.chk.named_type("builtins.int") # thanks, fixture life
5867-
expected = make_optional_type(supports_index)
58685862
type_args = []
58695863
for index in [e.begin_index, e.end_index, e.stride]:
58705864
if index:
5871-
t = self.accept(index)
5872-
self.chk.check_subtype(t, expected, index, message_registry.INVALID_SLICE_INDEX)
5873-
type_args.append(t)
5865+
type_args.append(self.accept(index))
58745866
else:
58755867
type_args.append(NoneType())
58765868
return self.chk.named_generic_type("builtins.slice", type_args)

mypy/message_registry.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
9292
TOO_MANY_TARGETS_FOR_VARIADIC_UNPACK: Final = ErrorMessage(
9393
"Too many assignment targets for variadic unpack"
9494
)
95-
INVALID_SLICE_INDEX: Final = ErrorMessage("Slice index must be an integer, SupportsIndex or None")
9695
CANNOT_INFER_LAMBDA_TYPE: Final = ErrorMessage("Cannot infer type of lambda")
9796
CANNOT_ACCESS_INIT: Final = (
9897
'Accessing "__init__" on an instance is unsound, since instance.__init__ could be from'

test-data/unit/check-class-namedtuple.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ class Base(NamedTuple):
538538
# E: No overload variant of "__getitem__" of "tuple" matches argument type "TypeVar" \
539539
# N: Possible overload variants: \
540540
# N: def __getitem__(self, int, /) -> int \
541-
# N: def __getitem__(self, slice, /) -> tuple[int, ...]
541+
# N: def __getitem__(self, slice[int | None], /) -> tuple[int, ...]
542542
return self.x
543543
def bad_override(self) -> int:
544544
return self.x

test-data/unit/check-expressions.test

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,10 +1226,10 @@ o[:] # E: Value of type "object" is not indexable
12261226
from typing import Any
12271227
a: Any
12281228
o: object
1229-
a[o:1] # E: Slice index must be an integer, SupportsIndex or None
1230-
a[1:o] # E: Slice index must be an integer, SupportsIndex or None
1231-
a[o:] # E: Slice index must be an integer, SupportsIndex or None
1232-
a[:o] # E: Slice index must be an integer, SupportsIndex or None
1229+
a[o:1]
1230+
a[1:o]
1231+
a[o:]
1232+
a[:o]
12331233
[builtins fixtures/slice.pyi]
12341234

12351235
[case testSliceSupportsIndex]

test-data/unit/check-narrowing.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ weird_mixture: Union[KeyedTypedDict, KeyedNamedTuple]
438438
if weird_mixture["key"] is Key.B: # E: No overload variant of "__getitem__" of "tuple" matches argument type "str" \
439439
# N: Possible overload variants: \
440440
# N: def __getitem__(self, int, /) -> Literal[Key.C] \
441-
# N: def __getitem__(self, slice, /) -> tuple[Literal[Key.C], ...]
441+
# N: def __getitem__(self, slice[int | None], /) -> tuple[Literal[Key.C], ...]
442442
reveal_type(weird_mixture) # N: Revealed type is "TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]}) | tuple[Literal[__main__.Key.C], fallback=__main__.KeyedNamedTuple]"
443443
else:
444444
reveal_type(weird_mixture) # N: Revealed type is "TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]}) | tuple[Literal[__main__.Key.C], fallback=__main__.KeyedNamedTuple]"

test-data/unit/check-tuples.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,7 +1435,7 @@ reveal_type(t[x]) # N: Revealed type is "builtins.int | builtins.str"
14351435
t[y] # E: No overload variant of "__getitem__" of "tuple" matches argument type "str" \
14361436
# N: Possible overload variants: \
14371437
# N: def __getitem__(self, int, /) -> int | str \
1438-
# N: def __getitem__(self, slice, /) -> tuple[int | str, ...]
1438+
# N: def __getitem__(self, slice[int | None], /) -> tuple[int | str, ...]
14391439

14401440
[builtins fixtures/tuple.pyi]
14411441

@@ -1444,7 +1444,7 @@ t = (0, "")
14441444
x = 0
14451445
y = ""
14461446
reveal_type(t[x:]) # N: Revealed type is "builtins.tuple[builtins.int | builtins.str, ...]"
1447-
t[y:] # E: Slice index must be an integer, SupportsIndex or None
1447+
t[y:] # E: Invalid index type "slice[str, None, None]" for "tuple[int, str]"; expected type "slice[int | None]"
14481448
[builtins fixtures/tuple.pyi]
14491449

14501450
[case testTupleSliceStepZeroNoCrash]

test-data/unit/fixtures/slice.pyi

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Builtins stub used in slicing test cases.
2-
from typing import Generic, TypeVar
2+
from typing import Generic, TypeVar, Protocol
33
T = TypeVar('T')
4+
_Tco = TypeVar('_Tco', covariant=True)
5+
6+
class SupportsIndex(Protocol):
7+
def __index__(self) -> int: ...
48

59
class object:
610
def __init__(self): pass
@@ -12,8 +16,8 @@ class function: pass
1216
class int: pass
1317
class str: pass
1418

15-
class slice: pass
19+
class slice(Generic[_Tco]): pass
1620
class ellipsis: pass
1721
class dict: pass
1822
class list(Generic[T]):
19-
def __getitem__(self, x: slice) -> list[T]: pass
23+
def __getitem__(self, x: slice[SupportsIndex | None]) -> list[T]: pass

test-data/unit/fixtures/tuple.pyi

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ class tuple(Sequence[_Tco], Generic[_Tco]):
2121
def __contains__(self, item: object) -> bool: pass
2222
@overload
2323
def __getitem__(self, x: int) -> _Tco: pass
24+
# Real stubs use SupportsIndex, but we use int to speed up tests, as this is a common fixture.
2425
@overload
25-
def __getitem__(self, x: slice) -> Tuple[_Tco, ...]: ...
26+
def __getitem__(self, x: slice[int | None]) -> Tuple[_Tco, ...]: ...
2627
def __mul__(self, n: int) -> Tuple[_Tco, ...]: pass
2728
def __rmul__(self, n: int) -> Tuple[_Tco, ...]: pass
2829
def __add__(self, x: Tuple[_Tco, ...]) -> Tuple[_Tco, ...]: pass
@@ -37,7 +38,7 @@ class int:
3738
def __neg__(self) -> 'int': pass
3839
def __pos__(self) -> 'int': pass
3940
class float: pass
40-
class slice: pass
41+
class slice(Generic[_Tco]): pass
4142
class bool(int): pass
4243
class str: pass # For convenience
4344
class bytes: pass
@@ -47,7 +48,7 @@ class list(Sequence[_T], Generic[_T]):
4748
@overload
4849
def __getitem__(self, i: int) -> _T: ...
4950
@overload
50-
def __getitem__(self, s: slice) -> list[_T]: ...
51+
def __getitem__(self, s: slice[int | None]) -> list[_T]: ...
5152
def __contains__(self, item: object) -> bool: ...
5253
def __iter__(self) -> Iterator[_T]: ...
5354

0 commit comments

Comments
 (0)