Skip to content

builtins.slice: more precise __new__ overloads and let StopT default to StartT. #12904

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
48b9293
slice.__new__ pin start/step to None if not given
randolf-scholz Oct 24, 2024
b491e4d
generic slice initialization
randolf-scholz Oct 24, 2024
3010eda
None-overloads
randolf-scholz Oct 24, 2024
d0f3325
Merge branch 'main' into slice_generic_defaults
randolf-scholz Oct 24, 2024
b4bcf86
explicit overloads
randolf-scholz Oct 24, 2024
e838b8f
removed accidental copy pasta
randolf-scholz Oct 25, 2024
0104927
simplify overloads
randolf-scholz Oct 25, 2024
0d4a4cf
Merge branch 'main' into slice_generic_defaults
randolf-scholz Oct 25, 2024
72e94b7
more simplification
randolf-scholz Oct 25, 2024
c73c28c
Update stdlib/builtins.pyi
randolf-scholz Oct 25, 2024
2be2aa0
always specify all generic parameters
randolf-scholz Oct 25, 2024
37f532a
different __new__ overloads
randolf-scholz Oct 25, 2024
11ca6c6
added test cases
randolf-scholz Oct 25, 2024
4c4f319
added tests
randolf-scholz Oct 25, 2024
84897a9
docstring first
randolf-scholz Oct 25, 2024
ba15c42
keep alphabetic order
randolf-scholz Oct 25, 2024
6c77882
T | Any test
randolf-scholz Oct 25, 2024
131c95c
region wording
randolf-scholz Oct 25, 2024
c411763
fixed tests
randolf-scholz Oct 25, 2024
6b778e0
Update stdlib/@tests/test_cases/builtins/check_slice.py
randolf-scholz Oct 25, 2024
9f51b27
Update stdlib/@tests/test_cases/builtins/check_slice.py
randolf-scholz Oct 25, 2024
11e7a16
Update stdlib/@tests/test_cases/builtins/check_slice.py
randolf-scholz Oct 25, 2024
f1d6c1e
Update stdlib/@tests/test_cases/builtins/check_slice.py
randolf-scholz Oct 25, 2024
1daeca7
Merge branch 'main' into slice_generic_alternative
AlexWaygood Nov 12, 2024
6bdad56
added integration tests for timeseries
randolf-scholz Nov 12, 2024
080b7fa
return type test
randolf-scholz Nov 12, 2024
c7053c2
Update stdlib/@tests/test_cases/builtins/check_slice.py
randolf-scholz Nov 12, 2024
45a667f
reverted to and added consistency tests
randolf-scholz Nov 12, 2024
f0790f6
re-organized tests by number of arguments; added negative checks
randolf-scholz Nov 12, 2024
223ff20
improved readability by using equal length arguments
randolf-scholz Nov 12, 2024
faa1d00
Merge branch 'main' into slice_generic_alternative
randolf-scholz Nov 21, 2024
58c6528
Merge branch 'main' into slice_generic_alternative
randolf-scholz Jan 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 240 additions & 0 deletions stdlib/@tests/test_cases/builtins/check_slice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
"""
Assuming X, Y and Z are types other than None, the following rules apply to the slice type:

- The type hint `slice` should be compatible with all slices, including:
- `slice(None)`, `slice(None, None)` and `slice(None, None, None)`. (⟿ `slice[?, ?, ?]`)
- The type hint `slice[T]` should be compatible with:
- `slice(None)`, `slice(None, None)` and `slice(None, None, None)` (⟿ `slice[?, ?, ?]`)
- `slice(t)`, `slice(None, t)` and `slice(None, t, None)`. (⟿ `slice[?, T, ?]`)
- `slice(t, None)` and `slice(t, None, None)`. (⟿ `slice[T, ?, ?]`)
- `slice(t, t)` and `slice(t, t, None)`. (⟿ `slice[T, T, ?]`)
- The type hint `slice[X, Y]` should be compatible with:
- `slice(None)`, `slice(None, None)` and `slice(None, None, None)` (⟿ `slice[?, ?, ?]`)
- `slice(y)`, `slice(None, y)` and `slice(None, y, None)`. (⟿ `slice[?, Y, ?]`)
- `slice(x, None)` and `slice(x, None, None)` (⟿ `slice[X, ?, ?]`)
- `slice(x, y)` and `slice(x, y, None)`. (⟿ `slice[X, Y, ?]`)
- The type hint `slice[X, Y, Z]` should be compatible with:
- `slice(None)`, `slice(None, None)` and `slice(None, None, None)`. (⟿ `slice[?, ?, ?]`)
- `slice(y)`, `slice(None, y)` and `slice(None, y, None)`. (⟿ `slice[?, Y, ?]`)
- `slice(x, None)` and `slice(x, None, None)` (⟿ `slice[X, ?, ?]`)
- `slice(x, y)` and `slice(x, y, None)`. (⟿ `slice[X, Y, ?]`)
- `slice(None, None, z)` (⟿ `slice[?, ?, Z]`)
- `slice(None, y, z)` (⟿ `slice[?, Y, Z]`)
- `slice(x, None, z)` (⟿ `slice[X, ?, Z]`)
- `slice(x, y, z)` (⟿ `slice[X, Y, Z]`)

Consistency criterion: Assuming now X, Y, Z can potentially be None, the following rules apply:

- `slice(x)` must be compatible with `slice[None, X, None]`, even if X is None.
- `slice(x, y)` must be compatible with `slice[X,Y,None]`, even if X is None or Y is None.
- `slice(x, y, z)` must be compatible with `slice[X, Y, Z]`, even if X, Y, or Z are `None`.
"""

from datetime import date, datetime as DT, timedelta as TD
from typing import Any
from typing_extensions import assert_type

# region Tests for slice constructor overloads -----------------------------------------
assert_type(slice(None), "slice[Any, Any, Any]")
assert_type(slice(1234), "slice[Any, int, Any]")

assert_type(slice(None, None), "slice[Any, Any, Any]")
assert_type(slice(None, 5678), "slice[Any, int, Any]")
assert_type(slice(1234, None), "slice[int, Any, Any]")
assert_type(slice(1234, 5678), "slice[int, int, Any]")

assert_type(slice(None, None, None), "slice[Any, Any, Any]")
assert_type(slice(None, 5678, None), "slice[Any, int, Any]")
assert_type(slice(1234, None, None), "slice[int, Any, Any]")
assert_type(slice(1234, 5678, None), "slice[int, int, Any]")
assert_type(slice(1234, 5678, 9012), "slice[int, int, int]")
# endregion Tests for slice constructor overloads --------------------------------------


# region Tests for slice properties ----------------------------------------------------
# Note: if an argument is not None, we should get precisely the same type back
assert_type(slice(1234).stop, int)

assert_type(slice(1234, None).start, int)
assert_type(slice(None, 5678).stop, int)

assert_type(slice(1234, None, None).start, int)
assert_type(slice(None, 5678, None).stop, int)
assert_type(slice(None, None, 9012).step, int)
# endregion Tests for slice properties -------------------------------------------------


# region Test for slice assignments ----------------------------------------------------
# exhaustively test all possible assignments: miss (X), None (N), int (I), and str (S)
rXNX: slice = slice(None)
rXIX: slice = slice(1234)
rXSX: slice = slice("70")

rNNX: slice = slice(None, None)
rINX: slice = slice(1234, None)
rSNX: slice = slice("70", None)

rNIX: slice = slice(None, 5678)
rIIX: slice = slice(1234, 5678)
rSIX: slice = slice("70", 9012)

rNSX: slice = slice(None, "71")
rISX: slice = slice(1234, "71")
rSSX: slice = slice("70", "71")

rNNN: slice = slice(None, None, None)
rINN: slice = slice(1234, None, None)
rSNN: slice = slice("70", None, None)
rNIN: slice = slice(None, 5678, None)
rIIN: slice = slice(1234, 5678, None)
rSIN: slice = slice("70", 5678, None)
rNSN: slice = slice(None, "71", None)
rISN: slice = slice(1234, "71", None)
rSSN: slice = slice("70", "71", None)

rNNI: slice = slice(None, None, 9012)
rINI: slice = slice(1234, None, 9012)
rSNI: slice = slice("70", None, 9012)
rNII: slice = slice(None, 5678, 9012)
rIII: slice = slice(1234, 5678, 9012)
rSII: slice = slice("70", 5678, 9012)
rNSI: slice = slice(None, "71", 9012)
rISI: slice = slice(1234, "71", 9012)
rSSI: slice = slice("70", "71", 9012)

rNNS: slice = slice(None, None, "1d")
rINS: slice = slice(1234, None, "1d")
rSNS: slice = slice("70", None, "1d")
rNIS: slice = slice(None, 5678, "1d")
rIIS: slice = slice(1234, 5678, "1d")
rSIS: slice = slice("70", 5678, "1d")
rNSS: slice = slice(None, "71", "1d")
rISS: slice = slice(1234, "71", "1d")
rSSS: slice = slice("70", "71", "1d")
# endregion Test for slice assignments -------------------------------------------------


# region Tests for slice[T] assignments ------------------------------------------------
sXNX: "slice[int]" = slice(None)
sXIX: "slice[int]" = slice(1234)

sNNX: "slice[int]" = slice(None, None)
sNIX: "slice[int]" = slice(None, 5678)
sINX: "slice[int]" = slice(1234, None)
sIIX: "slice[int]" = slice(1234, 5678)

sNNN: "slice[int]" = slice(None, None, None)
sNIN: "slice[int]" = slice(None, 5678, None)
sNNS: "slice[int]" = slice(None, None, "1d")
sINN: "slice[int]" = slice(1234, None, None)
sINS: "slice[int]" = slice(1234, None, "1d")
sIIN: "slice[int]" = slice(1234, 5678, None)
sIIS: "slice[int]" = slice(1234, 5678, "1d")
# endregion Tests for slice[T] assignments ---------------------------------------------


# region Tests for slice[X, Y] assignments ---------------------------------------------
# Note: start=int is illegal and hence we add an explicit "type: ignore" comment.
tXNX: "slice[None, int]" = slice(None) # since slice(None) is slice[Any, Any, Any]
tXIX: "slice[None, int]" = slice(1234)

tNNX: "slice[None, int]" = slice(None, None)
tNIX: "slice[None, int]" = slice(None, 5678)
tINX: "slice[None, int]" = slice(1234, None) # type: ignore
tIIX: "slice[None, int]" = slice(1234, 5678) # type: ignore

tNNN: "slice[None, int]" = slice(None, None, None)
tNIN: "slice[None, int]" = slice(None, 5678, None)
tINN: "slice[None, int]" = slice(1234, None, None) # type: ignore
tIIN: "slice[None, int]" = slice(1234, 5678, None) # type: ignore
tNNS: "slice[None, int]" = slice(None, None, "1d")
tINS: "slice[None, int]" = slice(None, 5678, "1d")
tNIS: "slice[None, int]" = slice(1234, None, "1d") # type: ignore
tIIS: "slice[None, int]" = slice(1234, 5678, "1d") # type: ignore
# endregion Tests for slice[X, Y] assignments ------------------------------------------


# region Tests for slice[X, Y, Z] assignments ------------------------------------------
uXNX: "slice[int, int, int]" = slice(None)
uXIX: "slice[int, int, int]" = slice(1234)

uNNX: "slice[int, int, int]" = slice(None, None)
uNIX: "slice[int, int, int]" = slice(None, 5678)
uINX: "slice[int, int, int]" = slice(1234, None)
uIIX: "slice[int, int, int]" = slice(1234, 5678)

uNNN: "slice[int, int, int]" = slice(None, None, None)
uNNI: "slice[int, int, int]" = slice(None, None, 9012)
uNIN: "slice[int, int, int]" = slice(None, 5678, None)
uNII: "slice[int, int, int]" = slice(None, 5678, 9012)
uINN: "slice[int, int, int]" = slice(1234, None, None)
uINI: "slice[int, int, int]" = slice(1234, None, 9012)
uIIN: "slice[int, int, int]" = slice(1234, 5678, None)
uIII: "slice[int, int, int]" = slice(1234, 5678, 9012)
# endregion Tests for slice[X, Y, Z] assignments ---------------------------------------


# region Test for slice consistency criterion ------------------------------------------
year = date(2021, 1, 1)
vXNX: "slice[None, None, None]" = slice(None)
vXIX: "slice[None, date, None]" = slice(year)

vNNX: "slice[None, None, None]" = slice(None, None)
vNIX: "slice[None, date, None]" = slice(None, year)
vINX: "slice[date, None, None]" = slice(year, None)
vIIX: "slice[date, date, None]" = slice(year, year)

vNNN: "slice[None, None, None]" = slice(None, None, None)
vNIN: "slice[None, date, None]" = slice(None, year, None)
vINN: "slice[date, None, None]" = slice(year, None, None)
vIIN: "slice[date, date, None]" = slice(year, year, None)
vNNI: "slice[None, None, str]" = slice(None, None, "1d")
vNII: "slice[None, date, str]" = slice(None, year, "1d")
vINI: "slice[date, None, str]" = slice(year, None, "1d")
vIII: "slice[date, date, str]" = slice(year, year, "1d")
# endregion Test for slice consistency criterion ---------------------------------------


# region Integration tests for slices with datetimes -----------------------------------
class TimeSeries: # similar to pandas.Series with datetime index
def __getitem__(self, key: "slice[DT | str | None, DT | str | None]") -> Any:
"""Subsample the time series at the given dates."""
...


class TimeSeriesInterpolator: # similar to pandas.Series with datetime index
def __getitem__(self, key: "slice[DT, DT, TD | None]") -> Any:
"""Subsample the time series at the given dates."""
...


# tests slices as an argument
start = DT(1970, 1, 1)
stop = DT(1971, 1, 10)
step = TD(days=1)
# see: https://pandas.pydata.org/docs/user_guide/timeseries.html#partial-string-indexing
# FIXME: https://github.com/python/mypy/issues/2410 (use literal slices)
series = TimeSeries()
_ = series[slice(None, "1970-01-10")]
_ = series[slice("1970-01-01", None)]
_ = series[slice("1970-01-01", "1971-01-10")]
_ = series[slice(None, stop)]
_ = series[slice(start, None)]
_ = series[slice(start, stop)]
_ = series[slice(None)]

model = TimeSeriesInterpolator()
_ = model[slice(start, stop)]
_ = model[slice(start, stop, step)]
_ = model[slice(start, stop, None)]


# test slices as a return type
def foo(flag: bool, value: DT) -> "slice[DT, None] | slice[None, DT]":
if flag:
return slice(value, None) # slice[DT, DT|Any, Any] incompatible
else:
return slice(None, value) # slice[DT|Any, DT, Any] incompatible


# endregion Integration tests for slices with datetimes --------------------------------
39 changes: 25 additions & 14 deletions stdlib/builtins.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ from _typeshed import (
ConvertibleToFloat,
ConvertibleToInt,
FileDescriptorOrPath,
MaybeNone,
OpenBinaryMode,
OpenBinaryModeReading,
OpenBinaryModeUpdating,
Expand Down Expand Up @@ -95,9 +94,9 @@ _SupportsAnextT = TypeVar("_SupportsAnextT", bound=SupportsAnext[Any], covariant
_AwaitableT = TypeVar("_AwaitableT", bound=Awaitable[Any])
_AwaitableT_co = TypeVar("_AwaitableT_co", bound=Awaitable[Any], covariant=True)
_P = ParamSpec("_P")
_StartT = TypeVar("_StartT", covariant=True, default=Any)
_StopT = TypeVar("_StopT", covariant=True, default=Any)
_StepT = TypeVar("_StepT", covariant=True, default=Any)
_StartT_co = TypeVar("_StartT_co", covariant=True, default=Any) # slice -> slice[Any, Any, Any]
_StopT_co = TypeVar("_StopT_co", covariant=True, default=_StartT_co) # slice[A] -> slice[A, A, Any]
_StepT_co = TypeVar("_StepT_co", covariant=True, default=Any) # slice[A,B] -> slice[A, B, Any]

class object:
__doc__: str | None
Expand Down Expand Up @@ -940,23 +939,35 @@ class bool(int):
def __invert__(self) -> int: ...

@final
class slice(Generic[_StartT, _StopT, _StepT]):
class slice(Generic[_StartT_co, _StopT_co, _StepT_co]):
@property
def start(self) -> _StartT: ...
def start(self) -> _StartT_co: ...
@property
def step(self) -> _StepT: ...
def step(self) -> _StepT_co: ...
@property
def stop(self) -> _StopT: ...
def stop(self) -> _StopT_co: ...
# Note: __new__ overloads map `None` to `Any`, since users expect slice(x, None)
# to be compatible with slice(None, x).
# generic slice --------------------------------------------------------------------
@overload
def __new__(cls, stop: int | None, /) -> slice[int | MaybeNone, int | MaybeNone, int | MaybeNone]: ...
@overload
def __new__(
cls, start: int | None, stop: int | None, step: int | None = None, /
) -> slice[int | MaybeNone, int | MaybeNone, int | MaybeNone]: ...
def __new__(cls, start: None, stop: None = None, step: None = None, /) -> slice[Any, Any, Any]: ...
# unary overloads ------------------------------------------------------------------
@overload
def __new__(cls, stop: _T2, /) -> slice[Any, _T2, Any]: ...
# binary overloads -----------------------------------------------------------------
@overload
def __new__(cls, start: _T1, stop: None, step: None = None, /) -> slice[_T1, Any, Any]: ...
@overload
def __new__(cls, start: None, stop: _T2, step: None = None, /) -> slice[Any, _T2, Any]: ...
@overload
def __new__(cls, start: _T1, stop: _T2, step: None = None, /) -> slice[_T1, _T2, Any]: ...
# ternary overloads ----------------------------------------------------------------
@overload
def __new__(cls, start: None, stop: None, step: _T3, /) -> slice[Any, Any, _T3]: ...
@overload
def __new__(cls, start: _T1, stop: None, step: _T3, /) -> slice[_T1, Any, _T3]: ...
@overload
def __new__(cls, start: _T1, stop: _T2, /) -> slice[_T1, _T2, Any]: ...
def __new__(cls, start: None, stop: _T2, step: _T3, /) -> slice[Any, _T2, _T3]: ...
@overload
def __new__(cls, start: _T1, stop: _T2, step: _T3, /) -> slice[_T1, _T2, _T3]: ...
def __eq__(self, value: object, /) -> bool: ...
Expand Down
Loading