diff --git a/crates/red_knot_python_semantic/resources/mdtest/subscript/string.md b/crates/red_knot_python_semantic/resources/mdtest/subscript/string.md index bd47d5b358..ae8099196d 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/subscript/string.md +++ b/crates/red_knot_python_semantic/resources/mdtest/subscript/string.md @@ -86,3 +86,14 @@ substring2 = str_instance()[0:5] # TODO: Support overloads... Should be `str` reveal_type(substring2) # revealed: @Todo ``` + +## Unsupported slice types + +```py +# TODO: It would be great if we raised an error here. This can be done once +# we have support for overloads and generics, and once typeshed has a more +# precise annotation for `str.__getitem__`, that makes use of the generic +# `slice[..]` type. We could then infer `slice[str, str]` here and see that +# it doesn't match the signature of `str.__getitem__`. +"foo"["bar":"baz"] +``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 9a382697ea..81c8ddba70 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1944,6 +1944,11 @@ pub struct SliceLiteralType<'db> { step: Option, } +impl<'db> SliceLiteralType<'db> { + fn as_tuple(self, db: &dyn Db) -> (Option, Option, Option) { + (self.start(db), self.stop(db), self.step(db)) + } +} #[salsa::interned] pub struct TupleType<'db> { #[return_ref] diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 19a97f71c9..133fb11f03 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -3226,9 +3226,7 @@ impl<'db> TypeInferenceBuilder<'db> { // Ex) Given `("a", 1, Null)[0:2]`, return `("a", 1)` (Type::Tuple(tuple_ty), Type::SliceLiteral(slice_ty)) => { let elements = tuple_ty.elements(self.db); - let start = slice_ty.start(self.db); - let stop = slice_ty.stop(self.db); - let step = slice_ty.step(self.db); + let (start, stop, step) = slice_ty.as_tuple(self.db); if let Ok(new_elements) = elements.as_ref().py_slice(start, stop, step) { let new_elements: Vec<_> = new_elements.copied().collect(); @@ -3266,9 +3264,7 @@ impl<'db> TypeInferenceBuilder<'db> { // 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 (start, stop, step) = slice_ty.as_tuple(self.db); let chars: Vec<_> = literal_value.chars().collect(); let result = if let Ok(new_chars) = chars.as_slice().py_slice(start, stop, step) { @@ -3305,9 +3301,7 @@ impl<'db> TypeInferenceBuilder<'db> { // 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); + let (start, stop, step) = slice_ty.as_tuple(self.db); if let Ok(new_bytes) = literal_value.as_ref().py_slice(start, stop, step) { let new_bytes: Vec = new_bytes.copied().collect(); @@ -3418,9 +3412,10 @@ impl<'db> TypeInferenceBuilder<'db> { let ty_step = self.infer_optional_expression(step.as_deref()); let type_to_slice_argument = |ty: Option>| match ty { - 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::IntLiteral(n)) => match i32::try_from(n) { + Ok(n) => SliceArg::Arg(Some(n)), + Err(_) => SliceArg::Unsupported, + }, 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) => { diff --git a/crates/red_knot_python_semantic/src/util/subscript.rs b/crates/red_knot_python_semantic/src/util/subscript.rs index dfbc891940..9dbfbbdb2d 100644 --- a/crates/red_knot_python_semantic/src/util/subscript.rs +++ b/crates/red_knot_python_semantic/src/util/subscript.rs @@ -17,8 +17,8 @@ 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() + usize::try_from(index) + .expect("Should only ever pass a positive integer to `from_nonnegative_i32`") } fn from_negative_i32(index: i32) -> usize {