Skip to content

Commit

Permalink
Allow SupportsIndex in slice expressions (#14738)
Browse files Browse the repository at this point in the history
Helps with #2410, as suggested by BvB93 in
#2410 (comment)

PEP 696 will be the real solution here, since it will allow us to make
slice generic with few backward compatibility issues
  • Loading branch information
hauntsaninja committed Mar 11, 2023
1 parent 3098574 commit fb6b8bc
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 8 deletions.
6 changes: 5 additions & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4638,7 +4638,11 @@ def _super_arg_types(self, e: SuperExpr) -> Type | tuple[Type, Type]:
return type_type, instance_type

def visit_slice_expr(self, e: SliceExpr) -> Type:
expected = make_optional_type(self.named_type("builtins.int"))
try:
supports_index = self.chk.named_type("typing_extensions.SupportsIndex")
except KeyError:
supports_index = self.chk.named_type("builtins.int") # thanks, fixture life
expected = make_optional_type(supports_index)
for index in [e.begin_index, e.end_index, e.stride]:
if index:
t = self.accept(index)
Expand Down
2 changes: 1 addition & 1 deletion mypy/message_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
INCOMPATIBLE_TYPES_IN_CAPTURE: Final = ErrorMessage("Incompatible types in capture pattern")
MUST_HAVE_NONE_RETURN_TYPE: Final = ErrorMessage('The return type of "{}" must be None')
TUPLE_INDEX_OUT_OF_RANGE: Final = ErrorMessage("Tuple index out of range")
INVALID_SLICE_INDEX: Final = ErrorMessage("Slice index must be an integer or None")
INVALID_SLICE_INDEX: Final = ErrorMessage("Slice index must be an integer, SupportsIndex or None")
CANNOT_INFER_LAMBDA_TYPE: Final = ErrorMessage("Cannot infer type of lambda")
CANNOT_ACCESS_INIT: Final = (
'Accessing "__init__" on an instance is unsound, since instance.__init__ could be from'
Expand Down
27 changes: 22 additions & 5 deletions test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -1115,11 +1115,28 @@ o[:] # E: Value of type "object" is not indexable

[case testNonIntSliceBounds]
from typing import Any
a, o = None, None # type: (Any, object)
a[o:1] # E: Slice index must be an integer or None
a[1:o] # E: Slice index must be an integer or None
a[o:] # E: Slice index must be an integer or None
a[:o] # E: Slice index must be an integer or None
a: Any
o: object
a[o:1] # E: Slice index must be an integer, SupportsIndex or None
a[1:o] # E: Slice index must be an integer, SupportsIndex or None
a[o:] # E: Slice index must be an integer, SupportsIndex or None
a[:o] # E: Slice index must be an integer, SupportsIndex or None
[builtins fixtures/slice.pyi]

[case testSliceSupportsIndex]
import typing_extensions
class Index:
def __init__(self, value: int) -> None:
self.value = value
def __index__(self) -> int:
return self.value

c = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
reveal_type(c[Index(0):Index(5)]) # N: Revealed type is "builtins.list[builtins.int]"
[file typing_extensions.pyi]
from typing import Protocol
class SupportsIndex(Protocol):
def __index__(self) -> int: ...
[builtins fixtures/slice.pyi]

[case testNoneSliceBounds]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-tuples.test
Original file line number Diff line number Diff line change
Expand Up @@ -1248,7 +1248,7 @@ t = (0, "")
x = 0
y = ""
reveal_type(t[x:]) # N: Revealed type is "builtins.tuple[Union[builtins.int, builtins.str], ...]"
t[y:] # E: Slice index must be an integer or None
t[y:] # E: Slice index must be an integer, SupportsIndex or None
[builtins fixtures/tuple.pyi]

[case testInferTupleTypeFallbackAgainstInstance]
Expand Down
2 changes: 2 additions & 0 deletions test-data/unit/fixtures/slice.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ class str: pass
class slice: pass
class ellipsis: pass
class dict: pass
class list(Generic[T]):
def __getitem__(self, x: slice) -> list[T]: pass

0 comments on commit fb6b8bc

Please sign in to comment.