diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF015.py b/crates/ruff/resources/test/fixtures/ruff/RUF015.py index e19602de8d..393694d89b 100644 --- a/crates/ruff/resources/test/fixtures/ruff/RUF015.py +++ b/crates/ruff/resources/test/fixtures/ruff/RUF015.py @@ -34,11 +34,23 @@ list(x)[::] [i for i in x][::2] [i for i in x][::] -# OK (doesn't mirror the underlying list) +# RUF015 (doesn't mirror the underlying list) [i + 1 for i in x][0] [i for i in x if i > 5][0] [(i, i + 1) for i in x][0] -# OK (multiple generators) +# RUF015 (multiple generators) y = range(10) [i + j for i in x for j in y][0] + +# RUF015 +list(range(10))[0] +list(x.y)[0] +list(x["y"])[0] + +# RUF015 (multi-line) +revision_heads_map_ast = [ + a + for a in revision_heads_map_ast_obj.body + if isinstance(a, ast.Assign) and a.targets[0].id == "REVISION_HEADS_MAP" +][0] diff --git a/crates/ruff/src/rules/ruff/rules/invalid_index_type.rs b/crates/ruff/src/rules/ruff/rules/invalid_index_type.rs index eedcec6e04..50719e2a28 100644 --- a/crates/ruff/src/rules/ruff/rules/invalid_index_type.rs +++ b/crates/ruff/src/rules/ruff/rules/invalid_index_type.rs @@ -49,7 +49,7 @@ impl Violation for InvalidIndexType { } } -/// RUF015 +/// RUF016 pub(crate) fn invalid_index_type(checker: &mut Checker, expr: &ExprSubscript) { let ExprSubscript { value, 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 17fa0922e5..3acce545b3 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,10 @@ +use std::borrow::Cow; + use num_bigint::BigInt; use num_traits::{One, Zero}; -use rustpython_parser::ast::{self, Comprehension, Constant, Expr, ExprSubscript}; +use ruff_text_size::{TextRange, TextSize}; +use rustpython_parser::ast::{self, Comprehension, Constant, Expr, Ranged}; +use unicode_width::UnicodeWidthStr; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; @@ -16,8 +20,8 @@ use crate::registry::AsRule; /// ## Why is this bad? /// Calling `list(...)` will create a new list of the entire collection, which /// can be very expensive for large collections. If you only need the first -/// element of the collection, you can use `next(iter(...))` to lazily fetch -/// the first element without creating a new list. +/// element of the collection, you can use `next(...)` or `next(iter(...)` to +/// lazily fetch the first element. /// /// Note that migrating from `list(...)[0]` to `next(iter(...))` can change /// the behavior of your program in two ways: @@ -26,16 +30,18 @@ use crate::registry::AsRule; /// `next(iter(...))` will only evaluate the first element. As such, any /// side effects that occur during iteration will be delayed. /// 2. Second, `list(...)[0]` will raise `IndexError` if the collection is -/// empty, while `next(iter(...))` will raise `StopIteration`. +/// empty, while `next(iter(...))` will raise `StopIteration`. /// /// ## Example /// ```python -/// head = list(range(1000000000000))[0] +/// head = list(x)[0] +/// head = [x * x for x in range(10)][0] /// ``` /// /// Use instead: /// ```python -/// head = next(iter(range(1000000000000))) +/// head = next(iter(x)) +/// head = next(x * x for x in range(10)) /// ``` /// /// ## References @@ -53,12 +59,13 @@ impl AlwaysAutofixableViolation for UnnecessaryIterableAllocationForFirstElement iterable, subscript_kind, } = self; + let iterable = Self::truncate(iterable); match subscript_kind { HeadSubscriptKind::Index => { - format!("Prefer `next(iter({iterable}))` over `list({iterable})[0]`") + format!("Prefer `next({iterable})` over single element slice") } HeadSubscriptKind::Slice => { - format!("Prefer `[next(iter({iterable}))]` over `list({iterable})[:1]`") + format!("Prefer `[next({iterable})]` over single element slice") } } } @@ -68,9 +75,21 @@ impl AlwaysAutofixableViolation for UnnecessaryIterableAllocationForFirstElement iterable, subscript_kind, } = self; + let iterable = Self::truncate(iterable); match subscript_kind { - HeadSubscriptKind::Index => format!("Replace with `next(iter({iterable}))`"), - HeadSubscriptKind::Slice => format!("Replace with `[next(iter({iterable}))]"), + HeadSubscriptKind::Index => format!("Replace with `next({iterable})`"), + HeadSubscriptKind::Slice => format!("Replace with `[next({iterable})]"), + } + } +} + +impl UnnecessaryIterableAllocationForFirstElement { + /// If the iterable is too long, or spans multiple lines, truncate it. + fn truncate(iterable: &str) -> &str { + if iterable.width() > 40 || iterable.contains(['\r', '\n']) { + "..." + } else { + iterable } } } @@ -78,7 +97,7 @@ impl AlwaysAutofixableViolation for UnnecessaryIterableAllocationForFirstElement /// RUF015 pub(crate) fn unnecessary_iterable_allocation_for_first_element( checker: &mut Checker, - subscript: &ExprSubscript, + subscript: &ast::ExprSubscript, ) { let ast::ExprSubscript { value, @@ -91,9 +110,15 @@ pub(crate) fn unnecessary_iterable_allocation_for_first_element( return; }; - let Some(iterable) = iterable_name(value, checker.semantic()) else { + let Some(target) = match_iteration_target(value, checker.semantic()) else { return; }; + let iterable = checker.locator.slice(target.range); + let iterable = if target.iterable { + Cow::Borrowed(iterable) + } else { + Cow::Owned(format!("iter({iterable})")) + }; let mut diagnostic = Diagnostic::new( UnnecessaryIterableAllocationForFirstElement { @@ -105,8 +130,8 @@ pub(crate) fn unnecessary_iterable_allocation_for_first_element( if checker.patch(diagnostic.kind.rule()) { let replacement = match subscript_kind { - HeadSubscriptKind::Index => format!("next(iter({iterable}))"), - HeadSubscriptKind::Slice => format!("[next(iter({iterable}))]"), + HeadSubscriptKind::Index => format!("next({iterable})"), + HeadSubscriptKind::Slice => format!("[next({iterable})]"), }; diagnostic.set_fix(Fix::suggested(Edit::range_replacement(replacement, *range))); } @@ -127,11 +152,11 @@ enum HeadSubscriptKind { /// first `bool` checks that the element is in fact first, the second checks if it's a slice or an /// index. fn classify_subscript(expr: &Expr) -> Option { - match expr { + let result = match expr { Expr::Constant(ast::ExprConstant { value: Constant::Int(value), .. - }) if value.is_zero() => Some(HeadSubscriptKind::Index), + }) if value.is_zero() => HeadSubscriptKind::Index, Expr::Slice(ast::ExprSlice { step, lower, upper, .. }) => { @@ -158,16 +183,33 @@ fn classify_subscript(expr: &Expr) -> Option { } } - Some(HeadSubscriptKind::Slice) + HeadSubscriptKind::Slice } - _ => None, - } + _ => return None, + }; + + Some(result) } -/// Fetch the name of the iterable from an expression if the expression returns an unmodified list -/// which can be sliced into. -fn iterable_name<'a>(expr: &'a Expr, model: &SemanticModel) -> Option<&'a str> { - match expr { +#[derive(Debug)] +struct IterationTarget { + /// The [`TextRange`] of the target. + range: TextRange, + /// Whether the target is an iterable (e.g., a generator). If not, the target must be wrapped + /// in `iter(...)` prior to calling `next(...)`. + iterable: bool, +} + +/// Return the [`IterationTarget`] of an expression, if the expression can be sliced into (i.e., +/// is a list comprehension, or call to `list` or `tuple`). +/// +/// For example, given `list(x)`, returns the range of `x`. Given `[x * x for x in y]`, returns the +/// range of `x * x for x in y`. +/// +/// As a special-case, given `[x for x in y]`, returns the range of `y` (rather than the +/// redundant comprehension). +fn match_iteration_target(expr: &Expr, model: &SemanticModel) -> Option { + let result = match expr { Expr::Call(ast::ExprCall { func, args, .. }) => { let ast::ExprName { id, .. } = func.as_name_expr()?; @@ -175,37 +217,82 @@ fn iterable_name<'a>(expr: &'a Expr, model: &SemanticModel) -> Option<&'a str> { return None; } + let [arg] = args.as_slice() else { + return None; + }; + if !model.is_builtin(id.as_str()) { return None; } - match args.first() { - Some(Expr::Name(ast::ExprName { id: arg_name, .. })) => Some(arg_name.as_str()), - Some(Expr::GeneratorExp(ast::ExprGeneratorExp { + match arg { + Expr::GeneratorExp(ast::ExprGeneratorExp { elt, generators, .. - })) => generator_iterable(elt, generators), - _ => None, + }) => match match_simple_comprehension(elt, generators) { + Some(range) => IterationTarget { + range, + iterable: false, + }, + None => IterationTarget { + range: arg.range(), + iterable: true, + }, + }, + Expr::ListComp(ast::ExprListComp { + elt, generators, .. + }) => match match_simple_comprehension(elt, generators) { + Some(range) => IterationTarget { + range, + iterable: false, + }, + None => IterationTarget { + range: arg + .range() + // Remove the `[` + .add_start(TextSize::from(1)) + // Remove the `]` + .sub_end(TextSize::from(1)), + iterable: true, + }, + }, + _ => IterationTarget { + range: arg.range(), + iterable: false, + }, } } + Expr::ListComp(ast::ExprListComp { elt, generators, .. - }) => generator_iterable(elt, generators), - _ => None, - } + }) => match match_simple_comprehension(elt, generators) { + Some(range) => IterationTarget { + range, + iterable: false, + }, + None => IterationTarget { + range: expr + .range() + // Remove the `[` + .add_start(TextSize::from(1)) + // Remove the `]` + .sub_end(TextSize::from(1)), + iterable: true, + }, + }, + + _ => return None, + }; + + Some(result) } -/// Given a comprehension, returns the name of the iterable over which it iterates, if it's -/// a simple comprehension (e.g., `x` for `[i for i in x]`). -fn generator_iterable<'a>(elt: &'a Expr, generators: &'a Vec) -> Option<&'a str> { - // If the `elt` field is anything other than a [`Expr::Name`], we can't be sure that it - // doesn't modify the elements of the underlying iterator (e.g., `[i + 1 for i in x][0]`). - if !elt.is_name_expr() { - return None; - } - - // If there's more than 1 generator, we can't safely say that it fits the diagnostic conditions - // (e.g., `[(i, j) for i in x for j in y][0]`). - let [generator] = generators.as_slice() else { +/// Returns the [`Expr`] target for a comprehension, if the comprehension is "simple" +/// (e.g., `x` for `[i for i in x]`). +fn match_simple_comprehension(elt: &Expr, generators: &[Comprehension]) -> Option { + let [generator @ Comprehension { + is_async: false, .. + }] = generators + else { return None; }; @@ -214,8 +301,14 @@ fn generator_iterable<'a>(elt: &'a Expr, generators: &'a Vec) -> return None; } - let ast::ExprName { id, .. } = generator.iter.as_name_expr()?; - Some(id.as_str()) + // Verify that the generator is, e.g. `i for i in x`, as opposed to `i for j in x`. + let elt = elt.as_name_expr()?; + let target = generator.target.as_name_expr()?; + if elt.id != target.id { + return None; + } + + Some(generator.iter.range()) } /// If an expression is a constant integer, returns the value of that integer; otherwise, diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF015_RUF015.py.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF015_RUF015.py.snap index 4eb011f0fe..c55e737c6d 100644 --- a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF015_RUF015.py.snap +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__RUF015_RUF015.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff/src/rules/ruff/mod.rs --- -RUF015.py:4:1: RUF015 [*] Prefer `next(iter(x))` over `list(x)[0]` +RUF015.py:4:1: RUF015 [*] Prefer `next(iter(x))` over single element slice | 3 | # RUF015 4 | list(x)[0] @@ -21,7 +21,7 @@ RUF015.py:4:1: RUF015 [*] Prefer `next(iter(x))` over `list(x)[0]` 6 6 | list(x)[:1:1] 7 7 | list(x)[:1:2] -RUF015.py:5:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` +RUF015.py:5:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice | 3 | # RUF015 4 | list(x)[0] @@ -42,7 +42,7 @@ RUF015.py:5:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` 7 7 | list(x)[:1:2] 8 8 | tuple(x)[0] -RUF015.py:6:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` +RUF015.py:6:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice | 4 | list(x)[0] 5 | list(x)[:1] @@ -63,7 +63,7 @@ RUF015.py:6:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` 8 8 | tuple(x)[0] 9 9 | tuple(x)[:1] -RUF015.py:7:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` +RUF015.py:7:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice | 5 | list(x)[:1] 6 | list(x)[:1:1] @@ -84,7 +84,7 @@ RUF015.py:7:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` 9 9 | tuple(x)[:1] 10 10 | tuple(x)[:1:1] -RUF015.py:8:1: RUF015 [*] Prefer `next(iter(x))` over `list(x)[0]` +RUF015.py:8:1: RUF015 [*] Prefer `next(iter(x))` over single element slice | 6 | list(x)[:1:1] 7 | list(x)[:1:2] @@ -105,7 +105,7 @@ RUF015.py:8:1: RUF015 [*] Prefer `next(iter(x))` over `list(x)[0]` 10 10 | tuple(x)[:1:1] 11 11 | tuple(x)[:1:2] -RUF015.py:9:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` +RUF015.py:9:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice | 7 | list(x)[:1:2] 8 | tuple(x)[0] @@ -126,7 +126,7 @@ RUF015.py:9:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` 11 11 | tuple(x)[:1:2] 12 12 | list(i for i in x)[0] -RUF015.py:10:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` +RUF015.py:10:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice | 8 | tuple(x)[0] 9 | tuple(x)[:1] @@ -147,7 +147,7 @@ RUF015.py:10:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` 12 12 | list(i for i in x)[0] 13 13 | list(i for i in x)[:1] -RUF015.py:11:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` +RUF015.py:11:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice | 9 | tuple(x)[:1] 10 | tuple(x)[:1:1] @@ -168,7 +168,7 @@ RUF015.py:11:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` 13 13 | list(i for i in x)[:1] 14 14 | list(i for i in x)[:1:1] -RUF015.py:12:1: RUF015 [*] Prefer `next(iter(x))` over `list(x)[0]` +RUF015.py:12:1: RUF015 [*] Prefer `next(iter(x))` over single element slice | 10 | tuple(x)[:1:1] 11 | tuple(x)[:1:2] @@ -189,7 +189,7 @@ RUF015.py:12:1: RUF015 [*] Prefer `next(iter(x))` over `list(x)[0]` 14 14 | list(i for i in x)[:1:1] 15 15 | list(i for i in x)[:1:2] -RUF015.py:13:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` +RUF015.py:13:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice | 11 | tuple(x)[:1:2] 12 | list(i for i in x)[0] @@ -210,7 +210,7 @@ RUF015.py:13:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` 15 15 | list(i for i in x)[:1:2] 16 16 | [i for i in x][0] -RUF015.py:14:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` +RUF015.py:14:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice | 12 | list(i for i in x)[0] 13 | list(i for i in x)[:1] @@ -231,7 +231,7 @@ RUF015.py:14:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` 16 16 | [i for i in x][0] 17 17 | [i for i in x][:1] -RUF015.py:15:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` +RUF015.py:15:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice | 13 | list(i for i in x)[:1] 14 | list(i for i in x)[:1:1] @@ -252,7 +252,7 @@ RUF015.py:15:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` 17 17 | [i for i in x][:1] 18 18 | [i for i in x][:1:1] -RUF015.py:16:1: RUF015 [*] Prefer `next(iter(x))` over `list(x)[0]` +RUF015.py:16:1: RUF015 [*] Prefer `next(iter(x))` over single element slice | 14 | list(i for i in x)[:1:1] 15 | list(i for i in x)[:1:2] @@ -273,7 +273,7 @@ RUF015.py:16:1: RUF015 [*] Prefer `next(iter(x))` over `list(x)[0]` 18 18 | [i for i in x][:1:1] 19 19 | [i for i in x][:1:2] -RUF015.py:17:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` +RUF015.py:17:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice | 15 | list(i for i in x)[:1:2] 16 | [i for i in x][0] @@ -294,7 +294,7 @@ RUF015.py:17:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` 19 19 | [i for i in x][:1:2] 20 20 | -RUF015.py:18:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` +RUF015.py:18:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice | 16 | [i for i in x][0] 17 | [i for i in x][:1] @@ -314,7 +314,7 @@ RUF015.py:18:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` 20 20 | 21 21 | # OK (not indexing (solely) the first element) -RUF015.py:19:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` +RUF015.py:19:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice | 17 | [i for i in x][:1] 18 | [i for i in x][:1:1] @@ -335,4 +335,172 @@ RUF015.py:19:1: RUF015 [*] Prefer `[next(iter(x))]` over `list(x)[:1]` 21 21 | # OK (not indexing (solely) the first element) 22 22 | list(x) +RUF015.py:38:1: RUF015 [*] Prefer `next(i + 1 for i in x)` over single element slice + | +37 | # RUF015 (doesn't mirror the underlying list) +38 | [i + 1 for i in x][0] + | ^^^^^^^^^^^^^^^^^^^^^ RUF015 +39 | [i for i in x if i > 5][0] +40 | [(i, i + 1) for i in x][0] + | + = help: Replace with `next(i + 1 for i in x)` + +ℹ Suggested fix +35 35 | [i for i in x][::] +36 36 | +37 37 | # RUF015 (doesn't mirror the underlying list) +38 |-[i + 1 for i in x][0] + 38 |+next(i + 1 for i in x) +39 39 | [i for i in x if i > 5][0] +40 40 | [(i, i + 1) for i in x][0] +41 41 | + +RUF015.py:39:1: RUF015 [*] Prefer `next(i for i in x if i > 5)` over single element slice + | +37 | # RUF015 (doesn't mirror the underlying list) +38 | [i + 1 for i in x][0] +39 | [i for i in x if i > 5][0] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF015 +40 | [(i, i + 1) for i in x][0] + | + = help: Replace with `next(i for i in x if i > 5)` + +ℹ Suggested fix +36 36 | +37 37 | # RUF015 (doesn't mirror the underlying list) +38 38 | [i + 1 for i in x][0] +39 |-[i for i in x if i > 5][0] + 39 |+next(i for i in x if i > 5) +40 40 | [(i, i + 1) for i in x][0] +41 41 | +42 42 | # RUF015 (multiple generators) + +RUF015.py:40:1: RUF015 [*] Prefer `next((i, i + 1) for i in x)` over single element slice + | +38 | [i + 1 for i in x][0] +39 | [i for i in x if i > 5][0] +40 | [(i, i + 1) for i in x][0] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF015 +41 | +42 | # RUF015 (multiple generators) + | + = help: Replace with `next((i, i + 1) for i in x)` + +ℹ Suggested fix +37 37 | # RUF015 (doesn't mirror the underlying list) +38 38 | [i + 1 for i in x][0] +39 39 | [i for i in x if i > 5][0] +40 |-[(i, i + 1) for i in x][0] + 40 |+next((i, i + 1) for i in x) +41 41 | +42 42 | # RUF015 (multiple generators) +43 43 | y = range(10) + +RUF015.py:44:1: RUF015 [*] Prefer `next(i + j for i in x for j in y)` over single element slice + | +42 | # RUF015 (multiple generators) +43 | y = range(10) +44 | [i + j for i in x for j in y][0] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF015 +45 | +46 | # RUF015 + | + = help: Replace with `next(i + j for i in x for j in y)` + +ℹ Suggested fix +41 41 | +42 42 | # RUF015 (multiple generators) +43 43 | y = range(10) +44 |-[i + j for i in x for j in y][0] + 44 |+next(i + j for i in x for j in y) +45 45 | +46 46 | # RUF015 +47 47 | list(range(10))[0] + +RUF015.py:47:1: RUF015 [*] Prefer `next(iter(range(10)))` over single element slice + | +46 | # RUF015 +47 | list(range(10))[0] + | ^^^^^^^^^^^^^^^^^^ RUF015 +48 | list(x.y)[0] +49 | list(x["y"])[0] + | + = help: Replace with `next(iter(range(10)))` + +ℹ Suggested fix +44 44 | [i + j for i in x for j in y][0] +45 45 | +46 46 | # RUF015 +47 |-list(range(10))[0] + 47 |+next(iter(range(10))) +48 48 | list(x.y)[0] +49 49 | list(x["y"])[0] +50 50 | + +RUF015.py:48:1: RUF015 [*] Prefer `next(iter(x.y))` over single element slice + | +46 | # RUF015 +47 | list(range(10))[0] +48 | list(x.y)[0] + | ^^^^^^^^^^^^ RUF015 +49 | list(x["y"])[0] + | + = help: Replace with `next(iter(x.y))` + +ℹ Suggested fix +45 45 | +46 46 | # RUF015 +47 47 | list(range(10))[0] +48 |-list(x.y)[0] + 48 |+next(iter(x.y)) +49 49 | list(x["y"])[0] +50 50 | +51 51 | # RUF015 (multi-line) + +RUF015.py:49:1: RUF015 [*] Prefer `next(iter(x["y"]))` over single element slice + | +47 | list(range(10))[0] +48 | list(x.y)[0] +49 | list(x["y"])[0] + | ^^^^^^^^^^^^^^^ RUF015 +50 | +51 | # RUF015 (multi-line) + | + = help: Replace with `next(iter(x["y"]))` + +ℹ Suggested fix +46 46 | # RUF015 +47 47 | list(range(10))[0] +48 48 | list(x.y)[0] +49 |-list(x["y"])[0] + 49 |+next(iter(x["y"])) +50 50 | +51 51 | # RUF015 (multi-line) +52 52 | revision_heads_map_ast = [ + +RUF015.py:52:26: RUF015 [*] Prefer `next(...)` over single element slice + | +51 | # RUF015 (multi-line) +52 | revision_heads_map_ast = [ + | __________________________^ +53 | | a +54 | | for a in revision_heads_map_ast_obj.body +55 | | if isinstance(a, ast.Assign) and a.targets[0].id == "REVISION_HEADS_MAP" +56 | | ][0] + | |____^ RUF015 + | + = help: Replace with `next(...)` + +ℹ Suggested fix +49 49 | list(x["y"])[0] +50 50 | +51 51 | # RUF015 (multi-line) +52 |-revision_heads_map_ast = [ + 52 |+revision_heads_map_ast = next( +53 53 | a +54 54 | for a in revision_heads_map_ast_obj.body +55 55 | if isinstance(a, ast.Assign) and a.targets[0].id == "REVISION_HEADS_MAP" +56 |-][0] + 56 |+) +