diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF016.py b/crates/ruff/resources/test/fixtures/ruff/RUF016.py new file mode 100644 index 00000000000000..545ad2ec537c05 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/ruff/RUF016.py @@ -0,0 +1,115 @@ +# Should not emit for valid access with index +var = "abc"[0] +var = f"abc"[0] +var = [1, 2, 3][0] +var = (1, 2, 3)[0] +var = b"abc"[0] + +# Should not emit for valid access with slice +var = "abc"[0:2] +var = f"abc"[0:2] +var = b"abc"[0:2] +var = [1, 2, 3][0:2] +var = (1, 2, 3)[0:2] +var = [1, 2, 3][None:2] +var = [1, 2, 3][0:None] +var = [1, 2, 3][:2] +var = [1, 2, 3][0:] + +# Should emit for invalid access on strings +var = "abc"["x"] +var = f"abc"["x"] + +# Should emit for invalid access on bytes +var = b"abc"["x"] + +# Should emit for invalid access on lists and tuples +var = [1, 2, 3]["x"] +var = (1, 2, 3)["x"] + +# Should emit for invalid access on list comprehensions +var = [x for x in range(10)]["x"] + +# Should emit for invalid access using tuple +var = "abc"[1, 2] + +# Should emit for invalid access using string +var = [1, 2]["x"] + +# Should emit for invalid access using float +var = [1, 2][0.25] + +# Should emit for invalid access using dict +var = [1, 2][{"x": "y"}] + +# Should emit for invalid access using dict comp +var = [1, 2][{x: "y" for x in range(2)}] + +# Should emit for invalid access using list +var = [1, 2][2, 3] + +# Should emit for invalid access using list comp +var = [1, 2][[x for x in range(2)]] + +# Should emit on invalid access using set +var = [1, 2][{"x", "y"}] + +# Should emit on invalid access using set comp +var = [1, 2][{x for x in range(2)}] + +# Should emit on invalid access using bytes +var = [1, 2][b"x"] + +# Should emit for non-integer slice start +var = [1, 2, 3]["x":2] +var = [1, 2, 3][f"x":2] +var = [1, 2, 3][1.2:2] +var = [1, 2, 3][{"x"}:2] +var = [1, 2, 3][{x for x in range(2)}:2] +var = [1, 2, 3][{"x": x for x in range(2)}:2] +var = [1, 2, 3][[x for x in range(2)]:2] + +# Should emit for non-integer slice end +var = [1, 2, 3][0:"x"] +var = [1, 2, 3][0:f"x"] +var = [1, 2, 3][0:1.2] +var = [1, 2, 3][0:{"x"}] +var = [1, 2, 3][0:{x for x in range(2)}] +var = [1, 2, 3][0:{"x": x for x in range(2)}] +var = [1, 2, 3][0:[x for x in range(2)]] + +# Should emit for non-integer slice step +var = [1, 2, 3][0:1:"x"] +var = [1, 2, 3][0:1:f"x"] +var = [1, 2, 3][0:1:1.2] +var = [1, 2, 3][0:1:{"x"}] +var = [1, 2, 3][0:1:{x for x in range(2)}] +var = [1, 2, 3][0:1:{"x": x for x in range(2)}] +var = [1, 2, 3][0:1:[x for x in range(2)]] + +# Should emit for non-integer slice start and end; should emit twice with specific ranges +var = [1, 2, 3]["x":"y"] + +# Should emit once for repeated invalid access +var = [1, 2, 3]["x"]["y"]["z"] + +# Cannot emit on invalid access using variable in index +x = "x" +var = "abc"[x] + +# Cannot emit on invalid access using call +def func(): + return 1 +var = "abc"[func()] + +# Cannot emit on invalid access using a variable in parent +x = [1, 2, 3] +var = x["y"] + +# Cannot emit for invalid access on byte array +var = bytearray(b"abc")["x"] + +# Cannot emit for slice bound using variable +x = "x" +var = [1, 2, 3][0:x] +var = [1, 2, 3][x:1] diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 11f8d3600f27c4..aff756000b2d64 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -2142,7 +2142,7 @@ where // Pre-visit. match expr { - subscript @ Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => { + Expr::Subscript(subscript @ ast::ExprSubscript { value, slice, .. }) => { // Ex) Optional[...], Union[...] if self.any_enabled(&[ Rule::FutureRewritableTypeAnnotation, @@ -2235,6 +2235,10 @@ where ruff::rules::unnecessary_iterable_allocation_for_first_element(self, subscript); } + if self.enabled(Rule::InvalidIndexType) { + ruff::rules::invalid_index_type(self, subscript); + } + pandas_vet::rules::subscript(self, value, expr); } Expr::Tuple(ast::ExprTuple { diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 03dd26fb827801..704deb14377686 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -781,6 +781,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { #[cfg(feature = "unreachable-code")] (Ruff, "014") => (RuleGroup::Nursery, rules::ruff::rules::UnreachableCode), (Ruff, "015") => (RuleGroup::Unspecified, rules::ruff::rules::UnnecessaryIterableAllocationForFirstElement), + (Ruff, "016") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidIndexType), (Ruff, "100") => (RuleGroup::Unspecified, rules::ruff::rules::UnusedNOQA), (Ruff, "200") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidPyprojectToml), diff --git a/crates/ruff/src/rules/ruff/mod.rs b/crates/ruff/src/rules/ruff/mod.rs index beb603359afb85..2736f4f23ac722 100644 --- a/crates/ruff/src/rules/ruff/mod.rs +++ b/crates/ruff/src/rules/ruff/mod.rs @@ -34,6 +34,7 @@ mod tests { Rule::UnnecessaryIterableAllocationForFirstElement, Path::new("RUF015.py") )] + #[test_case(Rule::InvalidIndexType, Path::new("RUF016.py"))] #[cfg_attr( feature = "unreachable-code", test_case(Rule::UnreachableCode, Path::new("RUF014.py")) diff --git a/crates/ruff/src/rules/ruff/rules/invalid_index_type.rs b/crates/ruff/src/rules/ruff/rules/invalid_index_type.rs new file mode 100644 index 00000000000000..399a77dbabd502 --- /dev/null +++ b/crates/ruff/src/rules/ruff/rules/invalid_index_type.rs @@ -0,0 +1,214 @@ +use rustpython_parser::ast::{Constant, Expr, ExprConstant, ExprSlice, ExprSubscript, Ranged}; + +use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_macros::{derive_message_formats, violation}; +use std::fmt; + +use crate::checkers::ast::Checker; + +/// ## What it does +/// Checks for indexed access to lists, strings, tuples, bytes, and comprehensions +/// using a type other than an integer or slice. +/// +/// ## Why is this bad? +/// Only integers or slices can be used as indices to these types. Using +/// other types will result in a `TypeError` at runtime and a `SyntaxWarning` at +/// import time. +/// +/// ## Example +/// ```python +/// var = [1, 2, 3]["x"] +/// ``` +/// +/// Use instead: +/// ```python +/// var = [1, 2, 3][0] +/// ``` +#[violation] +pub struct InvalidIndexType { + value_type: String, + index_type: String, + is_slice: bool, +} + +impl Violation for InvalidIndexType { + #[derive_message_formats] + fn message(&self) -> String { + let InvalidIndexType { + value_type, + index_type, + is_slice, + } = self; + if *is_slice { + format!("Slice in indexed access to type `{value_type}` uses type `{index_type}` instead of an integer.") + } else { + format!( + "Indexed access to type `{value_type}` uses type `{index_type}` instead of an integer or slice." + ) + } + } +} + +/// RUF015 +pub(crate) fn invalid_index_type(checker: &mut Checker, expr: &ExprSubscript) { + let ExprSubscript { + value, + slice: index, + .. + } = expr; + + // Check the value being indexed is a list, tuple, string, f-string, bytes, or comprehension + if !matches!( + value.as_ref(), + Expr::List(_) + | Expr::ListComp(_) + | Expr::Tuple(_) + | Expr::JoinedStr(_) + | Expr::Constant(ExprConstant { + value: Constant::Str(_) | Constant::Bytes(_), + .. + }) + ) { + return; + } + + // The value types supported by this rule should always be checkable + let Some(value_type) = CheckableExprType::try_from(value) else { + debug_assert!(false, "Index value must be a checkable type to generate a violation message."); + return; + }; + + // If the index is not a checkable type then we can't easily determine if there is a violation + let Some(index_type) = CheckableExprType::try_from(index) else { + return; + }; + + // Then check the contents of the index + match index.as_ref() { + Expr::Constant(ExprConstant { + value: index_value, .. + }) => { + // If the index is a constant, require an integer + if !index_value.is_int() { + checker.diagnostics.push(Diagnostic::new( + InvalidIndexType { + value_type: value_type.to_string(), + index_type: constant_type_name(index_value).to_string(), + is_slice: false, + }, + index.range(), + )); + } + } + Expr::Slice(ExprSlice { + lower, upper, step, .. + }) => { + // If the index is a slice, require integer or null bounds + for is_slice in [lower, upper, step].into_iter().flatten() { + if let Expr::Constant(ExprConstant { + value: index_value, .. + }) = is_slice.as_ref() + { + if !(index_value.is_int() || index_value.is_none()) { + checker.diagnostics.push(Diagnostic::new( + InvalidIndexType { + value_type: value_type.to_string(), + index_type: constant_type_name(index_value).to_string(), + is_slice: true, + }, + is_slice.range(), + )); + } + } else if let Some(is_slice_type) = CheckableExprType::try_from(is_slice.as_ref()) { + checker.diagnostics.push(Diagnostic::new( + InvalidIndexType { + value_type: value_type.to_string(), + index_type: is_slice_type.to_string(), + is_slice: true, + }, + is_slice.range(), + )); + } + } + } + _ => { + // If it's some other checkable data type, it's a violation + checker.diagnostics.push(Diagnostic::new( + InvalidIndexType { + value_type: value_type.to_string(), + index_type: index_type.to_string(), + is_slice: false, + }, + index.range(), + )); + } + } +} + +/// An expression that can be checked for type compatibility. +/// +/// These are generally "literal" type expressions in that we know their concrete type +/// without additional analysis; opposed to expressions like a function call where we +/// cannot determine what type it may return. +#[derive(Debug)] +enum CheckableExprType<'a> { + Constant(&'a Constant), + JoinedStr, + List, + ListComp, + SetComp, + DictComp, + Set, + Dict, + Tuple, + Slice, +} + +impl fmt::Display for CheckableExprType<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Constant(constant) => f.write_str(constant_type_name(constant)), + Self::JoinedStr => f.write_str("str"), + Self::List => f.write_str("list"), + Self::SetComp => f.write_str("set comprehension"), + Self::ListComp => f.write_str("list comprehension"), + Self::DictComp => f.write_str("dict comprehension"), + Self::Set => f.write_str("set"), + Self::Slice => f.write_str("slice"), + Self::Dict => f.write_str("dict"), + Self::Tuple => f.write_str("tuple"), + } + } +} + +impl<'a> CheckableExprType<'a> { + fn try_from(expr: &'a Expr) -> Option { + match expr { + Expr::Constant(ExprConstant { value, .. }) => Some(Self::Constant(value)), + Expr::JoinedStr(_) => Some(Self::JoinedStr), + Expr::List(_) => Some(Self::List), + Expr::ListComp(_) => Some(Self::ListComp), + Expr::SetComp(_) => Some(Self::SetComp), + Expr::DictComp(_) => Some(Self::DictComp), + Expr::Set(_) => Some(Self::Set), + Expr::Dict(_) => Some(Self::Dict), + Expr::Tuple(_) => Some(Self::Tuple), + Expr::Slice(_) => Some(Self::Slice), + _ => None, + } + } +} + +fn constant_type_name(constant: &Constant) -> &'static str { + match constant { + Constant::None => "None", + Constant::Bool(_) => "bool", + Constant::Str(_) => "str", + Constant::Bytes(_) => "bytes", + Constant::Int(_) => "int", + Constant::Tuple(_) => "tuple", + Constant::Float(_) => "float", + Constant::Complex { .. } => "complex", + Constant::Ellipsis => "ellipsis", + } +} diff --git a/crates/ruff/src/rules/ruff/rules/mod.rs b/crates/ruff/src/rules/ruff/rules/mod.rs index e6654f1204d71d..e4d39feefd825f 100644 --- a/crates/ruff/src/rules/ruff/rules/mod.rs +++ b/crates/ruff/src/rules/ruff/rules/mod.rs @@ -4,6 +4,7 @@ pub(crate) use collection_literal_concatenation::*; pub(crate) use explicit_f_string_type_conversion::*; pub(crate) use function_call_in_dataclass_default::*; pub(crate) use implicit_optional::*; +pub(crate) use invalid_index_type::*; pub(crate) use invalid_pyproject_toml::*; pub(crate) use mutable_class_default::*; pub(crate) use mutable_dataclass_default::*; @@ -22,6 +23,7 @@ mod explicit_f_string_type_conversion; mod function_call_in_dataclass_default; mod helpers; mod implicit_optional; +mod invalid_index_type; mod invalid_pyproject_toml; mod mutable_class_default; mod mutable_dataclass_default; diff --git a/crates/ruff/src/rules/ruff/rules/unnecessary_iterable_allocation_for_first_element.rs b/crates/ruff/src/rules/ruff/rules/unnecessary_iterable_allocation_for_first_element.rs index 62f2cca6244db8..17fa0922e532fb 100644 --- a/crates/ruff/src/rules/ruff/rules/unnecessary_iterable_allocation_for_first_element.rs +++ b/crates/ruff/src/rules/ruff/rules/unnecessary_iterable_allocation_for_first_element.rs @@ -1,6 +1,6 @@ use num_bigint::BigInt; use num_traits::{One, Zero}; -use rustpython_parser::ast::{self, Comprehension, Constant, Expr}; +use rustpython_parser::ast::{self, Comprehension, Constant, Expr, ExprSubscript}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; @@ -78,17 +78,14 @@ impl AlwaysAutofixableViolation for UnnecessaryIterableAllocationForFirstElement /// RUF015 pub(crate) fn unnecessary_iterable_allocation_for_first_element( checker: &mut Checker, - subscript: &Expr, + subscript: &ExprSubscript, ) { - let Expr::Subscript(ast::ExprSubscript { + let ast::ExprSubscript { value, slice, range, .. - }) = subscript - else { - return; - }; + } = subscript; let Some(subscript_kind) = classify_subscript(slice) else { return; diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF016_RUF016.py.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF016_RUF016.py.snap new file mode 100644 index 00000000000000..bf56f0d36c8b1a --- /dev/null +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF016_RUF016.py.snap @@ -0,0 +1,379 @@ +--- +source: crates/ruff/src/rules/ruff/mod.rs +--- +RUF016.py:20:13: RUF016 Indexed access to type `str` uses type `str` instead of an integer or slice. + | +19 | # Should emit for invalid access on strings +20 | var = "abc"["x"] + | ^^^ RUF016 +21 | var = f"abc"["x"] + | + +RUF016.py:21:14: RUF016 Indexed access to type `str` uses type `str` instead of an integer or slice. + | +19 | # Should emit for invalid access on strings +20 | var = "abc"["x"] +21 | var = f"abc"["x"] + | ^^^ RUF016 +22 | +23 | # Should emit for invalid access on bytes + | + +RUF016.py:24:14: RUF016 Indexed access to type `bytes` uses type `str` instead of an integer or slice. + | +23 | # Should emit for invalid access on bytes +24 | var = b"abc"["x"] + | ^^^ RUF016 +25 | +26 | # Should emit for invalid access on lists and tuples + | + +RUF016.py:27:17: RUF016 Indexed access to type `list` uses type `str` instead of an integer or slice. + | +26 | # Should emit for invalid access on lists and tuples +27 | var = [1, 2, 3]["x"] + | ^^^ RUF016 +28 | var = (1, 2, 3)["x"] + | + +RUF016.py:28:17: RUF016 Indexed access to type `tuple` uses type `str` instead of an integer or slice. + | +26 | # Should emit for invalid access on lists and tuples +27 | var = [1, 2, 3]["x"] +28 | var = (1, 2, 3)["x"] + | ^^^ RUF016 +29 | +30 | # Should emit for invalid access on list comprehensions + | + +RUF016.py:31:30: RUF016 Indexed access to type `list comprehension` uses type `str` instead of an integer or slice. + | +30 | # Should emit for invalid access on list comprehensions +31 | var = [x for x in range(10)]["x"] + | ^^^ RUF016 +32 | +33 | # Should emit for invalid access using tuple + | + +RUF016.py:34:13: RUF016 Indexed access to type `str` uses type `tuple` instead of an integer or slice. + | +33 | # Should emit for invalid access using tuple +34 | var = "abc"[1, 2] + | ^^^^ RUF016 +35 | +36 | # Should emit for invalid access using string + | + +RUF016.py:37:14: RUF016 Indexed access to type `list` uses type `str` instead of an integer or slice. + | +36 | # Should emit for invalid access using string +37 | var = [1, 2]["x"] + | ^^^ RUF016 +38 | +39 | # Should emit for invalid access using float + | + +RUF016.py:40:14: RUF016 Indexed access to type `list` uses type `float` instead of an integer or slice. + | +39 | # Should emit for invalid access using float +40 | var = [1, 2][0.25] + | ^^^^ RUF016 +41 | +42 | # Should emit for invalid access using dict + | + +RUF016.py:43:14: RUF016 Indexed access to type `list` uses type `dict` instead of an integer or slice. + | +42 | # Should emit for invalid access using dict +43 | var = [1, 2][{"x": "y"}] + | ^^^^^^^^^^ RUF016 +44 | +45 | # Should emit for invalid access using dict comp + | + +RUF016.py:46:14: RUF016 Indexed access to type `list` uses type `dict comprehension` instead of an integer or slice. + | +45 | # Should emit for invalid access using dict comp +46 | var = [1, 2][{x: "y" for x in range(2)}] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF016 +47 | +48 | # Should emit for invalid access using list + | + +RUF016.py:49:14: RUF016 Indexed access to type `list` uses type `tuple` instead of an integer or slice. + | +48 | # Should emit for invalid access using list +49 | var = [1, 2][2, 3] + | ^^^^ RUF016 +50 | +51 | # Should emit for invalid access using list comp + | + +RUF016.py:52:14: RUF016 Indexed access to type `list` uses type `list comprehension` instead of an integer or slice. + | +51 | # Should emit for invalid access using list comp +52 | var = [1, 2][[x for x in range(2)]] + | ^^^^^^^^^^^^^^^^^^^^^ RUF016 +53 | +54 | # Should emit on invalid access using set + | + +RUF016.py:55:14: RUF016 Indexed access to type `list` uses type `set` instead of an integer or slice. + | +54 | # Should emit on invalid access using set +55 | var = [1, 2][{"x", "y"}] + | ^^^^^^^^^^ RUF016 +56 | +57 | # Should emit on invalid access using set comp + | + +RUF016.py:58:14: RUF016 Indexed access to type `list` uses type `set comprehension` instead of an integer or slice. + | +57 | # Should emit on invalid access using set comp +58 | var = [1, 2][{x for x in range(2)}] + | ^^^^^^^^^^^^^^^^^^^^^ RUF016 +59 | +60 | # Should emit on invalid access using bytes + | + +RUF016.py:61:14: RUF016 Indexed access to type `list` uses type `bytes` instead of an integer or slice. + | +60 | # Should emit on invalid access using bytes +61 | var = [1, 2][b"x"] + | ^^^^ RUF016 +62 | +63 | # Should emit for non-integer slice start + | + +RUF016.py:64:17: RUF016 Slice in indexed access to type `list` uses type `str` instead of an integer. + | +63 | # Should emit for non-integer slice start +64 | var = [1, 2, 3]["x":2] + | ^^^ RUF016 +65 | var = [1, 2, 3][f"x":2] +66 | var = [1, 2, 3][1.2:2] + | + +RUF016.py:65:17: RUF016 Slice in indexed access to type `list` uses type `str` instead of an integer. + | +63 | # Should emit for non-integer slice start +64 | var = [1, 2, 3]["x":2] +65 | var = [1, 2, 3][f"x":2] + | ^^^^ RUF016 +66 | var = [1, 2, 3][1.2:2] +67 | var = [1, 2, 3][{"x"}:2] + | + +RUF016.py:66:17: RUF016 Slice in indexed access to type `list` uses type `float` instead of an integer. + | +64 | var = [1, 2, 3]["x":2] +65 | var = [1, 2, 3][f"x":2] +66 | var = [1, 2, 3][1.2:2] + | ^^^ RUF016 +67 | var = [1, 2, 3][{"x"}:2] +68 | var = [1, 2, 3][{x for x in range(2)}:2] + | + +RUF016.py:67:17: RUF016 Slice in indexed access to type `list` uses type `set` instead of an integer. + | +65 | var = [1, 2, 3][f"x":2] +66 | var = [1, 2, 3][1.2:2] +67 | var = [1, 2, 3][{"x"}:2] + | ^^^^^ RUF016 +68 | var = [1, 2, 3][{x for x in range(2)}:2] +69 | var = [1, 2, 3][{"x": x for x in range(2)}:2] + | + +RUF016.py:68:17: RUF016 Slice in indexed access to type `list` uses type `set comprehension` instead of an integer. + | +66 | var = [1, 2, 3][1.2:2] +67 | var = [1, 2, 3][{"x"}:2] +68 | var = [1, 2, 3][{x for x in range(2)}:2] + | ^^^^^^^^^^^^^^^^^^^^^ RUF016 +69 | var = [1, 2, 3][{"x": x for x in range(2)}:2] +70 | var = [1, 2, 3][[x for x in range(2)]:2] + | + +RUF016.py:69:17: RUF016 Slice in indexed access to type `list` uses type `dict comprehension` instead of an integer. + | +67 | var = [1, 2, 3][{"x"}:2] +68 | var = [1, 2, 3][{x for x in range(2)}:2] +69 | var = [1, 2, 3][{"x": x for x in range(2)}:2] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF016 +70 | var = [1, 2, 3][[x for x in range(2)]:2] + | + +RUF016.py:70:17: RUF016 Slice in indexed access to type `list` uses type `list comprehension` instead of an integer. + | +68 | var = [1, 2, 3][{x for x in range(2)}:2] +69 | var = [1, 2, 3][{"x": x for x in range(2)}:2] +70 | var = [1, 2, 3][[x for x in range(2)]:2] + | ^^^^^^^^^^^^^^^^^^^^^ RUF016 +71 | +72 | # Should emit for non-integer slice end + | + +RUF016.py:73:19: RUF016 Slice in indexed access to type `list` uses type `str` instead of an integer. + | +72 | # Should emit for non-integer slice end +73 | var = [1, 2, 3][0:"x"] + | ^^^ RUF016 +74 | var = [1, 2, 3][0:f"x"] +75 | var = [1, 2, 3][0:1.2] + | + +RUF016.py:74:19: RUF016 Slice in indexed access to type `list` uses type `str` instead of an integer. + | +72 | # Should emit for non-integer slice end +73 | var = [1, 2, 3][0:"x"] +74 | var = [1, 2, 3][0:f"x"] + | ^^^^ RUF016 +75 | var = [1, 2, 3][0:1.2] +76 | var = [1, 2, 3][0:{"x"}] + | + +RUF016.py:75:19: RUF016 Slice in indexed access to type `list` uses type `float` instead of an integer. + | +73 | var = [1, 2, 3][0:"x"] +74 | var = [1, 2, 3][0:f"x"] +75 | var = [1, 2, 3][0:1.2] + | ^^^ RUF016 +76 | var = [1, 2, 3][0:{"x"}] +77 | var = [1, 2, 3][0:{x for x in range(2)}] + | + +RUF016.py:76:19: RUF016 Slice in indexed access to type `list` uses type `set` instead of an integer. + | +74 | var = [1, 2, 3][0:f"x"] +75 | var = [1, 2, 3][0:1.2] +76 | var = [1, 2, 3][0:{"x"}] + | ^^^^^ RUF016 +77 | var = [1, 2, 3][0:{x for x in range(2)}] +78 | var = [1, 2, 3][0:{"x": x for x in range(2)}] + | + +RUF016.py:77:19: RUF016 Slice in indexed access to type `list` uses type `set comprehension` instead of an integer. + | +75 | var = [1, 2, 3][0:1.2] +76 | var = [1, 2, 3][0:{"x"}] +77 | var = [1, 2, 3][0:{x for x in range(2)}] + | ^^^^^^^^^^^^^^^^^^^^^ RUF016 +78 | var = [1, 2, 3][0:{"x": x for x in range(2)}] +79 | var = [1, 2, 3][0:[x for x in range(2)]] + | + +RUF016.py:78:19: RUF016 Slice in indexed access to type `list` uses type `dict comprehension` instead of an integer. + | +76 | var = [1, 2, 3][0:{"x"}] +77 | var = [1, 2, 3][0:{x for x in range(2)}] +78 | var = [1, 2, 3][0:{"x": x for x in range(2)}] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF016 +79 | var = [1, 2, 3][0:[x for x in range(2)]] + | + +RUF016.py:79:19: RUF016 Slice in indexed access to type `list` uses type `list comprehension` instead of an integer. + | +77 | var = [1, 2, 3][0:{x for x in range(2)}] +78 | var = [1, 2, 3][0:{"x": x for x in range(2)}] +79 | var = [1, 2, 3][0:[x for x in range(2)]] + | ^^^^^^^^^^^^^^^^^^^^^ RUF016 +80 | +81 | # Should emit for non-integer slice step + | + +RUF016.py:82:21: RUF016 Slice in indexed access to type `list` uses type `str` instead of an integer. + | +81 | # Should emit for non-integer slice step +82 | var = [1, 2, 3][0:1:"x"] + | ^^^ RUF016 +83 | var = [1, 2, 3][0:1:f"x"] +84 | var = [1, 2, 3][0:1:1.2] + | + +RUF016.py:83:21: RUF016 Slice in indexed access to type `list` uses type `str` instead of an integer. + | +81 | # Should emit for non-integer slice step +82 | var = [1, 2, 3][0:1:"x"] +83 | var = [1, 2, 3][0:1:f"x"] + | ^^^^ RUF016 +84 | var = [1, 2, 3][0:1:1.2] +85 | var = [1, 2, 3][0:1:{"x"}] + | + +RUF016.py:84:21: RUF016 Slice in indexed access to type `list` uses type `float` instead of an integer. + | +82 | var = [1, 2, 3][0:1:"x"] +83 | var = [1, 2, 3][0:1:f"x"] +84 | var = [1, 2, 3][0:1:1.2] + | ^^^ RUF016 +85 | var = [1, 2, 3][0:1:{"x"}] +86 | var = [1, 2, 3][0:1:{x for x in range(2)}] + | + +RUF016.py:85:21: RUF016 Slice in indexed access to type `list` uses type `set` instead of an integer. + | +83 | var = [1, 2, 3][0:1:f"x"] +84 | var = [1, 2, 3][0:1:1.2] +85 | var = [1, 2, 3][0:1:{"x"}] + | ^^^^^ RUF016 +86 | var = [1, 2, 3][0:1:{x for x in range(2)}] +87 | var = [1, 2, 3][0:1:{"x": x for x in range(2)}] + | + +RUF016.py:86:21: RUF016 Slice in indexed access to type `list` uses type `set comprehension` instead of an integer. + | +84 | var = [1, 2, 3][0:1:1.2] +85 | var = [1, 2, 3][0:1:{"x"}] +86 | var = [1, 2, 3][0:1:{x for x in range(2)}] + | ^^^^^^^^^^^^^^^^^^^^^ RUF016 +87 | var = [1, 2, 3][0:1:{"x": x for x in range(2)}] +88 | var = [1, 2, 3][0:1:[x for x in range(2)]] + | + +RUF016.py:87:21: RUF016 Slice in indexed access to type `list` uses type `dict comprehension` instead of an integer. + | +85 | var = [1, 2, 3][0:1:{"x"}] +86 | var = [1, 2, 3][0:1:{x for x in range(2)}] +87 | var = [1, 2, 3][0:1:{"x": x for x in range(2)}] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF016 +88 | var = [1, 2, 3][0:1:[x for x in range(2)]] + | + +RUF016.py:88:21: RUF016 Slice in indexed access to type `list` uses type `list comprehension` instead of an integer. + | +86 | var = [1, 2, 3][0:1:{x for x in range(2)}] +87 | var = [1, 2, 3][0:1:{"x": x for x in range(2)}] +88 | var = [1, 2, 3][0:1:[x for x in range(2)]] + | ^^^^^^^^^^^^^^^^^^^^^ RUF016 +89 | +90 | # Should emit for non-integer slice start and end; should emit twice with specific ranges + | + +RUF016.py:91:17: RUF016 Slice in indexed access to type `list` uses type `str` instead of an integer. + | +90 | # Should emit for non-integer slice start and end; should emit twice with specific ranges +91 | var = [1, 2, 3]["x":"y"] + | ^^^ RUF016 +92 | +93 | # Should emit once for repeated invalid access + | + +RUF016.py:91:21: RUF016 Slice in indexed access to type `list` uses type `str` instead of an integer. + | +90 | # Should emit for non-integer slice start and end; should emit twice with specific ranges +91 | var = [1, 2, 3]["x":"y"] + | ^^^ RUF016 +92 | +93 | # Should emit once for repeated invalid access + | + +RUF016.py:94:17: RUF016 Indexed access to type `list` uses type `str` instead of an integer or slice. + | +93 | # Should emit once for repeated invalid access +94 | var = [1, 2, 3]["x"]["y"]["z"] + | ^^^ RUF016 +95 | +96 | # Cannot emit on invalid access using variable in index + | + + diff --git a/ruff.schema.json b/ruff.schema.json index ae611fd379760b..2fc94daff77182 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2405,6 +2405,7 @@ "RUF013", "RUF014", "RUF015", + "RUF016", "RUF1", "RUF10", "RUF100",