mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-16 16:40:36 +00:00
[red-knot] Slice expression types & subscript expressions with slices (#13917)
## Summary - Add a new `Type::SliceLiteral` variant - Infer `SliceLiteral` types for slice expressions, such as `<int-literal>:<int-literal>:<int-literal>`. - Infer "sliced" literal types for subscript expressions using slices, such as `<string-literal>[<slice-literal>]`. - Infer types for expressions involving slices of tuples: `<tuple>[<slice-literal>]`. closes #13853 ## Test Plan - Unit tests for indexing/slicing utility functions - Markdown-based tests for - Subscript expressions `tuple[slice]` - Subscript expressions `string_literal[slice]` - Subscript expressions `bytes_literal[slice]`
This commit is contained in:
parent
2fe203292a
commit
56c796acee
8 changed files with 848 additions and 98 deletions
|
@ -1,6 +1,6 @@
|
||||||
# Bytes subscript
|
# Bytes subscripts
|
||||||
|
|
||||||
## Simple
|
## Indexing
|
||||||
|
|
||||||
```py
|
```py
|
||||||
b = b"\x00abc\xff"
|
b = b"\x00abc\xff"
|
||||||
|
@ -21,11 +21,7 @@ reveal_type(x) # revealed: Unknown
|
||||||
|
|
||||||
y = b[-6] # error: [index-out-of-bounds] "Index -6 is out of bounds for bytes literal `Literal[b"\x00abc\xff"]` with length 5"
|
y = b[-6] # error: [index-out-of-bounds] "Index -6 is out of bounds for bytes literal `Literal[b"\x00abc\xff"]` with length 5"
|
||||||
reveal_type(y) # revealed: Unknown
|
reveal_type(y) # revealed: Unknown
|
||||||
```
|
|
||||||
|
|
||||||
## Function return
|
|
||||||
|
|
||||||
```py
|
|
||||||
def int_instance() -> int:
|
def int_instance() -> int:
|
||||||
return 42
|
return 42
|
||||||
|
|
||||||
|
@ -33,3 +29,29 @@ a = b"abcde"[int_instance()]
|
||||||
# TODO: Support overloads... Should be `bytes`
|
# TODO: Support overloads... Should be `bytes`
|
||||||
reveal_type(a) # revealed: @Todo
|
reveal_type(a) # revealed: @Todo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Slices
|
||||||
|
|
||||||
|
```py
|
||||||
|
b = b"\x00abc\xff"
|
||||||
|
|
||||||
|
reveal_type(b[0:2]) # revealed: Literal[b"\x00a"]
|
||||||
|
reveal_type(b[-3:]) # revealed: Literal[b"bc\xff"]
|
||||||
|
|
||||||
|
b[0:4:0] # error: [zero-stepsize-in-slice]
|
||||||
|
b[:4:0] # error: [zero-stepsize-in-slice]
|
||||||
|
b[0::0] # error: [zero-stepsize-in-slice]
|
||||||
|
b[::0] # error: [zero-stepsize-in-slice]
|
||||||
|
|
||||||
|
def int_instance() -> int: ...
|
||||||
|
|
||||||
|
byte_slice1 = b[int_instance() : int_instance()]
|
||||||
|
# TODO: Support overloads... Should be `bytes`
|
||||||
|
reveal_type(byte_slice1) # revealed: @Todo
|
||||||
|
|
||||||
|
def bytes_instance() -> bytes: ...
|
||||||
|
|
||||||
|
byte_slice2 = bytes_instance()[0:5]
|
||||||
|
# TODO: Support overloads... Should be `bytes`
|
||||||
|
reveal_type(byte_slice2) # revealed: @Todo
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Stepsize zero in slices
|
||||||
|
|
||||||
|
We raise a `zero-stepsize-in-slice` diagnostic when trying to slice a literal
|
||||||
|
string, bytes, or tuple with a step size of zero (see tests in `string.md`,
|
||||||
|
`bytes.md` and `tuple.md`). But we don't want to raise this diagnostic when
|
||||||
|
slicing a custom type:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class MySequence:
|
||||||
|
def __getitem__(self, s: slice) -> int:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
MySequence()[0:1:0] # No error
|
||||||
|
```
|
|
@ -1,6 +1,6 @@
|
||||||
# Subscript on strings
|
# String subscripts
|
||||||
|
|
||||||
## Simple
|
## Indexing
|
||||||
|
|
||||||
```py
|
```py
|
||||||
s = "abcde"
|
s = "abcde"
|
||||||
|
@ -18,15 +18,71 @@ reveal_type(a) # revealed: Unknown
|
||||||
|
|
||||||
b = s[-8] # error: [index-out-of-bounds] "Index -8 is out of bounds for string `Literal["abcde"]` with length 5"
|
b = s[-8] # error: [index-out-of-bounds] "Index -8 is out of bounds for string `Literal["abcde"]` with length 5"
|
||||||
reveal_type(b) # revealed: Unknown
|
reveal_type(b) # revealed: Unknown
|
||||||
```
|
|
||||||
|
|
||||||
## Function return
|
def int_instance() -> int: ...
|
||||||
|
|
||||||
```py
|
|
||||||
def int_instance() -> int:
|
|
||||||
return 42
|
|
||||||
|
|
||||||
a = "abcde"[int_instance()]
|
a = "abcde"[int_instance()]
|
||||||
# TODO: Support overloads... Should be `str`
|
# TODO: Support overloads... Should be `str`
|
||||||
reveal_type(a) # revealed: @Todo
|
reveal_type(a) # revealed: @Todo
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Slices
|
||||||
|
|
||||||
|
```py
|
||||||
|
s = "abcde"
|
||||||
|
|
||||||
|
reveal_type(s[0:0]) # revealed: Literal[""]
|
||||||
|
reveal_type(s[0:1]) # revealed: Literal["a"]
|
||||||
|
reveal_type(s[0:2]) # revealed: Literal["ab"]
|
||||||
|
reveal_type(s[0:5]) # revealed: Literal["abcde"]
|
||||||
|
reveal_type(s[0:6]) # revealed: Literal["abcde"]
|
||||||
|
reveal_type(s[1:3]) # revealed: Literal["bc"]
|
||||||
|
|
||||||
|
reveal_type(s[-3:5]) # revealed: Literal["cde"]
|
||||||
|
reveal_type(s[-4:-2]) # revealed: Literal["bc"]
|
||||||
|
reveal_type(s[-10:10]) # revealed: Literal["abcde"]
|
||||||
|
|
||||||
|
reveal_type(s[0:]) # revealed: Literal["abcde"]
|
||||||
|
reveal_type(s[2:]) # revealed: Literal["cde"]
|
||||||
|
reveal_type(s[5:]) # revealed: Literal[""]
|
||||||
|
reveal_type(s[:2]) # revealed: Literal["ab"]
|
||||||
|
reveal_type(s[:0]) # revealed: Literal[""]
|
||||||
|
reveal_type(s[:2]) # revealed: Literal["ab"]
|
||||||
|
reveal_type(s[:10]) # revealed: Literal["abcde"]
|
||||||
|
reveal_type(s[:]) # revealed: Literal["abcde"]
|
||||||
|
|
||||||
|
reveal_type(s[::-1]) # revealed: Literal["edcba"]
|
||||||
|
reveal_type(s[::2]) # revealed: Literal["ace"]
|
||||||
|
reveal_type(s[-2:-5:-1]) # revealed: Literal["dcb"]
|
||||||
|
reveal_type(s[::-2]) # revealed: Literal["eca"]
|
||||||
|
reveal_type(s[-1::-3]) # revealed: Literal["eb"]
|
||||||
|
|
||||||
|
reveal_type(s[None:2:None]) # revealed: Literal["ab"]
|
||||||
|
reveal_type(s[1:None:1]) # revealed: Literal["bcde"]
|
||||||
|
reveal_type(s[None:None:None]) # revealed: Literal["abcde"]
|
||||||
|
|
||||||
|
start = 1
|
||||||
|
stop = None
|
||||||
|
step = 2
|
||||||
|
reveal_type(s[start:stop:step]) # revealed: Literal["bd"]
|
||||||
|
|
||||||
|
reveal_type(s[False:True]) # revealed: Literal["a"]
|
||||||
|
reveal_type(s[True:3]) # revealed: Literal["bc"]
|
||||||
|
|
||||||
|
s[0:4:0] # error: [zero-stepsize-in-slice]
|
||||||
|
s[:4:0] # error: [zero-stepsize-in-slice]
|
||||||
|
s[0::0] # error: [zero-stepsize-in-slice]
|
||||||
|
s[::0] # error: [zero-stepsize-in-slice]
|
||||||
|
|
||||||
|
def int_instance() -> int: ...
|
||||||
|
|
||||||
|
substring1 = s[int_instance() : int_instance()]
|
||||||
|
# TODO: Support overloads... Should be `LiteralString`
|
||||||
|
reveal_type(substring1) # revealed: @Todo
|
||||||
|
|
||||||
|
def str_instance() -> str: ...
|
||||||
|
|
||||||
|
substring2 = str_instance()[0:5]
|
||||||
|
# TODO: Support overloads... Should be `str`
|
||||||
|
reveal_type(substring2) # revealed: @Todo
|
||||||
|
```
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Tuple subscripts
|
# Tuple subscripts
|
||||||
|
|
||||||
## Basic
|
## Indexing
|
||||||
|
|
||||||
```py
|
```py
|
||||||
t = (1, "a", "b")
|
t = (1, "a", "b")
|
||||||
|
@ -10,9 +10,66 @@ reveal_type(t[1]) # revealed: Literal["a"]
|
||||||
reveal_type(t[-1]) # revealed: Literal["b"]
|
reveal_type(t[-1]) # revealed: Literal["b"]
|
||||||
reveal_type(t[-2]) # revealed: Literal["a"]
|
reveal_type(t[-2]) # revealed: Literal["a"]
|
||||||
|
|
||||||
|
reveal_type(t[False]) # revealed: Literal[1]
|
||||||
|
reveal_type(t[True]) # revealed: Literal["a"]
|
||||||
|
|
||||||
a = t[4] # error: [index-out-of-bounds]
|
a = t[4] # error: [index-out-of-bounds]
|
||||||
reveal_type(a) # revealed: Unknown
|
reveal_type(a) # revealed: Unknown
|
||||||
|
|
||||||
b = t[-4] # error: [index-out-of-bounds]
|
b = t[-4] # error: [index-out-of-bounds]
|
||||||
reveal_type(b) # revealed: Unknown
|
reveal_type(b) # revealed: Unknown
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Slices
|
||||||
|
|
||||||
|
```py
|
||||||
|
t = (1, "a", None, b"b")
|
||||||
|
|
||||||
|
reveal_type(t[0:0]) # revealed: tuple[()]
|
||||||
|
reveal_type(t[0:1]) # revealed: tuple[Literal[1]]
|
||||||
|
reveal_type(t[0:2]) # revealed: tuple[Literal[1], Literal["a"]]
|
||||||
|
reveal_type(t[0:4]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
|
||||||
|
reveal_type(t[0:5]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
|
||||||
|
reveal_type(t[1:3]) # revealed: tuple[Literal["a"], None]
|
||||||
|
|
||||||
|
reveal_type(t[-2:4]) # revealed: tuple[None, Literal[b"b"]]
|
||||||
|
reveal_type(t[-3:-1]) # revealed: tuple[Literal["a"], None]
|
||||||
|
reveal_type(t[-10:10]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
|
||||||
|
|
||||||
|
reveal_type(t[0:]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
|
||||||
|
reveal_type(t[2:]) # revealed: tuple[None, Literal[b"b"]]
|
||||||
|
reveal_type(t[4:]) # revealed: tuple[()]
|
||||||
|
reveal_type(t[:0]) # revealed: tuple[()]
|
||||||
|
reveal_type(t[:2]) # revealed: tuple[Literal[1], Literal["a"]]
|
||||||
|
reveal_type(t[:10]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
|
||||||
|
reveal_type(t[:]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
|
||||||
|
|
||||||
|
reveal_type(t[::-1]) # revealed: tuple[Literal[b"b"], None, Literal["a"], Literal[1]]
|
||||||
|
reveal_type(t[::2]) # revealed: tuple[Literal[1], None]
|
||||||
|
reveal_type(t[-2:-5:-1]) # revealed: tuple[None, Literal["a"], Literal[1]]
|
||||||
|
reveal_type(t[::-2]) # revealed: tuple[Literal[b"b"], Literal["a"]]
|
||||||
|
reveal_type(t[-1::-3]) # revealed: tuple[Literal[b"b"], Literal[1]]
|
||||||
|
|
||||||
|
reveal_type(t[None:2:None]) # revealed: tuple[Literal[1], Literal["a"]]
|
||||||
|
reveal_type(t[1:None:1]) # revealed: tuple[Literal["a"], None, Literal[b"b"]]
|
||||||
|
reveal_type(t[None:None:None]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
|
||||||
|
|
||||||
|
start = 1
|
||||||
|
stop = None
|
||||||
|
step = 2
|
||||||
|
reveal_type(t[start:stop:step]) # revealed: tuple[Literal["a"], Literal[b"b"]]
|
||||||
|
|
||||||
|
reveal_type(t[False:True]) # revealed: tuple[Literal[1]]
|
||||||
|
reveal_type(t[True:3]) # revealed: tuple[Literal["a"], None]
|
||||||
|
|
||||||
|
t[0:4:0] # error: [zero-stepsize-in-slice]
|
||||||
|
t[:4:0] # error: [zero-stepsize-in-slice]
|
||||||
|
t[0::0] # error: [zero-stepsize-in-slice]
|
||||||
|
t[::0] # error: [zero-stepsize-in-slice]
|
||||||
|
|
||||||
|
def int_instance() -> int: ...
|
||||||
|
|
||||||
|
tuple_slice = t[int_instance() : int_instance()]
|
||||||
|
# TODO: Support overloads... Should be `tuple[Literal[1, 'a', b"b"] | None, ...]`
|
||||||
|
reveal_type(tuple_slice) # revealed: @Todo
|
||||||
|
```
|
||||||
|
|
|
@ -283,6 +283,8 @@ pub enum Type<'db> {
|
||||||
LiteralString,
|
LiteralString,
|
||||||
/// A bytes literal
|
/// A bytes literal
|
||||||
BytesLiteral(BytesLiteralType<'db>),
|
BytesLiteral(BytesLiteralType<'db>),
|
||||||
|
/// A slice literal, e.g. `1:5`, `10:0:-1` or `:`
|
||||||
|
SliceLiteral(SliceLiteralType<'db>),
|
||||||
/// A heterogeneous tuple type, with elements of the given types in source order.
|
/// A heterogeneous tuple type, with elements of the given types in source order.
|
||||||
// TODO: Support variable length homogeneous tuple type like `tuple[int, ...]`.
|
// TODO: Support variable length homogeneous tuple type like `tuple[int, ...]`.
|
||||||
Tuple(TupleType<'db>),
|
Tuple(TupleType<'db>),
|
||||||
|
@ -553,6 +555,7 @@ impl<'db> Type<'db> {
|
||||||
| Type::IntLiteral(..)
|
| Type::IntLiteral(..)
|
||||||
| Type::StringLiteral(..)
|
| Type::StringLiteral(..)
|
||||||
| Type::BytesLiteral(..)
|
| Type::BytesLiteral(..)
|
||||||
|
| Type::SliceLiteral(..)
|
||||||
| Type::FunctionLiteral(..)
|
| Type::FunctionLiteral(..)
|
||||||
| Type::ModuleLiteral(..)
|
| Type::ModuleLiteral(..)
|
||||||
| Type::ClassLiteral(..)),
|
| Type::ClassLiteral(..)),
|
||||||
|
@ -561,6 +564,7 @@ impl<'db> Type<'db> {
|
||||||
| Type::IntLiteral(..)
|
| Type::IntLiteral(..)
|
||||||
| Type::StringLiteral(..)
|
| Type::StringLiteral(..)
|
||||||
| Type::BytesLiteral(..)
|
| Type::BytesLiteral(..)
|
||||||
|
| Type::SliceLiteral(..)
|
||||||
| Type::FunctionLiteral(..)
|
| Type::FunctionLiteral(..)
|
||||||
| Type::ModuleLiteral(..)
|
| Type::ModuleLiteral(..)
|
||||||
| Type::ClassLiteral(..)),
|
| Type::ClassLiteral(..)),
|
||||||
|
@ -612,6 +616,13 @@ impl<'db> Type<'db> {
|
||||||
),
|
),
|
||||||
(Type::BytesLiteral(..), _) | (_, Type::BytesLiteral(..)) => true,
|
(Type::BytesLiteral(..), _) | (_, Type::BytesLiteral(..)) => true,
|
||||||
|
|
||||||
|
(Type::SliceLiteral(..), Type::Instance(class_type))
|
||||||
|
| (Type::Instance(class_type), Type::SliceLiteral(..)) => !matches!(
|
||||||
|
class_type.known(db),
|
||||||
|
Some(KnownClass::Slice | KnownClass::Object)
|
||||||
|
),
|
||||||
|
(Type::SliceLiteral(..), _) | (_, Type::SliceLiteral(..)) => true,
|
||||||
|
|
||||||
(
|
(
|
||||||
Type::FunctionLiteral(..) | Type::ModuleLiteral(..) | Type::ClassLiteral(..),
|
Type::FunctionLiteral(..) | Type::ModuleLiteral(..) | Type::ClassLiteral(..),
|
||||||
Type::Instance(class_type),
|
Type::Instance(class_type),
|
||||||
|
@ -673,6 +684,7 @@ impl<'db> Type<'db> {
|
||||||
| Type::IntLiteral(..)
|
| Type::IntLiteral(..)
|
||||||
| Type::StringLiteral(..)
|
| Type::StringLiteral(..)
|
||||||
| Type::BytesLiteral(..)
|
| Type::BytesLiteral(..)
|
||||||
|
| Type::SliceLiteral(..)
|
||||||
| Type::LiteralString => {
|
| Type::LiteralString => {
|
||||||
// Note: The literal types included in this pattern are not true singletons.
|
// Note: The literal types included in this pattern are not true singletons.
|
||||||
// There can be multiple Python objects (at different memory locations) that
|
// There can be multiple Python objects (at different memory locations) that
|
||||||
|
@ -717,7 +729,8 @@ impl<'db> Type<'db> {
|
||||||
| Type::IntLiteral(..)
|
| Type::IntLiteral(..)
|
||||||
| Type::BooleanLiteral(..)
|
| Type::BooleanLiteral(..)
|
||||||
| Type::StringLiteral(..)
|
| Type::StringLiteral(..)
|
||||||
| Type::BytesLiteral(..) => true,
|
| Type::BytesLiteral(..)
|
||||||
|
| Type::SliceLiteral(..) => true,
|
||||||
|
|
||||||
Type::Tuple(tuple) => tuple
|
Type::Tuple(tuple) => tuple
|
||||||
.elements(db)
|
.elements(db)
|
||||||
|
@ -738,6 +751,7 @@ impl<'db> Type<'db> {
|
||||||
| KnownClass::Tuple
|
| KnownClass::Tuple
|
||||||
| KnownClass::Set
|
| KnownClass::Set
|
||||||
| KnownClass::Dict
|
| KnownClass::Dict
|
||||||
|
| KnownClass::Slice
|
||||||
| KnownClass::GenericAlias
|
| KnownClass::GenericAlias
|
||||||
| KnownClass::ModuleType
|
| KnownClass::ModuleType
|
||||||
| KnownClass::FunctionType,
|
| KnownClass::FunctionType,
|
||||||
|
@ -818,6 +832,10 @@ impl<'db> Type<'db> {
|
||||||
// TODO defer to Type::Instance(<bytes from typeshed>).member
|
// TODO defer to Type::Instance(<bytes from typeshed>).member
|
||||||
Type::Todo
|
Type::Todo
|
||||||
}
|
}
|
||||||
|
Type::SliceLiteral(_) => {
|
||||||
|
// TODO defer to `builtins.slice` methods
|
||||||
|
Type::Todo
|
||||||
|
}
|
||||||
Type::Tuple(_) => {
|
Type::Tuple(_) => {
|
||||||
// TODO: implement tuple methods
|
// TODO: implement tuple methods
|
||||||
Type::Todo
|
Type::Todo
|
||||||
|
@ -872,6 +890,7 @@ impl<'db> Type<'db> {
|
||||||
Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()),
|
Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()),
|
||||||
Type::LiteralString => Truthiness::Ambiguous,
|
Type::LiteralString => Truthiness::Ambiguous,
|
||||||
Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()),
|
Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()),
|
||||||
|
Type::SliceLiteral(_) => Truthiness::AlwaysTrue,
|
||||||
Type::Tuple(items) => Truthiness::from(!items.elements(db).is_empty()),
|
Type::Tuple(items) => Truthiness::from(!items.elements(db).is_empty()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1028,6 +1047,7 @@ impl<'db> Type<'db> {
|
||||||
| Type::ModuleLiteral(_)
|
| Type::ModuleLiteral(_)
|
||||||
| Type::IntLiteral(_)
|
| Type::IntLiteral(_)
|
||||||
| Type::StringLiteral(_)
|
| Type::StringLiteral(_)
|
||||||
|
| Type::SliceLiteral(_)
|
||||||
| Type::Tuple(_)
|
| Type::Tuple(_)
|
||||||
| Type::LiteralString
|
| Type::LiteralString
|
||||||
| Type::None => Type::Unknown,
|
| Type::None => Type::Unknown,
|
||||||
|
@ -1045,6 +1065,7 @@ impl<'db> Type<'db> {
|
||||||
Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)),
|
Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)),
|
||||||
Type::BooleanLiteral(_) => KnownClass::Bool.to_class(db),
|
Type::BooleanLiteral(_) => KnownClass::Bool.to_class(db),
|
||||||
Type::BytesLiteral(_) => KnownClass::Bytes.to_class(db),
|
Type::BytesLiteral(_) => KnownClass::Bytes.to_class(db),
|
||||||
|
Type::SliceLiteral(_) => KnownClass::Slice.to_class(db),
|
||||||
Type::IntLiteral(_) => KnownClass::Int.to_class(db),
|
Type::IntLiteral(_) => KnownClass::Int.to_class(db),
|
||||||
Type::FunctionLiteral(_) => KnownClass::FunctionType.to_class(db),
|
Type::FunctionLiteral(_) => KnownClass::FunctionType.to_class(db),
|
||||||
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class(db),
|
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class(db),
|
||||||
|
@ -1128,6 +1149,7 @@ pub enum KnownClass {
|
||||||
Tuple,
|
Tuple,
|
||||||
Set,
|
Set,
|
||||||
Dict,
|
Dict,
|
||||||
|
Slice,
|
||||||
// Types
|
// Types
|
||||||
GenericAlias,
|
GenericAlias,
|
||||||
ModuleType,
|
ModuleType,
|
||||||
|
@ -1150,6 +1172,7 @@ impl<'db> KnownClass {
|
||||||
Self::Dict => "dict",
|
Self::Dict => "dict",
|
||||||
Self::List => "list",
|
Self::List => "list",
|
||||||
Self::Type => "type",
|
Self::Type => "type",
|
||||||
|
Self::Slice => "slice",
|
||||||
Self::GenericAlias => "GenericAlias",
|
Self::GenericAlias => "GenericAlias",
|
||||||
Self::ModuleType => "ModuleType",
|
Self::ModuleType => "ModuleType",
|
||||||
Self::FunctionType => "FunctionType",
|
Self::FunctionType => "FunctionType",
|
||||||
|
@ -1173,7 +1196,8 @@ impl<'db> KnownClass {
|
||||||
| Self::List
|
| Self::List
|
||||||
| Self::Tuple
|
| Self::Tuple
|
||||||
| Self::Set
|
| Self::Set
|
||||||
| Self::Dict => builtins_symbol_ty(db, self.as_str()),
|
| Self::Dict
|
||||||
|
| Self::Slice => builtins_symbol_ty(db, self.as_str()),
|
||||||
Self::GenericAlias | Self::ModuleType | Self::FunctionType => {
|
Self::GenericAlias | Self::ModuleType | Self::FunctionType => {
|
||||||
types_symbol_ty(db, self.as_str())
|
types_symbol_ty(db, self.as_str())
|
||||||
}
|
}
|
||||||
|
@ -1207,6 +1231,7 @@ impl<'db> KnownClass {
|
||||||
"set" => Some(Self::Set),
|
"set" => Some(Self::Set),
|
||||||
"dict" => Some(Self::Dict),
|
"dict" => Some(Self::Dict),
|
||||||
"list" => Some(Self::List),
|
"list" => Some(Self::List),
|
||||||
|
"slice" => Some(Self::Slice),
|
||||||
"GenericAlias" => Some(Self::GenericAlias),
|
"GenericAlias" => Some(Self::GenericAlias),
|
||||||
"NoneType" => Some(Self::NoneType),
|
"NoneType" => Some(Self::NoneType),
|
||||||
"ModuleType" => Some(Self::ModuleType),
|
"ModuleType" => Some(Self::ModuleType),
|
||||||
|
@ -1231,7 +1256,8 @@ impl<'db> KnownClass {
|
||||||
| Self::List
|
| Self::List
|
||||||
| Self::Tuple
|
| Self::Tuple
|
||||||
| Self::Set
|
| Self::Set
|
||||||
| Self::Dict => module.name() == "builtins",
|
| Self::Dict
|
||||||
|
| Self::Slice => module.name() == "builtins",
|
||||||
Self::GenericAlias | Self::ModuleType | Self::FunctionType => module.name() == "types",
|
Self::GenericAlias | Self::ModuleType | Self::FunctionType => module.name() == "types",
|
||||||
Self::NoneType => matches!(module.name().as_str(), "_typeshed" | "types"),
|
Self::NoneType => matches!(module.name().as_str(), "_typeshed" | "types"),
|
||||||
}
|
}
|
||||||
|
@ -1797,6 +1823,13 @@ pub struct BytesLiteralType<'db> {
|
||||||
value: Box<[u8]>,
|
value: Box<[u8]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[salsa::interned]
|
||||||
|
pub struct SliceLiteralType<'db> {
|
||||||
|
start: Option<i32>,
|
||||||
|
stop: Option<i32>,
|
||||||
|
step: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
#[salsa::interned]
|
#[salsa::interned]
|
||||||
pub struct TupleType<'db> {
|
pub struct TupleType<'db> {
|
||||||
#[return_ref]
|
#[return_ref]
|
||||||
|
|
|
@ -90,6 +90,28 @@ impl Display for DisplayRepresentation<'_> {
|
||||||
|
|
||||||
escape.bytes_repr().write(f)
|
escape.bytes_repr().write(f)
|
||||||
}
|
}
|
||||||
|
Type::SliceLiteral(slice) => {
|
||||||
|
f.write_str("slice[")?;
|
||||||
|
if let Some(start) = slice.start(self.db) {
|
||||||
|
write!(f, "Literal[{start}]")?;
|
||||||
|
} else {
|
||||||
|
f.write_str("None")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.write_str(", ")?;
|
||||||
|
|
||||||
|
if let Some(stop) = slice.stop(self.db) {
|
||||||
|
write!(f, "Literal[{stop}]")?;
|
||||||
|
} else {
|
||||||
|
f.write_str("None")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(step) = slice.step(self.db) {
|
||||||
|
write!(f, ", Literal[{step}]")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.write_str("]")
|
||||||
|
}
|
||||||
Type::Tuple(tuple) => {
|
Type::Tuple(tuple) => {
|
||||||
f.write_str("tuple[")?;
|
f.write_str("tuple[")?;
|
||||||
let elements = tuple.elements(self.db);
|
let elements = tuple.elements(self.db);
|
||||||
|
@ -301,7 +323,9 @@ mod tests {
|
||||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||||
|
|
||||||
use crate::db::tests::TestDb;
|
use crate::db::tests::TestDb;
|
||||||
use crate::types::{global_symbol_ty, BytesLiteralType, StringLiteralType, Type, UnionType};
|
use crate::types::{
|
||||||
|
global_symbol_ty, BytesLiteralType, SliceLiteralType, StringLiteralType, Type, UnionType,
|
||||||
|
};
|
||||||
use crate::{Program, ProgramSettings, PythonVersion, SearchPathSettings};
|
use crate::{Program, ProgramSettings, PythonVersion, SearchPathSettings};
|
||||||
|
|
||||||
fn setup_db() -> TestDb {
|
fn setup_db() -> TestDb {
|
||||||
|
@ -376,4 +400,46 @@ mod tests {
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_slice_literal_display() {
|
||||||
|
let db = setup_db();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Type::SliceLiteral(SliceLiteralType::new(&db, None, None, None))
|
||||||
|
.display(&db)
|
||||||
|
.to_string(),
|
||||||
|
"slice[None, None]"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Type::SliceLiteral(SliceLiteralType::new(&db, Some(1), None, None))
|
||||||
|
.display(&db)
|
||||||
|
.to_string(),
|
||||||
|
"slice[Literal[1], None]"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Type::SliceLiteral(SliceLiteralType::new(&db, None, Some(2), None))
|
||||||
|
.display(&db)
|
||||||
|
.to_string(),
|
||||||
|
"slice[None, Literal[2]]"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Type::SliceLiteral(SliceLiteralType::new(&db, Some(1), Some(5), None))
|
||||||
|
.display(&db)
|
||||||
|
.to_string(),
|
||||||
|
"slice[Literal[1], Literal[5]]"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Type::SliceLiteral(SliceLiteralType::new(&db, Some(1), Some(5), Some(2)))
|
||||||
|
.display(&db)
|
||||||
|
.to_string(),
|
||||||
|
"slice[Literal[1], Literal[5], Literal[2]]"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Type::SliceLiteral(SliceLiteralType::new(&db, None, None, Some(2)))
|
||||||
|
.display(&db)
|
||||||
|
.to_string(),
|
||||||
|
"slice[None, None, Literal[2]]"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,14 +52,13 @@ use crate::stdlib::builtins_module_scope;
|
||||||
use crate::types::diagnostic::{TypeCheckDiagnostic, TypeCheckDiagnostics};
|
use crate::types::diagnostic::{TypeCheckDiagnostic, TypeCheckDiagnostics};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
bindings_ty, builtins_symbol_ty, declarations_ty, global_symbol_ty, symbol_ty,
|
bindings_ty, builtins_symbol_ty, declarations_ty, global_symbol_ty, symbol_ty,
|
||||||
typing_extensions_symbol_ty, BytesLiteralType, ClassType, FunctionType, KnownFunction,
|
typing_extensions_symbol_ty, BytesLiteralType, ClassType, FunctionType, IterationOutcome,
|
||||||
StringLiteralType, Truthiness, TupleType, Type, TypeArrayDisplay, UnionType,
|
KnownClass, KnownFunction, SliceLiteralType, StringLiteralType, Truthiness, TupleType, Type,
|
||||||
|
TypeArrayDisplay, UnionBuilder, UnionType,
|
||||||
};
|
};
|
||||||
use crate::util::subscript::PythonSubscript;
|
use crate::util::subscript::{PyIndex, PySlice};
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
|
|
||||||
use super::{IterationOutcome, KnownClass, UnionBuilder};
|
|
||||||
|
|
||||||
/// Infer all types for a [`ScopeId`], including all definitions and expressions in that scope.
|
/// Infer all types for a [`ScopeId`], including all definitions and expressions in that scope.
|
||||||
/// Use when checking a scope, or needing to provide a type for an arbitrary expression in the
|
/// Use when checking a scope, or needing to provide a type for an arbitrary expression in the
|
||||||
/// scope.
|
/// scope.
|
||||||
|
@ -1483,6 +1482,14 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn slice_step_size_zero_diagnostic(&mut self, node: AnyNodeRef) {
|
||||||
|
self.add_diagnostic(
|
||||||
|
node,
|
||||||
|
"zero-stepsize-in-slice",
|
||||||
|
format_args!("Slice step size can not be zero"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Emit a diagnostic declaring that a type does not support subscripting.
|
/// Emit a diagnostic declaring that a type does not support subscripting.
|
||||||
pub(super) fn non_subscriptable_diagnostic(
|
pub(super) fn non_subscriptable_diagnostic(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -3199,13 +3206,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
) -> Type<'db> {
|
) -> Type<'db> {
|
||||||
match (value_ty, slice_ty) {
|
match (value_ty, slice_ty) {
|
||||||
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
|
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
|
||||||
(Type::Tuple(tuple_ty), Type::IntLiteral(int)) => {
|
(Type::Tuple(tuple_ty), Type::IntLiteral(int)) if i32::try_from(int).is_ok() => {
|
||||||
let elements = tuple_ty.elements(self.db);
|
let elements = tuple_ty.elements(self.db);
|
||||||
elements
|
elements
|
||||||
.iter()
|
.iter()
|
||||||
.python_subscript(int)
|
.py_index(i32::try_from(int).expect("checked in branch arm"))
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|_| {
|
||||||
self.index_out_of_bounds_diagnostic(
|
self.index_out_of_bounds_diagnostic(
|
||||||
"tuple",
|
"tuple",
|
||||||
value_node.into(),
|
value_node.into(),
|
||||||
|
@ -3216,25 +3223,36 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Type::Unknown
|
Type::Unknown
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Ex) Given `("a", "b", "c", "d")[True]`, return `"b"`
|
// Ex) Given `("a", 1, Null)[0:2]`, return `("a", 1)`
|
||||||
(Type::Tuple(_), Type::BooleanLiteral(bool)) => self.infer_subscript_expression_types(
|
(Type::Tuple(tuple_ty), Type::SliceLiteral(slice_ty)) => {
|
||||||
value_node,
|
let elements = tuple_ty.elements(self.db);
|
||||||
value_ty,
|
let start = slice_ty.start(self.db);
|
||||||
Type::IntLiteral(i64::from(bool)),
|
let stop = slice_ty.stop(self.db);
|
||||||
),
|
let step = slice_ty.step(self.db);
|
||||||
|
|
||||||
|
if let Ok(new_elements) = elements.as_ref().py_slice(start, stop, step) {
|
||||||
|
let new_elements: Vec<_> = new_elements.copied().collect();
|
||||||
|
Type::Tuple(TupleType::new(self.db, new_elements.into_boxed_slice()))
|
||||||
|
} else {
|
||||||
|
self.slice_step_size_zero_diagnostic(value_node.into());
|
||||||
|
Type::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
// Ex) Given `"value"[1]`, return `"a"`
|
// Ex) Given `"value"[1]`, return `"a"`
|
||||||
(Type::StringLiteral(literal_ty), Type::IntLiteral(int)) => {
|
(Type::StringLiteral(literal_ty), Type::IntLiteral(int))
|
||||||
|
if i32::try_from(int).is_ok() =>
|
||||||
|
{
|
||||||
let literal_value = literal_ty.value(self.db);
|
let literal_value = literal_ty.value(self.db);
|
||||||
literal_value
|
literal_value
|
||||||
.chars()
|
.chars()
|
||||||
.python_subscript(int)
|
.py_index(i32::try_from(int).expect("checked in branch arm"))
|
||||||
.map(|ch| {
|
.map(|ch| {
|
||||||
Type::StringLiteral(StringLiteralType::new(
|
Type::StringLiteral(StringLiteralType::new(
|
||||||
self.db,
|
self.db,
|
||||||
ch.to_string().into_boxed_str(),
|
ch.to_string().into_boxed_str(),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|_| {
|
||||||
self.index_out_of_bounds_diagnostic(
|
self.index_out_of_bounds_diagnostic(
|
||||||
"string",
|
"string",
|
||||||
value_node.into(),
|
value_node.into(),
|
||||||
|
@ -3245,16 +3263,35 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Type::Unknown
|
Type::Unknown
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// Ex) Given `"value"[1:3]`, return `"al"`
|
||||||
|
(Type::StringLiteral(literal_ty), Type::SliceLiteral(slice_ty)) => {
|
||||||
|
let literal_value = literal_ty.value(self.db);
|
||||||
|
let start = slice_ty.start(self.db);
|
||||||
|
let stop = slice_ty.stop(self.db);
|
||||||
|
let step = slice_ty.step(self.db);
|
||||||
|
|
||||||
|
let chars: Vec<_> = literal_value.chars().collect();
|
||||||
|
let result = if let Ok(new_chars) = chars.as_slice().py_slice(start, stop, step) {
|
||||||
|
let literal: String = new_chars.collect();
|
||||||
|
Type::StringLiteral(StringLiteralType::new(self.db, literal.into_boxed_str()))
|
||||||
|
} else {
|
||||||
|
self.slice_step_size_zero_diagnostic(value_node.into());
|
||||||
|
Type::Unknown
|
||||||
|
};
|
||||||
|
result
|
||||||
|
}
|
||||||
// Ex) Given `b"value"[1]`, return `b"a"`
|
// Ex) Given `b"value"[1]`, return `b"a"`
|
||||||
(Type::BytesLiteral(literal_ty), Type::IntLiteral(int)) => {
|
(Type::BytesLiteral(literal_ty), Type::IntLiteral(int))
|
||||||
|
if i32::try_from(int).is_ok() =>
|
||||||
|
{
|
||||||
let literal_value = literal_ty.value(self.db);
|
let literal_value = literal_ty.value(self.db);
|
||||||
literal_value
|
literal_value
|
||||||
.iter()
|
.iter()
|
||||||
.python_subscript(int)
|
.py_index(i32::try_from(int).expect("checked in branch arm"))
|
||||||
.map(|byte| {
|
.map(|byte| {
|
||||||
Type::BytesLiteral(BytesLiteralType::new(self.db, [*byte].as_slice()))
|
Type::BytesLiteral(BytesLiteralType::new(self.db, [*byte].as_slice()))
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|_| {
|
||||||
self.index_out_of_bounds_diagnostic(
|
self.index_out_of_bounds_diagnostic(
|
||||||
"bytes literal",
|
"bytes literal",
|
||||||
value_node.into(),
|
value_node.into(),
|
||||||
|
@ -3265,13 +3302,30 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Type::Unknown
|
Type::Unknown
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// Ex) Given `b"value"[1:3]`, return `b"al"`
|
||||||
|
(Type::BytesLiteral(literal_ty), Type::SliceLiteral(slice_ty)) => {
|
||||||
|
let literal_value = literal_ty.value(self.db);
|
||||||
|
let start = slice_ty.start(self.db);
|
||||||
|
let stop = slice_ty.stop(self.db);
|
||||||
|
let step = slice_ty.step(self.db);
|
||||||
|
|
||||||
|
if let Ok(new_bytes) = literal_value.as_ref().py_slice(start, stop, step) {
|
||||||
|
let new_bytes: Vec<u8> = new_bytes.copied().collect();
|
||||||
|
Type::BytesLiteral(BytesLiteralType::new(self.db, new_bytes.into_boxed_slice()))
|
||||||
|
} else {
|
||||||
|
self.slice_step_size_zero_diagnostic(value_node.into());
|
||||||
|
Type::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
// Ex) Given `"value"[True]`, return `"a"`
|
// Ex) Given `"value"[True]`, return `"a"`
|
||||||
(Type::StringLiteral(_) | Type::BytesLiteral(_), Type::BooleanLiteral(bool)) => self
|
(
|
||||||
.infer_subscript_expression_types(
|
Type::Tuple(_) | Type::StringLiteral(_) | Type::BytesLiteral(_),
|
||||||
value_node,
|
Type::BooleanLiteral(bool),
|
||||||
value_ty,
|
) => self.infer_subscript_expression_types(
|
||||||
Type::IntLiteral(i64::from(bool)),
|
value_node,
|
||||||
),
|
value_ty,
|
||||||
|
Type::IntLiteral(i64::from(bool)),
|
||||||
|
),
|
||||||
(value_ty, slice_ty) => {
|
(value_ty, slice_ty) => {
|
||||||
// Resolve the value to its class.
|
// Resolve the value to its class.
|
||||||
let value_meta_ty = value_ty.to_meta_type(self.db);
|
let value_meta_ty = value_ty.to_meta_type(self.db);
|
||||||
|
@ -3347,6 +3401,11 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_slice_expression(&mut self, slice: &ast::ExprSlice) -> Type<'db> {
|
fn infer_slice_expression(&mut self, slice: &ast::ExprSlice) -> Type<'db> {
|
||||||
|
enum SliceArg {
|
||||||
|
Arg(Option<i32>),
|
||||||
|
Unsupported,
|
||||||
|
}
|
||||||
|
|
||||||
let ast::ExprSlice {
|
let ast::ExprSlice {
|
||||||
range: _,
|
range: _,
|
||||||
lower,
|
lower,
|
||||||
|
@ -3354,12 +3413,33 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
step,
|
step,
|
||||||
} = slice;
|
} = slice;
|
||||||
|
|
||||||
self.infer_optional_expression(lower.as_deref());
|
let ty_lower = self.infer_optional_expression(lower.as_deref());
|
||||||
self.infer_optional_expression(upper.as_deref());
|
let ty_upper = self.infer_optional_expression(upper.as_deref());
|
||||||
self.infer_optional_expression(step.as_deref());
|
let ty_step = self.infer_optional_expression(step.as_deref());
|
||||||
|
|
||||||
// TODO slice
|
let type_to_slice_argument = |ty: Option<Type<'db>>| match ty {
|
||||||
Type::Todo
|
Some(Type::IntLiteral(n)) if i32::try_from(n).is_ok() => {
|
||||||
|
SliceArg::Arg(Some(i32::try_from(n).expect("checked in branch arm")))
|
||||||
|
}
|
||||||
|
Some(Type::BooleanLiteral(b)) => SliceArg::Arg(Some(i32::from(b))),
|
||||||
|
Some(Type::None) => SliceArg::Arg(None),
|
||||||
|
Some(Type::Instance(class)) if class.is_known(self.db, KnownClass::NoneType) => {
|
||||||
|
SliceArg::Arg(None)
|
||||||
|
}
|
||||||
|
None => SliceArg::Arg(None),
|
||||||
|
_ => SliceArg::Unsupported,
|
||||||
|
};
|
||||||
|
|
||||||
|
match (
|
||||||
|
type_to_slice_argument(ty_lower),
|
||||||
|
type_to_slice_argument(ty_upper),
|
||||||
|
type_to_slice_argument(ty_step),
|
||||||
|
) {
|
||||||
|
(SliceArg::Arg(lower), SliceArg::Arg(upper), SliceArg::Arg(step)) => {
|
||||||
|
Type::SliceLiteral(SliceLiteralType::new(self.db, lower, upper, step))
|
||||||
|
}
|
||||||
|
_ => KnownClass::Slice.to_instance(self.db),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_type_parameters(&mut self, type_parameters: &ast::TypeParams) {
|
fn infer_type_parameters(&mut self, type_parameters: &ast::TypeParams) {
|
||||||
|
|
|
@ -1,18 +1,192 @@
|
||||||
pub(crate) trait PythonSubscript {
|
//! This module provides utility functions for indexing (`PyIndex`) and slicing
|
||||||
|
//! operations (`PySlice`) on iterators, following the semantics of equivalent
|
||||||
|
//! operations in Python.
|
||||||
|
|
||||||
|
use itertools::Either;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub(crate) struct OutOfBoundsError;
|
||||||
|
|
||||||
|
pub(crate) trait PyIndex {
|
||||||
type Item;
|
type Item;
|
||||||
|
|
||||||
fn python_subscript(&mut self, index: i64) -> Option<Self::Item>;
|
fn py_index(&mut self, index: i32) -> Result<Self::Item, OutOfBoundsError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I, T: DoubleEndedIterator<Item = I>> PythonSubscript for T {
|
fn from_nonnegative_i32(index: i32) -> usize {
|
||||||
|
static_assertions::const_assert!(usize::BITS >= 32);
|
||||||
|
debug_assert!(index >= 0);
|
||||||
|
|
||||||
|
// SAFETY: `index` is non-negative, and `usize` is at least 32 bits.
|
||||||
|
usize::try_from(index).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_negative_i32(index: i32) -> usize {
|
||||||
|
static_assertions::const_assert!(usize::BITS >= 32);
|
||||||
|
|
||||||
|
index.checked_neg().map(from_nonnegative_i32).unwrap_or({
|
||||||
|
// 'checked_neg' only fails for i32::MIN. We can not
|
||||||
|
// represent -i32::MIN as a i32, but we can represent
|
||||||
|
// it as a usize, since usize is at least 32 bits.
|
||||||
|
from_nonnegative_i32(i32::MAX) + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||||
|
enum Position {
|
||||||
|
BeforeStart,
|
||||||
|
AtIndex(usize),
|
||||||
|
AfterEnd,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Nth {
|
||||||
|
FromStart(usize),
|
||||||
|
FromEnd(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Nth {
|
||||||
|
fn from_index(index: i32) -> Self {
|
||||||
|
if index >= 0 {
|
||||||
|
Nth::FromStart(from_nonnegative_i32(index))
|
||||||
|
} else {
|
||||||
|
Nth::FromEnd(from_negative_i32(index) - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_position(&self, len: usize) -> Position {
|
||||||
|
debug_assert!(len > 0);
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Nth::FromStart(nth) => {
|
||||||
|
if *nth < len {
|
||||||
|
Position::AtIndex(*nth)
|
||||||
|
} else {
|
||||||
|
Position::AfterEnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Nth::FromEnd(nth_rev) => {
|
||||||
|
if *nth_rev < len {
|
||||||
|
Position::AtIndex(len - 1 - *nth_rev)
|
||||||
|
} else {
|
||||||
|
Position::BeforeStart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, T> PyIndex for T
|
||||||
|
where
|
||||||
|
T: DoubleEndedIterator<Item = I>,
|
||||||
|
{
|
||||||
type Item = I;
|
type Item = I;
|
||||||
|
|
||||||
fn python_subscript(&mut self, index: i64) -> Option<I> {
|
fn py_index(&mut self, index: i32) -> Result<I, OutOfBoundsError> {
|
||||||
if index >= 0 {
|
match Nth::from_index(index) {
|
||||||
self.nth(usize::try_from(index).ok()?)
|
Nth::FromStart(nth) => self.nth(nth).ok_or(OutOfBoundsError),
|
||||||
|
Nth::FromEnd(nth_rev) => self.nth_back(nth_rev).ok_or(OutOfBoundsError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub(crate) struct StepSizeZeroError;
|
||||||
|
|
||||||
|
pub(crate) trait PySlice {
|
||||||
|
type Item;
|
||||||
|
|
||||||
|
fn py_slice(
|
||||||
|
&self,
|
||||||
|
start: Option<i32>,
|
||||||
|
stop: Option<i32>,
|
||||||
|
step: Option<i32>,
|
||||||
|
) -> Result<
|
||||||
|
Either<impl Iterator<Item = &Self::Item>, impl Iterator<Item = &Self::Item>>,
|
||||||
|
StepSizeZeroError,
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PySlice for &[T] {
|
||||||
|
type Item = T;
|
||||||
|
|
||||||
|
fn py_slice(
|
||||||
|
&self,
|
||||||
|
start: Option<i32>,
|
||||||
|
stop: Option<i32>,
|
||||||
|
step_int: Option<i32>,
|
||||||
|
) -> Result<
|
||||||
|
Either<impl Iterator<Item = &Self::Item>, impl Iterator<Item = &Self::Item>>,
|
||||||
|
StepSizeZeroError,
|
||||||
|
> {
|
||||||
|
let step_int = step_int.unwrap_or(1);
|
||||||
|
if step_int == 0 {
|
||||||
|
return Err(StepSizeZeroError);
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = self.len();
|
||||||
|
if len == 0 {
|
||||||
|
// The iterator needs to have the same type as the step>0 case below,
|
||||||
|
// so we need to use `.skip(0)`.
|
||||||
|
#[allow(clippy::iter_skip_zero)]
|
||||||
|
return Ok(Either::Left(self.iter().skip(0).take(0).step_by(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let to_position = |index| Nth::from_index(index).to_position(len);
|
||||||
|
|
||||||
|
if step_int.is_positive() {
|
||||||
|
let step = from_nonnegative_i32(step_int);
|
||||||
|
|
||||||
|
let start = start.map(to_position).unwrap_or(Position::BeforeStart);
|
||||||
|
let stop = stop.map(to_position).unwrap_or(Position::AfterEnd);
|
||||||
|
|
||||||
|
let (skip, take, step) = if start < stop {
|
||||||
|
let skip = match start {
|
||||||
|
Position::BeforeStart => 0,
|
||||||
|
Position::AtIndex(start_index) => start_index,
|
||||||
|
Position::AfterEnd => len,
|
||||||
|
};
|
||||||
|
|
||||||
|
let take = match stop {
|
||||||
|
Position::BeforeStart => 0,
|
||||||
|
Position::AtIndex(stop_index) => stop_index - skip,
|
||||||
|
Position::AfterEnd => len - skip,
|
||||||
|
};
|
||||||
|
|
||||||
|
(skip, take, step)
|
||||||
|
} else {
|
||||||
|
(0, 0, step)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Either::Left(
|
||||||
|
self.iter().skip(skip).take(take).step_by(step),
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
let nth_rev = usize::try_from(index.checked_neg()?).ok()?.checked_sub(1)?;
|
let step = from_negative_i32(step_int);
|
||||||
self.rev().nth(nth_rev)
|
|
||||||
|
let start = start.map(to_position).unwrap_or(Position::AfterEnd);
|
||||||
|
let stop = stop.map(to_position).unwrap_or(Position::BeforeStart);
|
||||||
|
|
||||||
|
let (skip, take, step) = if start <= stop {
|
||||||
|
(0, 0, step)
|
||||||
|
} else {
|
||||||
|
let skip = match start {
|
||||||
|
Position::BeforeStart => len,
|
||||||
|
Position::AtIndex(start_index) => len - 1 - start_index,
|
||||||
|
Position::AfterEnd => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let take = match stop {
|
||||||
|
Position::BeforeStart => len - skip,
|
||||||
|
Position::AtIndex(stop_index) => (len - 1) - skip - stop_index,
|
||||||
|
Position::AfterEnd => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
(skip, take, step)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Either::Right(
|
||||||
|
self.iter().rev().skip(skip).take(take).step_by(step),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,64 +194,312 @@ impl<I, T: DoubleEndedIterator<Item = I>> PythonSubscript for T {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::redundant_clone)]
|
#[allow(clippy::redundant_clone)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::PythonSubscript;
|
use crate::util::subscript::{OutOfBoundsError, StepSizeZeroError};
|
||||||
|
|
||||||
|
use super::{PyIndex, PySlice};
|
||||||
|
use itertools::assert_equal;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn python_subscript_basic() {
|
fn py_index_empty() {
|
||||||
let iter = 'a'..='e';
|
let iter = std::iter::empty::<char>();
|
||||||
|
|
||||||
assert_eq!(iter.clone().python_subscript(0), Some('a'));
|
assert_eq!(iter.clone().py_index(0), Err(OutOfBoundsError));
|
||||||
assert_eq!(iter.clone().python_subscript(1), Some('b'));
|
assert_eq!(iter.clone().py_index(1), Err(OutOfBoundsError));
|
||||||
assert_eq!(iter.clone().python_subscript(4), Some('e'));
|
assert_eq!(iter.clone().py_index(-1), Err(OutOfBoundsError));
|
||||||
assert_eq!(iter.clone().python_subscript(5), None);
|
assert_eq!(iter.clone().py_index(i32::MIN), Err(OutOfBoundsError));
|
||||||
|
assert_eq!(iter.clone().py_index(i32::MAX), Err(OutOfBoundsError));
|
||||||
assert_eq!(iter.clone().python_subscript(-1), Some('e'));
|
|
||||||
assert_eq!(iter.clone().python_subscript(-2), Some('d'));
|
|
||||||
assert_eq!(iter.clone().python_subscript(-5), Some('a'));
|
|
||||||
assert_eq!(iter.clone().python_subscript(-6), None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn python_subscript_empty() {
|
fn py_index_single_element() {
|
||||||
let iter = 'a'..'a';
|
let iter = ['a'].into_iter();
|
||||||
|
|
||||||
assert_eq!(iter.clone().python_subscript(0), None);
|
assert_eq!(iter.clone().py_index(0), Ok('a'));
|
||||||
assert_eq!(iter.clone().python_subscript(1), None);
|
assert_eq!(iter.clone().py_index(1), Err(OutOfBoundsError));
|
||||||
assert_eq!(iter.clone().python_subscript(-1), None);
|
assert_eq!(iter.clone().py_index(-1), Ok('a'));
|
||||||
|
assert_eq!(iter.clone().py_index(-2), Err(OutOfBoundsError));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn python_subscript_single_element() {
|
fn py_index_more_elements() {
|
||||||
let iter = 'a'..='a';
|
let iter = ['a', 'b', 'c', 'd', 'e'].into_iter();
|
||||||
|
|
||||||
assert_eq!(iter.clone().python_subscript(0), Some('a'));
|
assert_eq!(iter.clone().py_index(0), Ok('a'));
|
||||||
assert_eq!(iter.clone().python_subscript(1), None);
|
assert_eq!(iter.clone().py_index(1), Ok('b'));
|
||||||
assert_eq!(iter.clone().python_subscript(-1), Some('a'));
|
assert_eq!(iter.clone().py_index(4), Ok('e'));
|
||||||
assert_eq!(iter.clone().python_subscript(-2), None);
|
assert_eq!(iter.clone().py_index(5), Err(OutOfBoundsError));
|
||||||
|
|
||||||
|
assert_eq!(iter.clone().py_index(-1), Ok('e'));
|
||||||
|
assert_eq!(iter.clone().py_index(-2), Ok('d'));
|
||||||
|
assert_eq!(iter.clone().py_index(-5), Ok('a'));
|
||||||
|
assert_eq!(iter.clone().py_index(-6), Err(OutOfBoundsError));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn python_subscript_uses_full_index_range() {
|
fn py_index_uses_full_index_range() {
|
||||||
let iter = 0..=u64::MAX;
|
let iter = 0..=u32::MAX;
|
||||||
|
|
||||||
assert_eq!(iter.clone().python_subscript(0), Some(0));
|
// u32::MAX - |i32::MIN| + 1 = 2^32 - 1 - 2^31 + 1 = 2^31
|
||||||
assert_eq!(iter.clone().python_subscript(1), Some(1));
|
assert_eq!(iter.clone().py_index(i32::MIN), Ok(2u32.pow(31)));
|
||||||
assert_eq!(
|
assert_eq!(iter.clone().py_index(-2), Ok(u32::MAX - 2 + 1));
|
||||||
iter.clone().python_subscript(i64::MAX),
|
assert_eq!(iter.clone().py_index(-1), Ok(u32::MAX - 1 + 1));
|
||||||
Some(i64::MAX as u64)
|
|
||||||
|
assert_eq!(iter.clone().py_index(0), Ok(0));
|
||||||
|
assert_eq!(iter.clone().py_index(1), Ok(1));
|
||||||
|
assert_eq!(iter.clone().py_index(i32::MAX), Ok(i32::MAX as u32));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn assert_eq_slice<const N: usize, const M: usize>(
|
||||||
|
input: &[char; N],
|
||||||
|
start: Option<i32>,
|
||||||
|
stop: Option<i32>,
|
||||||
|
step: Option<i32>,
|
||||||
|
expected: &[char; M],
|
||||||
|
) {
|
||||||
|
assert_equal(
|
||||||
|
input.as_slice().py_slice(start, stop, step).unwrap(),
|
||||||
|
expected.iter(),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(iter.clone().python_subscript(-1), Some(u64::MAX));
|
#[test]
|
||||||
assert_eq!(iter.clone().python_subscript(-2), Some(u64::MAX - 1));
|
fn py_slice_empty_input() {
|
||||||
|
let input = [];
|
||||||
|
|
||||||
// i64::MIN is not representable as a positive number, so it is not
|
assert_eq_slice(&input, None, None, None, &[]);
|
||||||
// a valid index:
|
assert_eq_slice(&input, Some(0), None, None, &[]);
|
||||||
assert_eq!(iter.clone().python_subscript(i64::MIN), None);
|
assert_eq_slice(&input, None, Some(0), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(0), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(-5), Some(-5), None, &[]);
|
||||||
|
assert_eq_slice(&input, None, None, Some(-1), &[]);
|
||||||
|
assert_eq_slice(&input, None, None, Some(2), &[]);
|
||||||
|
}
|
||||||
|
|
||||||
// but i64::MIN +1 is:
|
#[test]
|
||||||
assert_eq!(
|
fn py_slice_single_element_input() {
|
||||||
iter.clone().python_subscript(i64::MIN + 1),
|
let input = ['a'];
|
||||||
Some(2u64.pow(63) + 1)
|
|
||||||
);
|
assert_eq_slice(&input, None, None, None, &['a']);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(0), None, None, &['a']);
|
||||||
|
assert_eq_slice(&input, None, Some(0), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(0), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(1), None, &['a']);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(2), None, &['a']);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(-1), None, None, &['a']);
|
||||||
|
assert_eq_slice(&input, Some(-1), Some(-1), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(-1), Some(0), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(-1), Some(1), None, &['a']);
|
||||||
|
assert_eq_slice(&input, Some(-1), Some(2), None, &['a']);
|
||||||
|
assert_eq_slice(&input, None, Some(-1), None, &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(-2), None, None, &['a']);
|
||||||
|
assert_eq_slice(&input, Some(-2), Some(-1), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(-2), Some(0), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(-2), Some(1), None, &['a']);
|
||||||
|
assert_eq_slice(&input, Some(-2), Some(2), None, &['a']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn py_slice_nonnegative_indices() {
|
||||||
|
let input = ['a', 'b', 'c', 'd', 'e'];
|
||||||
|
|
||||||
|
assert_eq_slice(&input, None, Some(0), None, &[]);
|
||||||
|
assert_eq_slice(&input, None, Some(1), None, &['a']);
|
||||||
|
assert_eq_slice(&input, None, Some(4), None, &['a', 'b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&input, None, Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, None, Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, None, None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(0), Some(0), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(1), None, &['a']);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(0), None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(1), Some(0), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(1), Some(1), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(1), Some(2), None, &['b']);
|
||||||
|
assert_eq_slice(&input, Some(1), Some(4), None, &['b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&input, Some(1), Some(5), None, &['b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(1), Some(6), None, &['b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(1), None, None, &['b', 'c', 'd', 'e']);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(4), Some(0), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(4), Some(4), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(4), Some(5), None, &['e']);
|
||||||
|
assert_eq_slice(&input, Some(4), Some(6), None, &['e']);
|
||||||
|
assert_eq_slice(&input, Some(4), None, None, &['e']);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(5), Some(0), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(5), Some(5), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(5), Some(6), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(5), None, None, &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(6), Some(0), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(6), Some(6), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(6), None, None, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn py_slice_negatice_indices() {
|
||||||
|
let input = ['a', 'b', 'c', 'd', 'e'];
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(-6), None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(-4), None, &['a']);
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(-5), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(-6), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(-10), None, &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(-5), None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(-5), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&input, Some(-5), Some(-4), None, &['a']);
|
||||||
|
assert_eq_slice(&input, Some(-5), Some(-5), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(-5), Some(-6), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(-5), Some(-10), None, &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(-4), None, None, &['b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(-4), Some(-1), None, &['b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&input, Some(-4), Some(-3), None, &['b']);
|
||||||
|
assert_eq_slice(&input, Some(-4), Some(-4), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(-4), Some(-10), None, &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(-1), None, None, &['e']);
|
||||||
|
assert_eq_slice(&input, Some(-1), Some(-1), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(-1), Some(-10), None, &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, None, Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&input, None, Some(-4), None, &['a']);
|
||||||
|
assert_eq_slice(&input, None, Some(-5), None, &[]);
|
||||||
|
assert_eq_slice(&input, None, Some(-6), None, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn py_slice_mixed_positive_negative_indices() {
|
||||||
|
let input = ['a', 'b', 'c', 'd', 'e'];
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(0), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&input, Some(1), Some(-1), None, &['b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&input, Some(3), Some(-1), None, &['d']);
|
||||||
|
assert_eq_slice(&input, Some(4), Some(-1), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(5), Some(-1), None, &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(0), Some(-4), None, &['a']);
|
||||||
|
assert_eq_slice(&input, Some(1), Some(-4), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(3), Some(-4), None, &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(0), Some(-5), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(1), Some(-5), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(3), Some(-5), None, &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(0), Some(-6), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(1), Some(-6), None, &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(1), None, &['a']);
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(0), None, &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(-5), Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(-5), Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(-5), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&input, Some(-5), Some(1), None, &['a']);
|
||||||
|
assert_eq_slice(&input, Some(-5), Some(0), None, &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(-4), Some(6), None, &['b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(-4), Some(5), None, &['b', 'c', 'd', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(-4), Some(4), None, &['b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&input, Some(-4), Some(2), None, &['b']);
|
||||||
|
assert_eq_slice(&input, Some(-4), Some(1), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(-4), Some(0), None, &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(-1), Some(6), None, &['e']);
|
||||||
|
assert_eq_slice(&input, Some(-1), Some(5), None, &['e']);
|
||||||
|
assert_eq_slice(&input, Some(-1), Some(4), None, &[]);
|
||||||
|
assert_eq_slice(&input, Some(-1), Some(1), None, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn py_slice_step_forward() {
|
||||||
|
// indices: 0 1 2 3 4 5 6
|
||||||
|
let input = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
|
||||||
|
|
||||||
|
// Step size zero is invalid:
|
||||||
|
assert!(matches!(
|
||||||
|
input.as_slice().py_slice(None, None, Some(0)),
|
||||||
|
Err(StepSizeZeroError)
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
input.as_slice().py_slice(Some(0), Some(5), Some(0)),
|
||||||
|
Err(StepSizeZeroError)
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
input.as_slice().py_slice(Some(0), Some(0), Some(0)),
|
||||||
|
Err(StepSizeZeroError)
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(0), Some(8), Some(2), &['a', 'c', 'e', 'g']);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(7), Some(2), &['a', 'c', 'e', 'g']);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(6), Some(2), &['a', 'c', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(5), Some(2), &['a', 'c', 'e']);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(4), Some(2), &['a', 'c']);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(3), Some(2), &['a', 'c']);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(2), Some(2), &['a']);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(1), Some(2), &['a']);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(0), Some(2), &[]);
|
||||||
|
assert_eq_slice(&input, Some(1), Some(5), Some(2), &['b', 'd']);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(0), Some(7), Some(3), &['a', 'd', 'g']);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(6), Some(3), &['a', 'd']);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(0), None, Some(10), &['a']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn py_slice_step_backward() {
|
||||||
|
// indices: 0 1 2 3 4 5 6
|
||||||
|
let input = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(7), Some(0), Some(-2), &['g', 'e', 'c']);
|
||||||
|
assert_eq_slice(&input, Some(6), Some(0), Some(-2), &['g', 'e', 'c']);
|
||||||
|
assert_eq_slice(&input, Some(5), Some(0), Some(-2), &['f', 'd', 'b']);
|
||||||
|
assert_eq_slice(&input, Some(4), Some(0), Some(-2), &['e', 'c']);
|
||||||
|
assert_eq_slice(&input, Some(3), Some(0), Some(-2), &['d', 'b']);
|
||||||
|
assert_eq_slice(&input, Some(2), Some(0), Some(-2), &['c']);
|
||||||
|
assert_eq_slice(&input, Some(1), Some(0), Some(-2), &['b']);
|
||||||
|
assert_eq_slice(&input, Some(0), Some(0), Some(-2), &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(7), None, Some(-2), &['g', 'e', 'c', 'a']);
|
||||||
|
assert_eq_slice(&input, None, None, Some(-2), &['g', 'e', 'c', 'a']);
|
||||||
|
assert_eq_slice(&input, None, Some(0), Some(-2), &['g', 'e', 'c']);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(5), Some(1), Some(-2), &['f', 'd']);
|
||||||
|
assert_eq_slice(&input, Some(5), Some(2), Some(-2), &['f', 'd']);
|
||||||
|
assert_eq_slice(&input, Some(5), Some(3), Some(-2), &['f']);
|
||||||
|
assert_eq_slice(&input, Some(5), Some(4), Some(-2), &['f']);
|
||||||
|
assert_eq_slice(&input, Some(5), Some(5), Some(-2), &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(6), None, Some(-3), &['g', 'd', 'a']);
|
||||||
|
assert_eq_slice(&input, Some(6), Some(0), Some(-3), &['g', 'd']);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(7), None, Some(-10), &['g']);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(-9), Some(-1), &['b', 'a']);
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(-8), Some(-1), &['b', 'a']);
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(-7), Some(-1), &['b']);
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(-6), Some(-1), &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(-7), Some(-9), Some(-1), &['a']);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(-8), Some(-9), Some(-1), &[]);
|
||||||
|
assert_eq_slice(&input, Some(-9), Some(-9), Some(-1), &[]);
|
||||||
|
|
||||||
|
assert_eq_slice(&input, Some(-6), Some(-2), Some(-1), &[]);
|
||||||
|
assert_eq_slice(&input, Some(-9), Some(-6), Some(-1), &[]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue