[ty] Diagnose zero-step slices on lists#25966
Conversation
Typing conformance resultsNo changes detected ✅Current numbersThe percentage of diagnostics emitted that were expected errors held steady at 94.36%. The percentage of expected errors that received a diagnostic held steady at 88.91%. The number of fully passing files held steady at 93/134. |
c83b6c6 to
4545d0a
Compare
Memory usage reportSummary
Significant changesClick to expand detailed breakdownflake8
sphinx
trio
prefect
|
|
|
Hmm, to me it seems strange to special-case >>> bytearray(b"foo")[0:2:0]
Traceback (most recent call last):
File "<python-input-0>", line 1, in <module>
bytearray(b"foo")[0:2:0]
~~~~~~~~~~~~~~~~~^^^^^^^
ValueError: slice step cannot be zero
>>> memoryview(b"foo")[0:2:0]
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
memoryview(b"foo")[0:2:0]
~~~~~~~~~~~~~~~~~~^^^^^^^
ValueError: slice step cannot be zero
>>> range(42)[0:2:0]
Traceback (most recent call last):
File "<python-input-2>", line 1, in <module>
range(42)[0:2:0]
~~~~~~~~~^^^^^^^
ValueError: slice step cannot be zerowe don't emit errors for any of these on your branch, yet they will all fail at runtime for at least some inhabitants of the type: bytearray(b"foo")[0:2:0]
memoryview(b"foo")[0:2:0]
range(42)[0:2:0]
def f(x: str, y: bytes):
x[1:1:0]
y[1:1:0]The existing places where we emit |
|
I broadened it. |
| fn builtin_sequence_rejects_zero_step<'db>( | ||
| db: &'db dyn Db, | ||
| instance: NominalInstanceType<'db>, | ||
| ) -> bool { | ||
| let class = instance.class_literal(db); | ||
| matches!( | ||
| class.name(db).as_str(), | ||
| "list" | "bytearray" | "memoryview" | "range" | "bytes" | "str" | ||
| ) && file_to_module(db, class.file(db)) | ||
| .is_some_and(|module| module.is_known(db, KnownModule::Builtins)) | ||
| } |
There was a problem hiding this comment.
sort-of the whole point of having the KnownClass enum is so that that we don't have to do ad-hoc matches on strings everywhere like this. So I'd just add KnownClass::Range and KnownClass::Memoryview variants -- then this can be
diff --git a/crates/ty_python_semantic/src/types/subscript.rs b/crates/ty_python_semantic/src/types/subscript.rs
index 1e47fa6980..6ddc18f1d0 100644
--- a/crates/ty_python_semantic/src/types/subscript.rs
+++ b/crates/ty_python_semantic/src/types/subscript.rs
@@ -523,18 +523,6 @@ fn typed_dict_subscript<'db>(
)
}
-fn builtin_sequence_rejects_zero_step<'db>(
- db: &'db dyn Db,
- instance: NominalInstanceType<'db>,
-) -> bool {
- let class = instance.class_literal(db);
- matches!(
- class.name(db).as_str(),
- "list" | "bytearray" | "memoryview" | "range" | "bytes" | "str"
- ) && file_to_module(db, class.file(db))
- .is_some_and(|module| module.is_known(db, KnownModule::Builtins))
-}
-
impl<'db> Type<'db> {
pub(super) fn subscript(
self,
@@ -599,7 +587,18 @@ impl<'db> Type<'db> {
(
Type::NominalInstance(maybe_sequence_nominal),
Type::NominalInstance(maybe_slice_nominal),
- ) if builtin_sequence_rejects_zero_step(db, maybe_sequence_nominal)
+ ) if matches!(
+ maybe_sequence_nominal.known_class(db),
+ Some(
+ KnownClass::List
+ | KnownClass::Tuple
+ | KnownClass::Str
+ | KnownClass::Bytes
+ | KnownClass::Bytearray
+ | KnownClass::Range
+ | KnownClass::Memoryview
+ )
+ )
&& maybe_slice_nominal
.slice_literal(db)
.is_some_and(|slice| slice.step == Some(0)) =>
Summary
A slice with a step size of zero always fails for an exact
list, but we currently only reportzero-stepsize-in-slicefor string, bytes, and tuple literals:We now report the diagnostic when the receiver type is
listor another known built-in while preserving the inferred result type for error recovery.(A hypothetical subclass could override
__getitem__to accept a zero step, but thelisttype includes exactlistinstances that are known to reject it. Subclasses with their own nominal type and custom sequence types continue through normal subscript dispatch without this diagnostic.)Closes astral-sh/ty#3736.