-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Make slice generic #11637
base: main
Are you sure you want to change the base?
Make slice generic #11637
Conversation
Want to see the primer hits.
This comment has been minimized.
This comment has been minimized.
cc @cdce8p I think the primer hits suggest a mypy bug where it looks up "_StartT" in the wrong scope. |
Recursive TypeVar defaults was one of the things that didn't make it into If you're interested, maybe that's also something for @AlexWaygood, a few weeks back I created a custom repo to upload dev releases of mypy to PyPI under the mypy-dev project name. They always track a mypy commit upstream and are great to test / use the lasted version (even in production if you feel like it). https://github.com/cdce8p/mypy-dev |
I just assumed my attempt at implementation was wrong and gave up. Should this be reopened, then? |
Thanks @cdce8p, I forgot that PR didn't make it into 1.9. Hopefully we'll have another release soon. Your mypy-dev project is interesting; maybe that's even something we could do on mypy itself. For example, we could have a weekly cron job uploading alpha releases. @Sachaa-Thanasius I think we may be able to merge this PR after mypy 1.10 is released (when that will happen, I don't know). You can leave it open in the meantime and mark it as deferred. With some hackery it may be possible to point typeshed's CI at the mypy master branch so we can test earlier whether everything works as expected. |
Understood. Should slice be changed in the runtime cpython as well to support |
Also, sidenote: The pyright errors specifically for
|
This comment has been minimized.
This comment has been minimized.
@Sachaa-Thanasius You could try to replace the mypy version listed in typeshed/requirements-tests.txt Line 8 in 5c75292
-mypy==1.9.0
+mypy-dev==1.10.0a2 That release tracks python/mypy@b013cc0 which was commited fairly recently to mypy master (5 days ago). Update: Seems like the mypy primer doesn't work for it, unfortunately. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Well, that's far more promising than before. Still not sure if this needs to be reflected in cpython by adding a |
I've removed the Deferred label. I've just looked at spark primer output. The problem is on their side, since they reuse the |
The error that pyright is hitting is that the usage of # Truncated setup.
from collections.abc import Iterator, Sequence
from itertools import combinations, repeat, starmap
from operator import getitem
from typing import TypeVar, cast
_T = TypeVar("_T")
# Actual function
def subslices(seq: Sequence[_T]) -> Iterator[Sequence[_T]]:
slices = starmap(slice, combinations(range(len(seq) + 1), 2))
reveal_type(slices) # Type of "slices" is "starmap[slice[_StartT@slice, _StopT@slice, _StepT@slice]]"
# Adding this cast removes the error on the final line:
# slices = cast(starmap[slice[int, int, int | None]], slices)
# And using a generator instead of starmap removes the error on the final line as well:
# slices = (slice(*args) for args in combinations(range(len(seq) + 1), 2))
return map(getitem, repeat(seq), slices) # Long error. Full playground example here to recreate the conditions of the error. Might be more of a deficiency in the typing of |
I might be missing something obvious but it is not clear to me why the type of $ cat t.py
from typing_extensions import reveal_type
s = slice(10)
reveal_type(s)
reveal_type(s.start)
reveal_type(s.stop)
reveal_type(s.step)
$ python t.py
Runtime type is 'slice'
Runtime type is 'NoneType'
Runtime type is 'int'
Runtime type is 'NoneType' Similarly, the type of So I would expect the definitions of the type variables to be: _StartT = TypeVar("_StartT", default=None)
_StopT = TypeVar("_StopT", default=int)
_StepT = TypeVar("_StepT", default=None) |
I won’t lie; I just copy-pasted the example implementation from PEP 696 without much thought, since this PR wasn’t originally opened with the intention of being merged. Haven’t double-checked since. Definitely will take another look though. |
This comment has been minimized.
This comment has been minimized.
stdlib/builtins.pyi
Outdated
_StartT = TypeVar("_StartT", default=int) | ||
_StopT = TypeVar("_StopT", default=_StartT) | ||
_StepT = TypeVar("_StepT", default=int | None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe as a very first step, try using Any
as default. We could change this later, but this should allow explicit annotations without regressions.
This comment has been minimized.
This comment has been minimized.
Courtesy of the feedback from @hamdanal.
This comment has been minimized.
This comment has been minimized.
Maybe I'm misreading, but some of these errors indicate that mypy is using the typevar defaults as the typevar bounds as well, which wouldn't be correct afaik. EDIT: Removed a comment about the one pyright error; I'm not sure how that works right now. |
I'd go with @srittau's idea of setting defaults to |
Co-authored-by: Sebastian Rittau <srittau@rittau.biz>
One more thing, shouldn't the type variables be covariant? |
This comment has been minimized.
This comment has been minimized.
I feel like I don't know enough about how slices are used beyond the basics to know the answer to this, tbh. More than willing to defer to someone else's knowledge. |
Determining variance doesn't really require knowing how the class is used. Roughly speaking, a type parameter can be covariant if it's only used in method return types and contravariant if it's only used in parameter types. Here the typevars are only used in return types (read-only properties), so they can be covariant. Type checkers will tell you if you use a covariant or contravariant typevar where you should have used an invariant one. |
Covariance might help with some of the primer hits. I also don't understand why mypy wants explicit annotations in the test cases. |
Diff from mypy_primer, showing the effect of this PR on open source code: manticore (https://github.com/trailofbits/manticore)
- tests/wasm/json2mc.py:103: note: def __getitem__(self, slice, /) -> list[Any]
+ tests/wasm/json2mc.py:103: note: def __getitem__(self, slice[Any, Any, Any], /) -> list[Any]
pandas (https://github.com/pandas-dev/pandas)
+ pandas/core/array_algos/transforms.py:41: error: Argument 2 to "slice" has incompatible type "int"; expected "None" [arg-type]
+ pandas/core/algorithms.py:1372: error: Argument 2 to "slice" has incompatible type "int"; expected "None" [arg-type]
+ pandas/core/algorithms.py:1372: error: Argument 1 to "slice" has incompatible type "int"; expected "None" [arg-type]
+ pandas/core/algorithms.py:1372: error: Argument 2 to "slice" has incompatible type "None"; expected "int" [arg-type]
+ pandas/core/algorithms.py:1383: error: Argument 1 to "slice" has incompatible type "None"; expected "int" [arg-type]
+ pandas/core/algorithms.py:1383: error: Argument 2 to "slice" has incompatible type "int"; expected "None" [arg-type]
+ pandas/core/algorithms.py:1387: error: Argument 2 to "slice" has incompatible type "int"; expected "None" [arg-type]
+ pandas/core/algorithms.py:1387: error: Argument 1 to "slice" has incompatible type "int"; expected "None" [arg-type]
+ pandas/core/algorithms.py:1387: error: Argument 2 to "slice" has incompatible type "None"; expected "int" [arg-type]
kornia (https://github.com/kornia/kornia)
+ kornia/contrib/models/sam/architecture/mask_decoder.py:92: error: Argument 2 to "slice" has incompatible type "int"; expected "None" [arg-type]
werkzeug (https://github.com/pallets/werkzeug)
- tests/test_wrappers.py:522: note: def __setitem__(self, slice, Iterable[tuple[str, str]], /) -> None
+ tests/test_wrappers.py:522: note: def __setitem__(self, slice[Any, Any, Any], Iterable[tuple[str, str]], /) -> None
- tests/test_wrappers.py:563: note: def __setitem__(self, slice, Iterable[tuple[str, str]], /) -> None
+ tests/test_wrappers.py:563: note: def __setitem__(self, slice[Any, Any, Any], Iterable[tuple[str, str]], /) -> None
operator (https://github.com/canonical/operator)
- ops/framework.py:1291: note: def __getitem__(self, slice, /) -> MutableSequence[Any]
+ ops/framework.py:1291: note: def __getitem__(self, slice[Any, Any, Any], /) -> MutableSequence[Any]
- ops/framework.py:1291: note: def __getitem__(self, slice, /) -> Sequence[Any]
+ ops/framework.py:1291: note: def __getitem__(self, slice[Any, Any, Any], /) -> Sequence[Any]
- ops/framework.py:1294: note: def __setitem__(self, slice, Iterable[Any], /) -> None
+ ops/framework.py:1294: note: def __setitem__(self, slice[Any, Any, Any], Iterable[Any], /) -> None
- ops/framework.py:1298: note: def __delitem__(self, slice, /) -> None
+ ops/framework.py:1298: note: def __delitem__(self, slice[Any, Any, Any], /) -> None
speedrun.com_global_scoreboard_webapp (https://github.com/Avasam/speedrun.com_global_scoreboard_webapp)
- backend/api/tournament_scheduler_api.py:327: note: def __getitem__(self, slice, /) -> list[Any]
+ backend/api/tournament_scheduler_api.py:327: note: def __getitem__(self, slice[Any, Any, Any], /) -> list[Any]
- backend/api/tournament_scheduler_api.py:331: note: def __getitem__(self, slice, /) -> list[Any]
+ backend/api/tournament_scheduler_api.py:331: note: def __getitem__(self, slice[Any, Any, Any], /) -> list[Any]
- backend/api/tournament_scheduler_api.py:335: note: def __getitem__(self, slice, /) -> list[Any]
+ backend/api/tournament_scheduler_api.py:335: note: def __getitem__(self, slice[Any, Any, Any], /) -> list[Any]
ibis (https://github.com/ibis-project/ibis)
- ibis/selectors.py:646: error: Item "slice" of "slice | Iterable[int | str]" has no attribute "__iter__" (not iterable) [union-attr]
+ ibis/selectors.py:646: error: Item "slice[Any, Any, Any]" of "slice[Any, Any, Any] | Iterable[int | str]" has no attribute "__iter__" (not iterable) [union-attr]
- ibis/selectors.py:649: error: Item "Iterable[int | str]" of "slice | Iterable[int | str]" has no attribute "start" [union-attr]
+ ibis/selectors.py:649: error: Item "Iterable[int | str]" of "slice[Any, Any, Any] | Iterable[int | str]" has no attribute "start" [union-attr]
- ibis/selectors.py:650: error: Item "Iterable[int | str]" of "slice | Iterable[int | str]" has no attribute "stop" [union-attr]
+ ibis/selectors.py:650: error: Item "Iterable[int | str]" of "slice[Any, Any, Any] | Iterable[int | str]" has no attribute "stop" [union-attr]
- ibis/selectors.py:651: error: Item "Iterable[int | str]" of "slice | Iterable[int | str]" has no attribute "step" [union-attr]
+ ibis/selectors.py:651: error: Item "Iterable[int | str]" of "slice[Any, Any, Any] | Iterable[int | str]" has no attribute "step" [union-attr]
jax (https://github.com/google/jax)
+ jax/_src/core.py:1956: error: Argument 1 to "slice" has incompatible type "None"; expected "int" [arg-type]
+ jax/_src/numpy/lax_numpy.py:814: error: Argument 2 to "slice" has incompatible type "int"; expected "None" [arg-type]
+ jax/experimental/sparse/bcoo.py:1980: error: Argument 2 to "slice" has incompatible type "int"; expected "None" [arg-type]
+ jax/experimental/sparse/bcoo.py:1981: error: Argument 2 to "slice" has incompatible type "int"; expected "None" [arg-type]
+ jax/experimental/sparse/bcoo.py:1985: error: Argument 2 to "slice" has incompatible type "int"; expected "None" [arg-type]
discord.py (https://github.com/Rapptz/discord.py)
- discord/http.py:231: note: def __setitem__(self, slice, Iterable[Embed], /) -> None
+ discord/http.py:231: note: def __setitem__(self, slice[Any, Any, Any], Iterable[Embed], /) -> None
streamlit (https://github.com/streamlit/streamlit)
- lib/tests/streamlit/elements/lib/column_config_utils_test.py:382:30: error: Invalid index type "str" for "Union[ColumnConfig, None, str]"; expected type "Union[SupportsIndex, slice]" [index]
+ lib/tests/streamlit/elements/lib/column_config_utils_test.py:382:30: error: Invalid index type "str" for "Union[ColumnConfig, None, str]"; expected type "Union[SupportsIndex, slice[Any, Any, Any]]" [index]
- lib/tests/streamlit/runtime/state/query_params_test.py:264:37: note: def __getitem__(self, slice, /) -> Tuple[Any, ...]
+ lib/tests/streamlit/runtime/state/query_params_test.py:264:37: note: def __getitem__(self, slice[Any, Any, Any], /) -> Tuple[Any, ...]
|
Looking at the primer hits:
While this introduces a bit of churn in more complex/unorthodox uses of |
I don't know why the mypy test is failing but I figured out how to make it work. If we decouple the class scoped type variables from the diff --git a/stdlib/builtins.pyi b/stdlib/builtins.pyi
index 6e26bab06..bcc44c0c5 100644
--- a/stdlib/builtins.pyi
+++ b/stdlib/builtins.pyi
@@ -75,6 +75,7 @@ if sys.version_info >= (3, 9):
from types import GenericAlias
_T = TypeVar("_T")
+_R = TypeVar("_R")
_T_co = TypeVar("_T_co", covariant=True)
_T_contra = TypeVar("_T_contra", contravariant=True)
_R_co = TypeVar("_R_co", covariant=True)
@@ -925,9 +926,11 @@ class slice(Generic[_StartT, _StopT, _StepT]):
@property
def stop(self) -> _StopT: ...
@overload
- def __new__(cls, stop: _StopT, /) -> Self: ...
+ def __new__(cls, stop: _S, /) -> slice[Any, _S]: ...
@overload
- def __new__(cls, start: _StartT, stop: _StopT, step: _StepT = ..., /) -> Self: ...
+ def __new__(cls, start: _R, stop: _S, /) -> slice[_R, _S]: ...
+ @overload
+ def __new__(cls, start: _R, stop: _S, step: _T, /) -> slice[_R, _S, _T]: ...
def __eq__(self, value: object, /) -> bool: ...
__hash__: ClassVar[None] # type: ignore[assignment]
def indices(self, len: SupportsIndex, /) -> tuple[int, int, int]: ... I don't see a downside to returning |
Mainly doing this out of curiosity to see the primer hits.
EDIT: Would close #8647.