From 0c91ac80a1508de96149bafe9888d43dc80b0958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikko=20Lepp=C3=A4nen?= Date: Mon, 22 Jan 2024 13:30:23 +0200 Subject: [PATCH 01/10] [RUF] - Add unnecessary dict comprehension rule (RUF023) --- .../resources/test/fixtures/ruff/RUF023.py | 60 +++++ .../src/checkers/ast/analyze/expression.rs | 8 + crates/ruff_linter/src/codes.rs | 1 + ...flake8_executable__tests__EXE001_1.py.snap | 8 - ...flake8_executable__tests__EXE002_1.py.snap | 7 - crates/ruff_linter/src/rules/ruff/mod.rs | 1 + .../ruff_linter/src/rules/ruff/rules/mod.rs | 2 + ...cessary_dict_comprehension_for_iterable.rs | 180 +++++++++++++++ ..._rules__ruff__tests__RUF023_RUF023.py.snap | 206 ++++++++++++++++++ ruff.schema.json | 1 + 10 files changed, 459 insertions(+), 15 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/ruff/RUF023.py create mode 100644 crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs create mode 100644 crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF023_RUF023.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF023.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF023.py new file mode 100644 index 0000000000000..8548db6002ce5 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF023.py @@ -0,0 +1,60 @@ +# Violation cases: RUF023 + +def foo(): + numbers = [1,2,3] + {n: None for n in numbers} # RUF023 + +def foo(): + for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF023 + pass + +def foo(): + {n: 1.1 for n in [1,2,3]} # RUF023 + +def foo(): + {n: complex(3, 5) for n in [1,2,3]} # RUF023 + +def foo(): + def f(data): + return data + f({c: "a" for c in "12345"}) # RUF023 + +def foo(): + {n: True for n in [1,2,2]} # RUF023 + +def foo(): + {n: b'hello' for n in (1,2,2)} # RUF023 + +def foo(): + {n: ... for n in [1,2,3]} # RUF023 + +def foo(): + values = ["a", "b", "c"] + [{n: values for n in [1,2,3]}] # RUF023 + +def foo(): + def f(): + return 1 + + a = f() + {n: a for n in [1,2,3]} # RUF023 + +def foo(): + def f(): + return {n: 1 for c in [1,2,3,4,5] for n in [1,2,3]} # RUF023 + + f() + + +# Non-violation cases: RUF023 + +def foo(): + {n: 1 for n in [1,2,3,4,5] if n < 3} # OK + +def foo(): + {n: 1 for c in [1,2,3,4,5] for n in [1,2,3] if c < 3} # OK + +def foo(): + def f(): + pass + {n: f() for n in [1,2,3]} # OK diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 75ee29c6ea5d0..1f3d5447ff646 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -1419,11 +1419,19 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::UnnecessaryDictIndexLookup) { pylint::rules::unnecessary_dict_index_lookup_comprehension(checker, expr); } + if checker.enabled(Rule::UnnecessaryComprehension) { flake8_comprehensions::rules::unnecessary_dict_comprehension( checker, expr, key, value, generators, ); } + + if checker.enabled(Rule::UnnecessaryDictComprehensionForIterable) { + ruff::rules::unnecessary_dict_comprehension_for_iterable( + checker, expr, value, generators, + ); + } + if checker.enabled(Rule::FunctionUsesLoopVariable) { flake8_bugbear::rules::function_uses_loop_variable(checker, &Node::Expr(expr)); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 40bbdabb659a3..e392f208abb3b 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -926,6 +926,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "020") => (RuleGroup::Preview, rules::ruff::rules::NeverUnion), (Ruff, "021") => (RuleGroup::Preview, rules::ruff::rules::ParenthesizeChainedOperators), (Ruff, "022") => (RuleGroup::Preview, rules::ruff::rules::UnsortedDunderAll), + (Ruff, "023") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryDictComprehensionForIterable), (Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA), (Ruff, "200") => (RuleGroup::Stable, rules::ruff::rules::InvalidPyprojectToml), diff --git a/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE001_1.py.snap b/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE001_1.py.snap index 0a58f7dfccd1f..26e075ae3eb6e 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE001_1.py.snap +++ b/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE001_1.py.snap @@ -1,12 +1,4 @@ --- source: crates/ruff_linter/src/rules/flake8_executable/mod.rs --- -EXE001_1.py:1:1: EXE001 Shebang is present but file is not executable - | -1 | #!/usr/bin/python - | ^^^^^^^^^^^^^^^^^ EXE001 -2 | -3 | if __name__ == '__main__': - | - diff --git a/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE002_1.py.snap b/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE002_1.py.snap index 8f47999522a75..26e075ae3eb6e 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE002_1.py.snap +++ b/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE002_1.py.snap @@ -1,11 +1,4 @@ --- source: crates/ruff_linter/src/rules/flake8_executable/mod.rs --- -EXE002_1.py:1:1: EXE002 The file is executable but no shebang is present - | -1 | if __name__ == '__main__': - | EXE002 -2 | print('I should be executable.') - | - diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index d4eddf142ed95..907987663168f 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -43,6 +43,7 @@ mod tests { #[test_case(Rule::NeverUnion, Path::new("RUF020.py"))] #[test_case(Rule::ParenthesizeChainedOperators, Path::new("RUF021.py"))] #[test_case(Rule::UnsortedDunderAll, Path::new("RUF022.py"))] + #[test_case(Rule::UnnecessaryDictComprehensionForIterable, Path::new("RUF023.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/ruff/rules/mod.rs b/crates/ruff_linter/src/rules/ruff/rules/mod.rs index 07f55e184b1ad..54f9b000af495 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mod.rs @@ -15,6 +15,7 @@ pub(crate) use parenthesize_logical_operators::*; pub(crate) use quadratic_list_summation::*; pub(crate) use sort_dunder_all::*; pub(crate) use static_key_dict_comprehension::*; +pub(crate) use unnecessary_dict_comprehension_for_iterable::*; pub(crate) use unnecessary_iterable_allocation_for_first_element::*; pub(crate) use unnecessary_key_check::*; pub(crate) use unused_noqa::*; @@ -37,6 +38,7 @@ mod pairwise_over_zipped; mod parenthesize_logical_operators; mod sort_dunder_all; mod static_key_dict_comprehension; +mod unnecessary_dict_comprehension_for_iterable; mod unnecessary_iterable_allocation_for_first_element; mod unnecessary_key_check; mod unused_noqa; diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs new file mode 100644 index 0000000000000..36caada64aad4 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs @@ -0,0 +1,180 @@ +use anyhow::{bail, Result}; +use libcst_native::{Arg, Call, Expression, Name, ParenthesizableWhitespace}; +use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::{self as ast, Comprehension, Expr}; +use ruff_python_codegen::Stylist; +use ruff_source_file::Locator; +use ruff_text_size::Ranged; + +use crate::checkers::ast::Checker; +use crate::cst::matchers::match_expression; +use crate::fix::codemods::CodegenStylist; +use crate::fix::edits::pad; + +/// ## What it does +/// Checks for unnecessary `dict` comprehension when creating a new dictionary from iterable. +/// +/// ## Why is this bad? +/// It's unnecessary to use a `dict` comprehension to build a dictionary from an iterable when the value is `static`. +/// Use `dict.fromkeys(iterable)` instead of `{value: None for value in iterable}`. +/// +/// `dict.fromkeys(iterable)` is more readable and faster than `{value: None for value in iterable}`. +/// +/// +/// ## Examples +/// ```python +/// {a: None for a in iterable} +/// {a: 1 for a in iterable} +/// ``` +/// +/// Use instead: +/// ```python +/// dict.fromkeys(iterable) +/// dict.fromkeys(iterable, 1) +/// ``` +#[violation] +pub struct UnnecessaryDictComprehensionForIterable { + is_value_none_literal: bool, +} + +impl AlwaysFixableViolation for UnnecessaryDictComprehensionForIterable { + #[derive_message_formats] + #[allow(clippy::match_bool)] + fn message(&self) -> String { + if self.is_value_none_literal { + format!( + "Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable)`)" + ) + } else { + format!( + "Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`)" + ) + } + } + + #[allow(clippy::match_bool)] + fn fix_title(&self) -> String { + if self.is_value_none_literal { + format!("Rewrite using `dict.fromkeys(iterable, value)`)",) + } else { + format!("Rewrite using `dict.fromkeys(iterable)`)") + } + } +} + +/// RUF023 +pub(crate) fn unnecessary_dict_comprehension_for_iterable( + checker: &mut Checker, + expr: &Expr, + value: &Expr, + generators: &[Comprehension], +) { + let [generator] = generators else { + return; + }; + if !generator.ifs.is_empty() && generator.is_async { + return; + } + let Expr::Name(_) = &generator.target else { + return; + }; + + if !has_valid_expression_type(value) { + return; + } + + let mut diagnostic = Diagnostic::new( + UnnecessaryDictComprehensionForIterable { + is_value_none_literal: value.is_none_literal_expr(), + }, + expr.range(), + ); + diagnostic.try_set_fix(|| { + fix_unnecessary_dict_comprehension(expr, checker.locator(), checker.stylist()) + .map(Fix::safe_edit) + }); + checker.diagnostics.push(diagnostic); +} + +// only accept `None`, `Ellipsis`, `True`, `False`, `Number`, `String`, `Bytes`, `Name` as value +fn has_valid_expression_type(node: &ast::Expr) -> bool { + matches!( + node, + ast::Expr::StringLiteral(_) + | ast::Expr::BytesLiteral(_) + | ast::Expr::NumberLiteral(_) + | ast::Expr::BooleanLiteral(_) + | ast::Expr::NoneLiteral(_) + | ast::Expr::EllipsisLiteral(_) + | ast::Expr::Name(_) + ) +} + +/// Generate a [`Fix`] to replace `dict` comprehension with `dict.fromkeys`. +/// (RUF023) Convert `{n: None for n in [1,2,3]}` to `dict.fromkeys([1,2,3])` or +/// `{n: 1 for n in [1,2,3]}` to `dict.fromkeys([1,2,3], 1)`. +fn fix_unnecessary_dict_comprehension( + expr: &Expr, + locator: &Locator, + stylist: &Stylist, +) -> Result { + let module_text = locator.slice(expr); + let mut tree = match_expression(module_text)?; + + match &tree { + Expression::DictComp(inner) => { + let args = match &*inner.value { + Expression::Name(value) if value.value == "None" => { + vec![Arg { + value: inner.for_in.iter.clone(), + keyword: None, + equal: None, + comma: None, + star: "", + whitespace_after_star: ParenthesizableWhitespace::default(), + whitespace_after_arg: ParenthesizableWhitespace::default(), + }] + } + _ => vec![ + Arg { + value: inner.for_in.iter.clone(), + keyword: None, + equal: None, + comma: None, + star: "", + whitespace_after_star: ParenthesizableWhitespace::default(), + whitespace_after_arg: ParenthesizableWhitespace::default(), + }, + Arg { + value: *inner.value.clone(), + keyword: None, + equal: None, + comma: None, + star: "", + whitespace_after_star: ParenthesizableWhitespace::default(), + whitespace_after_arg: ParenthesizableWhitespace::default(), + }, + ], + }; + tree = Expression::Call(Box::new(Call { + func: Box::new(Expression::Name(Box::new(Name { + value: "dict.fromkeys", + lpar: vec![], + rpar: vec![], + }))), + args, + lpar: vec![], + rpar: vec![], + whitespace_after_func: ParenthesizableWhitespace::default(), + whitespace_before_args: ParenthesizableWhitespace::default(), + })); + } + _ => bail!("Expected a dict comprehension"), + } + + Ok(Edit::range_replacement( + pad(tree.codegen_stylist(stylist), expr.range(), locator), + expr.range(), + )) +} diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF023_RUF023.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF023_RUF023.py.snap new file mode 100644 index 0000000000000..b84b0a5976cc7 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF023_RUF023.py.snap @@ -0,0 +1,206 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +--- +RUF023.py:5:5: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable)`) + | +3 | def foo(): +4 | numbers = [1,2,3] +5 | {n: None for n in numbers} # RUF023 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 +6 | +7 | def foo(): + | + = help: Rewrite using `dict.fromkeys(iterable, value)`) + +ℹ Safe fix +2 2 | +3 3 | def foo(): +4 4 | numbers = [1,2,3] +5 |- {n: None for n in numbers} # RUF023 + 5 |+ dict.fromkeys(numbers) # RUF023 +6 6 | +7 7 | def foo(): +8 8 | for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF023 + +RUF023.py:8:23: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) + | +7 | def foo(): +8 | for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF023 + | ^^^^^^^^^^^^^^^^^^^^^^^ RUF023 +9 | pass + | + = help: Rewrite using `dict.fromkeys(iterable)`) + +ℹ Safe fix +5 5 | {n: None for n in numbers} # RUF023 +6 6 | +7 7 | def foo(): +8 |- for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF023 + 8 |+ for key, value in dict.fromkeys([1,2,3], 1).items(): # RUF023 +9 9 | pass +10 10 | +11 11 | def foo(): + +RUF023.py:12:5: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) + | +11 | def foo(): +12 | {n: 1.1 for n in [1,2,3]} # RUF023 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 +13 | +14 | def foo(): + | + = help: Rewrite using `dict.fromkeys(iterable)`) + +ℹ Safe fix +9 9 | pass +10 10 | +11 11 | def foo(): +12 |- {n: 1.1 for n in [1,2,3]} # RUF023 + 12 |+ dict.fromkeys([1,2,3], 1.1) # RUF023 +13 13 | +14 14 | def foo(): +15 15 | {n: complex(3, 5) for n in [1,2,3]} # RUF023 + +RUF023.py:20:7: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) + | +18 | def f(data): +19 | return data +20 | f({c: "a" for c in "12345"}) # RUF023 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 +21 | +22 | def foo(): + | + = help: Rewrite using `dict.fromkeys(iterable)`) + +ℹ Safe fix +17 17 | def foo(): +18 18 | def f(data): +19 19 | return data +20 |- f({c: "a" for c in "12345"}) # RUF023 + 20 |+ f(dict.fromkeys("12345", "a")) # RUF023 +21 21 | +22 22 | def foo(): +23 23 | {n: True for n in [1,2,2]} # RUF023 + +RUF023.py:23:5: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) + | +22 | def foo(): +23 | {n: True for n in [1,2,2]} # RUF023 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 +24 | +25 | def foo(): + | + = help: Rewrite using `dict.fromkeys(iterable)`) + +ℹ Safe fix +20 20 | f({c: "a" for c in "12345"}) # RUF023 +21 21 | +22 22 | def foo(): +23 |- {n: True for n in [1,2,2]} # RUF023 + 23 |+ dict.fromkeys([1,2,2], True) # RUF023 +24 24 | +25 25 | def foo(): +26 26 | {n: b'hello' for n in (1,2,2)} # RUF023 + +RUF023.py:26:5: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) + | +25 | def foo(): +26 | {n: b'hello' for n in (1,2,2)} # RUF023 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 +27 | +28 | def foo(): + | + = help: Rewrite using `dict.fromkeys(iterable)`) + +ℹ Safe fix +23 23 | {n: True for n in [1,2,2]} # RUF023 +24 24 | +25 25 | def foo(): +26 |- {n: b'hello' for n in (1,2,2)} # RUF023 + 26 |+ dict.fromkeys((1,2,2), b'hello') # RUF023 +27 27 | +28 28 | def foo(): +29 29 | {n: ... for n in [1,2,3]} # RUF023 + +RUF023.py:29:5: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) + | +28 | def foo(): +29 | {n: ... for n in [1,2,3]} # RUF023 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 +30 | +31 | def foo(): + | + = help: Rewrite using `dict.fromkeys(iterable)`) + +ℹ Safe fix +26 26 | {n: b'hello' for n in (1,2,2)} # RUF023 +27 27 | +28 28 | def foo(): +29 |- {n: ... for n in [1,2,3]} # RUF023 + 29 |+ dict.fromkeys([1,2,3], ...) # RUF023 +30 30 | +31 31 | def foo(): +32 32 | values = ["a", "b", "c"] + +RUF023.py:33:6: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) + | +31 | def foo(): +32 | values = ["a", "b", "c"] +33 | [{n: values for n in [1,2,3]}] # RUF023 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 +34 | +35 | def foo(): + | + = help: Rewrite using `dict.fromkeys(iterable)`) + +ℹ Safe fix +30 30 | +31 31 | def foo(): +32 32 | values = ["a", "b", "c"] +33 |- [{n: values for n in [1,2,3]}] # RUF023 + 33 |+ [dict.fromkeys([1,2,3], values)] # RUF023 +34 34 | +35 35 | def foo(): +36 36 | def f(): + +RUF023.py:40:5: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) + | +39 | a = f() +40 | {n: a for n in [1,2,3]} # RUF023 + | ^^^^^^^^^^^^^^^^^^^^^^^ RUF023 +41 | +42 | def foo(): + | + = help: Rewrite using `dict.fromkeys(iterable)`) + +ℹ Safe fix +37 37 | return 1 +38 38 | +39 39 | a = f() +40 |- {n: a for n in [1,2,3]} # RUF023 + 40 |+ dict.fromkeys([1,2,3], a) # RUF023 +41 41 | +42 42 | def foo(): +43 43 | def f(): + +RUF023.py:52:5: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) + | +51 | def foo(): +52 | {n: 1 for n in [1,2,3,4,5] if n < 3} # OK + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 +53 | +54 | def foo(): + | + = help: Rewrite using `dict.fromkeys(iterable)`) + +ℹ Safe fix +49 49 | # Non-violation cases: RUF023 +50 50 | +51 51 | def foo(): +52 |- {n: 1 for n in [1,2,3,4,5] if n < 3} # OK + 52 |+ dict.fromkeys([1,2,3,4,5], 1) # OK +53 53 | +54 54 | def foo(): +55 55 | {n: 1 for c in [1,2,3,4,5] for n in [1,2,3] if c < 3} # OK + + diff --git a/ruff.schema.json b/ruff.schema.json index 9ea851bd26b54..cb933832e8a47 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3449,6 +3449,7 @@ "RUF020", "RUF021", "RUF022", + "RUF023", "RUF1", "RUF10", "RUF100", From 4105716e1d313e47e7670233a8206af7681874f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikko=20Lepp=C3=A4nen?= Date: Mon, 22 Jan 2024 14:24:13 +0200 Subject: [PATCH 02/10] fix linux tests and use new rule name RUF025 --- .../resources/test/fixtures/ruff/RUF025.py | 26 +-- ...cessary_dict_comprehension_for_iterable.rs | 4 +- ..._rules__ruff__tests__RUF023_RUF023.py.snap | 206 ------------------ ..._rules__ruff__tests__RUF025_RUF025.py.snap | 74 +++---- 4 files changed, 52 insertions(+), 258 deletions(-) delete mode 100644 crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF023_RUF023.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py index 8548db6002ce5..5b9258059cbb5 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py @@ -1,52 +1,52 @@ -# Violation cases: RUF023 +# Violation cases: RUF025 def foo(): numbers = [1,2,3] - {n: None for n in numbers} # RUF023 + {n: None for n in numbers} # RUF025 def foo(): - for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF023 + for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF025 pass def foo(): - {n: 1.1 for n in [1,2,3]} # RUF023 + {n: 1.1 for n in [1,2,3]} # RUF025 def foo(): - {n: complex(3, 5) for n in [1,2,3]} # RUF023 + {n: complex(3, 5) for n in [1,2,3]} # RUF025 def foo(): def f(data): return data - f({c: "a" for c in "12345"}) # RUF023 + f({c: "a" for c in "12345"}) # RUF025 def foo(): - {n: True for n in [1,2,2]} # RUF023 + {n: True for n in [1,2,2]} # RUF025 def foo(): - {n: b'hello' for n in (1,2,2)} # RUF023 + {n: b'hello' for n in (1,2,2)} # RUF025 def foo(): - {n: ... for n in [1,2,3]} # RUF023 + {n: ... for n in [1,2,3]} # RUF025 def foo(): values = ["a", "b", "c"] - [{n: values for n in [1,2,3]}] # RUF023 + [{n: values for n in [1,2,3]}] # RUF025 def foo(): def f(): return 1 a = f() - {n: a for n in [1,2,3]} # RUF023 + {n: a for n in [1,2,3]} # RUF025 def foo(): def f(): - return {n: 1 for c in [1,2,3,4,5] for n in [1,2,3]} # RUF023 + return {n: 1 for c in [1,2,3,4,5] for n in [1,2,3]} # RUF025 f() -# Non-violation cases: RUF023 +# Non-violation cases: RUF025 def foo(): {n: 1 for n in [1,2,3,4,5] if n < 3} # OK diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs index 36caada64aad4..16f5d75e16a9d 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs @@ -63,7 +63,7 @@ impl AlwaysFixableViolation for UnnecessaryDictComprehensionForIterable { } } -/// RUF023 +/// RUF025 pub(crate) fn unnecessary_dict_comprehension_for_iterable( checker: &mut Checker, expr: &Expr, @@ -112,7 +112,7 @@ fn has_valid_expression_type(node: &ast::Expr) -> bool { } /// Generate a [`Fix`] to replace `dict` comprehension with `dict.fromkeys`. -/// (RUF023) Convert `{n: None for n in [1,2,3]}` to `dict.fromkeys([1,2,3])` or +/// (RUF025) Convert `{n: None for n in [1,2,3]}` to `dict.fromkeys([1,2,3])` or /// `{n: 1 for n in [1,2,3]}` to `dict.fromkeys([1,2,3], 1)`. fn fix_unnecessary_dict_comprehension( expr: &Expr, diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF023_RUF023.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF023_RUF023.py.snap deleted file mode 100644 index b84b0a5976cc7..0000000000000 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF023_RUF023.py.snap +++ /dev/null @@ -1,206 +0,0 @@ ---- -source: crates/ruff_linter/src/rules/ruff/mod.rs ---- -RUF023.py:5:5: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable)`) - | -3 | def foo(): -4 | numbers = [1,2,3] -5 | {n: None for n in numbers} # RUF023 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 -6 | -7 | def foo(): - | - = help: Rewrite using `dict.fromkeys(iterable, value)`) - -ℹ Safe fix -2 2 | -3 3 | def foo(): -4 4 | numbers = [1,2,3] -5 |- {n: None for n in numbers} # RUF023 - 5 |+ dict.fromkeys(numbers) # RUF023 -6 6 | -7 7 | def foo(): -8 8 | for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF023 - -RUF023.py:8:23: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) - | -7 | def foo(): -8 | for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF023 - | ^^^^^^^^^^^^^^^^^^^^^^^ RUF023 -9 | pass - | - = help: Rewrite using `dict.fromkeys(iterable)`) - -ℹ Safe fix -5 5 | {n: None for n in numbers} # RUF023 -6 6 | -7 7 | def foo(): -8 |- for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF023 - 8 |+ for key, value in dict.fromkeys([1,2,3], 1).items(): # RUF023 -9 9 | pass -10 10 | -11 11 | def foo(): - -RUF023.py:12:5: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) - | -11 | def foo(): -12 | {n: 1.1 for n in [1,2,3]} # RUF023 - | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 -13 | -14 | def foo(): - | - = help: Rewrite using `dict.fromkeys(iterable)`) - -ℹ Safe fix -9 9 | pass -10 10 | -11 11 | def foo(): -12 |- {n: 1.1 for n in [1,2,3]} # RUF023 - 12 |+ dict.fromkeys([1,2,3], 1.1) # RUF023 -13 13 | -14 14 | def foo(): -15 15 | {n: complex(3, 5) for n in [1,2,3]} # RUF023 - -RUF023.py:20:7: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) - | -18 | def f(data): -19 | return data -20 | f({c: "a" for c in "12345"}) # RUF023 - | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 -21 | -22 | def foo(): - | - = help: Rewrite using `dict.fromkeys(iterable)`) - -ℹ Safe fix -17 17 | def foo(): -18 18 | def f(data): -19 19 | return data -20 |- f({c: "a" for c in "12345"}) # RUF023 - 20 |+ f(dict.fromkeys("12345", "a")) # RUF023 -21 21 | -22 22 | def foo(): -23 23 | {n: True for n in [1,2,2]} # RUF023 - -RUF023.py:23:5: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) - | -22 | def foo(): -23 | {n: True for n in [1,2,2]} # RUF023 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 -24 | -25 | def foo(): - | - = help: Rewrite using `dict.fromkeys(iterable)`) - -ℹ Safe fix -20 20 | f({c: "a" for c in "12345"}) # RUF023 -21 21 | -22 22 | def foo(): -23 |- {n: True for n in [1,2,2]} # RUF023 - 23 |+ dict.fromkeys([1,2,2], True) # RUF023 -24 24 | -25 25 | def foo(): -26 26 | {n: b'hello' for n in (1,2,2)} # RUF023 - -RUF023.py:26:5: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) - | -25 | def foo(): -26 | {n: b'hello' for n in (1,2,2)} # RUF023 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 -27 | -28 | def foo(): - | - = help: Rewrite using `dict.fromkeys(iterable)`) - -ℹ Safe fix -23 23 | {n: True for n in [1,2,2]} # RUF023 -24 24 | -25 25 | def foo(): -26 |- {n: b'hello' for n in (1,2,2)} # RUF023 - 26 |+ dict.fromkeys((1,2,2), b'hello') # RUF023 -27 27 | -28 28 | def foo(): -29 29 | {n: ... for n in [1,2,3]} # RUF023 - -RUF023.py:29:5: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) - | -28 | def foo(): -29 | {n: ... for n in [1,2,3]} # RUF023 - | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 -30 | -31 | def foo(): - | - = help: Rewrite using `dict.fromkeys(iterable)`) - -ℹ Safe fix -26 26 | {n: b'hello' for n in (1,2,2)} # RUF023 -27 27 | -28 28 | def foo(): -29 |- {n: ... for n in [1,2,3]} # RUF023 - 29 |+ dict.fromkeys([1,2,3], ...) # RUF023 -30 30 | -31 31 | def foo(): -32 32 | values = ["a", "b", "c"] - -RUF023.py:33:6: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) - | -31 | def foo(): -32 | values = ["a", "b", "c"] -33 | [{n: values for n in [1,2,3]}] # RUF023 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 -34 | -35 | def foo(): - | - = help: Rewrite using `dict.fromkeys(iterable)`) - -ℹ Safe fix -30 30 | -31 31 | def foo(): -32 32 | values = ["a", "b", "c"] -33 |- [{n: values for n in [1,2,3]}] # RUF023 - 33 |+ [dict.fromkeys([1,2,3], values)] # RUF023 -34 34 | -35 35 | def foo(): -36 36 | def f(): - -RUF023.py:40:5: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) - | -39 | a = f() -40 | {n: a for n in [1,2,3]} # RUF023 - | ^^^^^^^^^^^^^^^^^^^^^^^ RUF023 -41 | -42 | def foo(): - | - = help: Rewrite using `dict.fromkeys(iterable)`) - -ℹ Safe fix -37 37 | return 1 -38 38 | -39 39 | a = f() -40 |- {n: a for n in [1,2,3]} # RUF023 - 40 |+ dict.fromkeys([1,2,3], a) # RUF023 -41 41 | -42 42 | def foo(): -43 43 | def f(): - -RUF023.py:52:5: RUF023 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) - | -51 | def foo(): -52 | {n: 1 for n in [1,2,3,4,5] if n < 3} # OK - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF023 -53 | -54 | def foo(): - | - = help: Rewrite using `dict.fromkeys(iterable)`) - -ℹ Safe fix -49 49 | # Non-violation cases: RUF023 -50 50 | -51 51 | def foo(): -52 |- {n: 1 for n in [1,2,3,4,5] if n < 3} # OK - 52 |+ dict.fromkeys([1,2,3,4,5], 1) # OK -53 53 | -54 54 | def foo(): -55 55 | {n: 1 for c in [1,2,3,4,5] for n in [1,2,3] if c < 3} # OK - - diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap index 547e6c66dff73..3a362c0977d0e 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap @@ -5,7 +5,7 @@ RUF025.py:5:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite u | 3 | def foo(): 4 | numbers = [1,2,3] -5 | {n: None for n in numbers} # RUF023 +5 | {n: None for n in numbers} # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 6 | 7 | def foo(): @@ -16,27 +16,27 @@ RUF025.py:5:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite u 2 2 | 3 3 | def foo(): 4 4 | numbers = [1,2,3] -5 |- {n: None for n in numbers} # RUF023 - 5 |+ dict.fromkeys(numbers) # RUF023 +5 |- {n: None for n in numbers} # RUF025 + 5 |+ dict.fromkeys(numbers) # RUF025 6 6 | 7 7 | def foo(): -8 8 | for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF023 +8 8 | for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF025 RUF025.py:8:23: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) | 7 | def foo(): -8 | for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF023 +8 | for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^ RUF025 9 | pass | = help: Rewrite using `dict.fromkeys(iterable)`) ℹ Safe fix -5 5 | {n: None for n in numbers} # RUF023 +5 5 | {n: None for n in numbers} # RUF025 6 6 | 7 7 | def foo(): -8 |- for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF023 - 8 |+ for key, value in dict.fromkeys([1,2,3], 1).items(): # RUF023 +8 |- for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF025 + 8 |+ for key, value in dict.fromkeys([1,2,3], 1).items(): # RUF025 9 9 | pass 10 10 | 11 11 | def foo(): @@ -44,7 +44,7 @@ RUF025.py:8:23: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite RUF025.py:12:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) | 11 | def foo(): -12 | {n: 1.1 for n in [1,2,3]} # RUF023 +12 | {n: 1.1 for n in [1,2,3]} # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 13 | 14 | def foo(): @@ -55,17 +55,17 @@ RUF025.py:12:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite 9 9 | pass 10 10 | 11 11 | def foo(): -12 |- {n: 1.1 for n in [1,2,3]} # RUF023 - 12 |+ dict.fromkeys([1,2,3], 1.1) # RUF023 +12 |- {n: 1.1 for n in [1,2,3]} # RUF025 + 12 |+ dict.fromkeys([1,2,3], 1.1) # RUF025 13 13 | 14 14 | def foo(): -15 15 | {n: complex(3, 5) for n in [1,2,3]} # RUF023 +15 15 | {n: complex(3, 5) for n in [1,2,3]} # RUF025 RUF025.py:20:7: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) | 18 | def f(data): 19 | return data -20 | f({c: "a" for c in "12345"}) # RUF023 +20 | f({c: "a" for c in "12345"}) # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 21 | 22 | def foo(): @@ -76,16 +76,16 @@ RUF025.py:20:7: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite 17 17 | def foo(): 18 18 | def f(data): 19 19 | return data -20 |- f({c: "a" for c in "12345"}) # RUF023 - 20 |+ f(dict.fromkeys("12345", "a")) # RUF023 +20 |- f({c: "a" for c in "12345"}) # RUF025 + 20 |+ f(dict.fromkeys("12345", "a")) # RUF025 21 21 | 22 22 | def foo(): -23 23 | {n: True for n in [1,2,2]} # RUF023 +23 23 | {n: True for n in [1,2,2]} # RUF025 RUF025.py:23:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) | 22 | def foo(): -23 | {n: True for n in [1,2,2]} # RUF023 +23 | {n: True for n in [1,2,2]} # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 24 | 25 | def foo(): @@ -93,19 +93,19 @@ RUF025.py:23:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite = help: Rewrite using `dict.fromkeys(iterable)`) ℹ Safe fix -20 20 | f({c: "a" for c in "12345"}) # RUF023 +20 20 | f({c: "a" for c in "12345"}) # RUF025 21 21 | 22 22 | def foo(): -23 |- {n: True for n in [1,2,2]} # RUF023 - 23 |+ dict.fromkeys([1,2,2], True) # RUF023 +23 |- {n: True for n in [1,2,2]} # RUF025 + 23 |+ dict.fromkeys([1,2,2], True) # RUF025 24 24 | 25 25 | def foo(): -26 26 | {n: b'hello' for n in (1,2,2)} # RUF023 +26 26 | {n: b'hello' for n in (1,2,2)} # RUF025 RUF025.py:26:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) | 25 | def foo(): -26 | {n: b'hello' for n in (1,2,2)} # RUF023 +26 | {n: b'hello' for n in (1,2,2)} # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 27 | 28 | def foo(): @@ -113,19 +113,19 @@ RUF025.py:26:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite = help: Rewrite using `dict.fromkeys(iterable)`) ℹ Safe fix -23 23 | {n: True for n in [1,2,2]} # RUF023 +23 23 | {n: True for n in [1,2,2]} # RUF025 24 24 | 25 25 | def foo(): -26 |- {n: b'hello' for n in (1,2,2)} # RUF023 - 26 |+ dict.fromkeys((1,2,2), b'hello') # RUF023 +26 |- {n: b'hello' for n in (1,2,2)} # RUF025 + 26 |+ dict.fromkeys((1,2,2), b'hello') # RUF025 27 27 | 28 28 | def foo(): -29 29 | {n: ... for n in [1,2,3]} # RUF023 +29 29 | {n: ... for n in [1,2,3]} # RUF025 RUF025.py:29:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) | 28 | def foo(): -29 | {n: ... for n in [1,2,3]} # RUF023 +29 | {n: ... for n in [1,2,3]} # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 30 | 31 | def foo(): @@ -133,11 +133,11 @@ RUF025.py:29:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite = help: Rewrite using `dict.fromkeys(iterable)`) ℹ Safe fix -26 26 | {n: b'hello' for n in (1,2,2)} # RUF023 +26 26 | {n: b'hello' for n in (1,2,2)} # RUF025 27 27 | 28 28 | def foo(): -29 |- {n: ... for n in [1,2,3]} # RUF023 - 29 |+ dict.fromkeys([1,2,3], ...) # RUF023 +29 |- {n: ... for n in [1,2,3]} # RUF025 + 29 |+ dict.fromkeys([1,2,3], ...) # RUF025 30 30 | 31 31 | def foo(): 32 32 | values = ["a", "b", "c"] @@ -146,7 +146,7 @@ RUF025.py:33:6: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite | 31 | def foo(): 32 | values = ["a", "b", "c"] -33 | [{n: values for n in [1,2,3]}] # RUF023 +33 | [{n: values for n in [1,2,3]}] # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 34 | 35 | def foo(): @@ -157,8 +157,8 @@ RUF025.py:33:6: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite 30 30 | 31 31 | def foo(): 32 32 | values = ["a", "b", "c"] -33 |- [{n: values for n in [1,2,3]}] # RUF023 - 33 |+ [dict.fromkeys([1,2,3], values)] # RUF023 +33 |- [{n: values for n in [1,2,3]}] # RUF025 + 33 |+ [dict.fromkeys([1,2,3], values)] # RUF025 34 34 | 35 35 | def foo(): 36 36 | def f(): @@ -166,7 +166,7 @@ RUF025.py:33:6: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite RUF025.py:40:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) | 39 | a = f() -40 | {n: a for n in [1,2,3]} # RUF023 +40 | {n: a for n in [1,2,3]} # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^ RUF025 41 | 42 | def foo(): @@ -177,8 +177,8 @@ RUF025.py:40:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite 37 37 | return 1 38 38 | 39 39 | a = f() -40 |- {n: a for n in [1,2,3]} # RUF023 - 40 |+ dict.fromkeys([1,2,3], a) # RUF023 +40 |- {n: a for n in [1,2,3]} # RUF025 + 40 |+ dict.fromkeys([1,2,3], a) # RUF025 41 41 | 42 42 | def foo(): 43 43 | def f(): @@ -194,7 +194,7 @@ RUF025.py:52:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite = help: Rewrite using `dict.fromkeys(iterable)`) ℹ Safe fix -49 49 | # Non-violation cases: RUF023 +49 49 | # Non-violation cases: RUF025 50 50 | 51 51 | def foo(): 52 |- {n: 1 for n in [1,2,3,4,5] if n < 3} # OK From cdb3e96ec9c4ea77ea1d866aa3bdbe399d65333c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikko=20Lepp=C3=A4nen?= Date: Mon, 22 Jan 2024 14:53:04 +0200 Subject: [PATCH 03/10] reverted flake8 snapshots --- ...nter__rules__flake8_executable__tests__EXE001_1.py.snap | 7 +++++++ ...nter__rules__flake8_executable__tests__EXE002_1.py.snap | 7 ++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE001_1.py.snap b/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE001_1.py.snap index 26e075ae3eb6e..ba870fb9657b3 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE001_1.py.snap +++ b/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE001_1.py.snap @@ -1,4 +1,11 @@ --- source: crates/ruff_linter/src/rules/flake8_executable/mod.rs --- +EXE001_1.py:1:1: EXE001 Shebang is present but file is not executable + | +1 | #!/usr/bin/python + | ^^^^^^^^^^^^^^^^^ EXE001 +2 | +3 | if __name__ == '__main__': + | diff --git a/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE002_1.py.snap b/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE002_1.py.snap index 26e075ae3eb6e..2ca899f1c27d6 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE002_1.py.snap +++ b/crates/ruff_linter/src/rules/flake8_executable/snapshots/ruff_linter__rules__flake8_executable__tests__EXE002_1.py.snap @@ -1,4 +1,9 @@ --- source: crates/ruff_linter/src/rules/flake8_executable/mod.rs --- - +EXE002_1.py:1:1: EXE002 The file is executable but no shebang is present + | +1 | if __name__ == '__main__': + | EXE002 +2 | print('I should be executable.') + | From 1e3e24b8ae34deb9b6f4463a757ec377530321df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikko=20Lepp=C3=A4nen?= Date: Tue, 23 Jan 2024 08:39:06 +0200 Subject: [PATCH 04/10] fix: don't provide suggestion if comprehension contains nested loops or key-value pair in comprehension is binded to a same name --- .../resources/test/fixtures/ruff/RUF025.py | 14 +++++-- .../src/checkers/ast/analyze/expression.rs | 2 +- ...cessary_dict_comprehension_for_iterable.rs | 14 ++++++- ..._rules__ruff__tests__RUF025_RUF025.py.snap | 42 +++++++++++++------ 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py index 5b9258059cbb5..9d4680fd8dfa6 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py @@ -40,10 +40,7 @@ def f(): {n: a for n in [1,2,3]} # RUF025 def foo(): - def f(): - return {n: 1 for c in [1,2,3,4,5] for n in [1,2,3]} # RUF025 - - f() + {n: False for n in {1: "a", 2: "b"}} # RUF025 # Non-violation cases: RUF025 @@ -58,3 +55,12 @@ def foo(): def f(): pass {n: f() for n in [1,2,3]} # OK + +def foo(): + {n: n for n in [1,2,3,4,5]} # OK + +def foo(): + def f(): + return {n: 1 for c in [1,2,3,4,5] for n in [1,2,3]} # OK + + f() \ No newline at end of file diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 6e4705e08c9d5..2657c73dfcc75 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -1431,7 +1431,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::UnnecessaryDictComprehensionForIterable) { ruff::rules::unnecessary_dict_comprehension_for_iterable( - checker, expr, value, generators, + checker, expr, key, value, generators, ); } diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs index 16f5d75e16a9d..0fd863b848198 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs @@ -67,19 +67,31 @@ impl AlwaysFixableViolation for UnnecessaryDictComprehensionForIterable { pub(crate) fn unnecessary_dict_comprehension_for_iterable( checker: &mut Checker, expr: &Expr, + key: &Expr, value: &Expr, generators: &[Comprehension], ) { let [generator] = generators else { return; }; - if !generator.ifs.is_empty() && generator.is_async { + + // Don't suggest `dict.fromkeys` for async generator expressions, because `dict.fromkeys` is not async. + // Don't suggest `dict.fromkeys` for nested generator expressions, because `dict.fromkeys` might be error-prone option at least for fixing. + // Don't suggest `dict.fromkeys` for generator expressions with `if` clauses, because `dict.fromkeys` might not be valid option. + if !generator.ifs.is_empty() && generator.is_async && generators.len() > 1 { return; } let Expr::Name(_) = &generator.target else { return; }; + // Don't suggest `dict.fromkeys` if key and value are binded to the same name. + if let (Expr::Name(key_name), Expr::Name(value_name)) = (key, value) { + if key_name.id == value_name.id { + return; + } + } + if !has_valid_expression_type(value) { return; } diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap index 3a362c0977d0e..73df611d3b2a1 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap @@ -181,26 +181,44 @@ RUF025.py:40:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite 40 |+ dict.fromkeys([1,2,3], a) # RUF025 41 41 | 42 42 | def foo(): -43 43 | def f(): +43 43 | {n: False for n in {1: "a", 2: "b"}} # RUF025 -RUF025.py:52:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) +RUF025.py:43:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) | -51 | def foo(): -52 | {n: 1 for n in [1,2,3,4,5] if n < 3} # OK +42 | def foo(): +43 | {n: False for n in {1: "a", 2: "b"}} # RUF025 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 + | + = help: Rewrite using `dict.fromkeys(iterable)`) + +ℹ Safe fix +40 40 | {n: a for n in [1,2,3]} # RUF025 +41 41 | +42 42 | def foo(): +43 |- {n: False for n in {1: "a", 2: "b"}} # RUF025 + 43 |+ dict.fromkeys({1: "a", 2: "b"}, False) # RUF025 +44 44 | +45 45 | +46 46 | # Non-violation cases: RUF025 + +RUF025.py:49:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) + | +48 | def foo(): +49 | {n: 1 for n in [1,2,3,4,5] if n < 3} # OK | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 -53 | -54 | def foo(): +50 | +51 | def foo(): | = help: Rewrite using `dict.fromkeys(iterable)`) ℹ Safe fix -49 49 | # Non-violation cases: RUF025 +46 46 | # Non-violation cases: RUF025 +47 47 | +48 48 | def foo(): +49 |- {n: 1 for n in [1,2,3,4,5] if n < 3} # OK + 49 |+ dict.fromkeys([1,2,3,4,5], 1) # OK 50 50 | 51 51 | def foo(): -52 |- {n: 1 for n in [1,2,3,4,5] if n < 3} # OK - 52 |+ dict.fromkeys([1,2,3,4,5], 1) # OK -53 53 | -54 54 | def foo(): -55 55 | {n: 1 for c in [1,2,3,4,5] for n in [1,2,3] if c < 3} # OK +52 52 | {n: 1 for c in [1,2,3,4,5] for n in [1,2,3] if c < 3} # OK From 99a57f4a3e162946fb20b80d349a6983eec5b82f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikko=20Lepp=C3=A4nen?= Date: Tue, 23 Jan 2024 16:39:04 +0200 Subject: [PATCH 05/10] code review fixes --- ...cessary_dict_comprehension_for_iterable.rs | 120 ++++++------------ ..._rules__ruff__tests__RUF025_RUF025.py.snap | 16 +-- 2 files changed, 45 insertions(+), 91 deletions(-) diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs index 0fd863b848198..831f5c9f87840 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs @@ -1,16 +1,10 @@ -use anyhow::{bail, Result}; -use libcst_native::{Arg, Call, Expression, Name, ParenthesizableWhitespace}; +use ast::ExprName; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::{self as ast, Comprehension, Expr}; -use ruff_python_codegen::Stylist; -use ruff_source_file::Locator; -use ruff_text_size::Ranged; +use ruff_python_ast::{self as ast, Arguments, Comprehension, Expr, ExprCall, ExprContext}; +use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; -use crate::cst::matchers::match_expression; -use crate::fix::codemods::CodegenStylist; -use crate::fix::edits::pad; /// ## What it does /// Checks for unnecessary `dict` comprehension when creating a new dictionary from iterable. @@ -40,7 +34,6 @@ pub struct UnnecessaryDictComprehensionForIterable { impl AlwaysFixableViolation for UnnecessaryDictComprehensionForIterable { #[derive_message_formats] - #[allow(clippy::match_bool)] fn message(&self) -> String { if self.is_value_none_literal { format!( @@ -53,7 +46,6 @@ impl AlwaysFixableViolation for UnnecessaryDictComprehensionForIterable { } } - #[allow(clippy::match_bool)] fn fix_title(&self) -> String { if self.is_value_none_literal { format!("Rewrite using `dict.fromkeys(iterable, value)`)",) @@ -75,13 +67,15 @@ pub(crate) fn unnecessary_dict_comprehension_for_iterable( return; }; - // Don't suggest `dict.fromkeys` for async generator expressions, because `dict.fromkeys` is not async. - // Don't suggest `dict.fromkeys` for nested generator expressions, because `dict.fromkeys` might be error-prone option at least for fixing. - // Don't suggest `dict.fromkeys` for generator expressions with `if` clauses, because `dict.fromkeys` might not be valid option. - if !generator.ifs.is_empty() && generator.is_async && generators.len() > 1 { + // Don't suggest `dict.fromkeys` for: + // - async generator expressions, because `dict.fromkeys` is not async. + // - nested generator expressions, because `dict.fromkeys` might be error-prone option at least for fixing. + // - generator expressions with `if` clauses, because `dict.fromkeys` might not be valid option. + if !generator.ifs.is_empty() && generator.is_async { return; } - let Expr::Name(_) = &generator.target else { + + if !generator.target.is_name_expr() { return; }; @@ -102,10 +96,13 @@ pub(crate) fn unnecessary_dict_comprehension_for_iterable( }, expr.range(), ); - diagnostic.try_set_fix(|| { - fix_unnecessary_dict_comprehension(expr, checker.locator(), checker.stylist()) - .map(Fix::safe_edit) - }); + + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + checker + .generator() + .expr(&fix_unnecessary_dict_comprehension(value, generator)), + expr.range(), + ))); checker.diagnostics.push(diagnostic); } @@ -126,67 +123,24 @@ fn has_valid_expression_type(node: &ast::Expr) -> bool { /// Generate a [`Fix`] to replace `dict` comprehension with `dict.fromkeys`. /// (RUF025) Convert `{n: None for n in [1,2,3]}` to `dict.fromkeys([1,2,3])` or /// `{n: 1 for n in [1,2,3]}` to `dict.fromkeys([1,2,3], 1)`. -fn fix_unnecessary_dict_comprehension( - expr: &Expr, - locator: &Locator, - stylist: &Stylist, -) -> Result { - let module_text = locator.slice(expr); - let mut tree = match_expression(module_text)?; - - match &tree { - Expression::DictComp(inner) => { - let args = match &*inner.value { - Expression::Name(value) if value.value == "None" => { - vec![Arg { - value: inner.for_in.iter.clone(), - keyword: None, - equal: None, - comma: None, - star: "", - whitespace_after_star: ParenthesizableWhitespace::default(), - whitespace_after_arg: ParenthesizableWhitespace::default(), - }] - } - _ => vec![ - Arg { - value: inner.for_in.iter.clone(), - keyword: None, - equal: None, - comma: None, - star: "", - whitespace_after_star: ParenthesizableWhitespace::default(), - whitespace_after_arg: ParenthesizableWhitespace::default(), - }, - Arg { - value: *inner.value.clone(), - keyword: None, - equal: None, - comma: None, - star: "", - whitespace_after_star: ParenthesizableWhitespace::default(), - whitespace_after_arg: ParenthesizableWhitespace::default(), - }, - ], - }; - tree = Expression::Call(Box::new(Call { - func: Box::new(Expression::Name(Box::new(Name { - value: "dict.fromkeys", - lpar: vec![], - rpar: vec![], - }))), - args, - lpar: vec![], - rpar: vec![], - whitespace_after_func: ParenthesizableWhitespace::default(), - whitespace_before_args: ParenthesizableWhitespace::default(), - })); - } - _ => bail!("Expected a dict comprehension"), - } - - Ok(Edit::range_replacement( - pad(tree.codegen_stylist(stylist), expr.range(), locator), - expr.range(), - )) +fn fix_unnecessary_dict_comprehension(value: &Expr, generator: &Comprehension) -> Expr { + let iterable = generator.iter.clone(); + let args = Arguments { + args: if value.is_none_literal_expr() { + vec![iterable] + } else { + vec![iterable, value.clone()] + }, + keywords: vec![], + range: TextRange::default(), + }; + Expr::Call(ExprCall { + func: Box::new(Expr::Name(ExprName { + id: "dict.fromkeys".into(), + ctx: ExprContext::Load, + range: TextRange::default(), + })), + arguments: args, + range: TextRange::default(), + }) } diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap index 73df611d3b2a1..8f166e9097164 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap @@ -36,7 +36,7 @@ RUF025.py:8:23: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite 6 6 | 7 7 | def foo(): 8 |- for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF025 - 8 |+ for key, value in dict.fromkeys([1,2,3], 1).items(): # RUF025 + 8 |+ for key, value in dict.fromkeys([1, 2, 3], 1).items(): # RUF025 9 9 | pass 10 10 | 11 11 | def foo(): @@ -56,7 +56,7 @@ RUF025.py:12:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite 10 10 | 11 11 | def foo(): 12 |- {n: 1.1 for n in [1,2,3]} # RUF025 - 12 |+ dict.fromkeys([1,2,3], 1.1) # RUF025 + 12 |+ dict.fromkeys([1, 2, 3], 1.1) # RUF025 13 13 | 14 14 | def foo(): 15 15 | {n: complex(3, 5) for n in [1,2,3]} # RUF025 @@ -97,7 +97,7 @@ RUF025.py:23:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite 21 21 | 22 22 | def foo(): 23 |- {n: True for n in [1,2,2]} # RUF025 - 23 |+ dict.fromkeys([1,2,2], True) # RUF025 + 23 |+ dict.fromkeys([1, 2, 2], True) # RUF025 24 24 | 25 25 | def foo(): 26 26 | {n: b'hello' for n in (1,2,2)} # RUF025 @@ -117,7 +117,7 @@ RUF025.py:26:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite 24 24 | 25 25 | def foo(): 26 |- {n: b'hello' for n in (1,2,2)} # RUF025 - 26 |+ dict.fromkeys((1,2,2), b'hello') # RUF025 + 26 |+ dict.fromkeys((1, 2, 2), b"hello") # RUF025 27 27 | 28 28 | def foo(): 29 29 | {n: ... for n in [1,2,3]} # RUF025 @@ -137,7 +137,7 @@ RUF025.py:29:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite 27 27 | 28 28 | def foo(): 29 |- {n: ... for n in [1,2,3]} # RUF025 - 29 |+ dict.fromkeys([1,2,3], ...) # RUF025 + 29 |+ dict.fromkeys([1, 2, 3], ...) # RUF025 30 30 | 31 31 | def foo(): 32 32 | values = ["a", "b", "c"] @@ -158,7 +158,7 @@ RUF025.py:33:6: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite 31 31 | def foo(): 32 32 | values = ["a", "b", "c"] 33 |- [{n: values for n in [1,2,3]}] # RUF025 - 33 |+ [dict.fromkeys([1,2,3], values)] # RUF025 + 33 |+ [dict.fromkeys([1, 2, 3], values)] # RUF025 34 34 | 35 35 | def foo(): 36 36 | def f(): @@ -178,7 +178,7 @@ RUF025.py:40:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite 38 38 | 39 39 | a = f() 40 |- {n: a for n in [1,2,3]} # RUF025 - 40 |+ dict.fromkeys([1,2,3], a) # RUF025 + 40 |+ dict.fromkeys([1, 2, 3], a) # RUF025 41 41 | 42 42 | def foo(): 43 43 | {n: False for n in {1: "a", 2: "b"}} # RUF025 @@ -216,7 +216,7 @@ RUF025.py:49:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite 47 47 | 48 48 | def foo(): 49 |- {n: 1 for n in [1,2,3,4,5] if n < 3} # OK - 49 |+ dict.fromkeys([1,2,3,4,5], 1) # OK + 49 |+ dict.fromkeys([1, 2, 3, 4, 5], 1) # OK 50 50 | 51 51 | def foo(): 52 52 | {n: 1 for c in [1,2,3,4,5] for n in [1,2,3] if c < 3} # OK From 0b4584de7f636feada3732f9d2a9654d3579bea5 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 23 Jan 2024 20:48:27 -0500 Subject: [PATCH 06/10] Tweaks --- .../resources/test/fixtures/ruff/RUF025.py | 57 +++--- .../src/checkers/ast/analyze/expression.rs | 4 +- ...cessary_dict_comprehension_for_iterable.rs | 96 ++++----- ..._rules__ruff__tests__RUF025_RUF025.py.snap | 189 ++++++++---------- 4 files changed, 154 insertions(+), 192 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py index 9d4680fd8dfa6..e5d325b60cc08 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py @@ -1,66 +1,69 @@ # Violation cases: RUF025 -def foo(): +def func(): numbers = [1,2,3] {n: None for n in numbers} # RUF025 -def foo(): +def func(): for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF025 pass -def foo(): +def func(): {n: 1.1 for n in [1,2,3]} # RUF025 -def foo(): +def func(): {n: complex(3, 5) for n in [1,2,3]} # RUF025 -def foo(): +def func(): def f(data): return data f({c: "a" for c in "12345"}) # RUF025 -def foo(): +def func(): {n: True for n in [1,2,2]} # RUF025 -def foo(): +def func(): {n: b'hello' for n in (1,2,2)} # RUF025 -def foo(): +def func(): {n: ... for n in [1,2,3]} # RUF025 -def foo(): - values = ["a", "b", "c"] - [{n: values for n in [1,2,3]}] # RUF025 - -def foo(): - def f(): - return 1 - - a = f() - {n: a for n in [1,2,3]} # RUF025 - -def foo(): +def func(): {n: False for n in {1: "a", 2: "b"}} # RUF025 +def func(): + {(a, b): 1 for (a, b) in [(1,2), (3, 4)]} # RUF025 + # Non-violation cases: RUF025 -def foo(): +def func(): {n: 1 for n in [1,2,3,4,5] if n < 3} # OK -def foo(): +def func(): {n: 1 for c in [1,2,3,4,5] for n in [1,2,3] if c < 3} # OK -def foo(): +def func(): def f(): pass {n: f() for n in [1,2,3]} # OK -def foo(): +def func(): {n: n for n in [1,2,3,4,5]} # OK -def foo(): +def func(): def f(): return {n: 1 for c in [1,2,3,4,5] for n in [1,2,3]} # OK - - f() \ No newline at end of file + + f() + +def func(): + def f(): + return 1 + + a = f() + {n: a for n in [1, 2, 3]} # RUF025 + +def func(): + values = ["a", "b", "c"] + [{n: values for n in [1,2,3]}] # RUF025 diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 2657c73dfcc75..da840cb3071d6 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -1430,9 +1430,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { } if checker.enabled(Rule::UnnecessaryDictComprehensionForIterable) { - ruff::rules::unnecessary_dict_comprehension_for_iterable( - checker, expr, key, value, generators, - ); + ruff::rules::unnecessary_dict_comprehension_for_iterable(checker, dict_comp); } if checker.enabled(Rule::FunctionUsesLoopVariable) { diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs index 831f5c9f87840..fa7ab0d0df3b0 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs @@ -1,20 +1,23 @@ use ast::ExprName; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::comparable::ComparableExpr; +use ruff_python_ast::helpers::is_constant; use ruff_python_ast::{self as ast, Arguments, Comprehension, Expr, ExprCall, ExprContext}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; /// ## What it does -/// Checks for unnecessary `dict` comprehension when creating a new dictionary from iterable. +/// Checks for unnecessary `dict` comprehension when creating a dictionary from +/// an iterable. /// /// ## Why is this bad? -/// It's unnecessary to use a `dict` comprehension to build a dictionary from an iterable when the value is `static`. -/// Use `dict.fromkeys(iterable)` instead of `{value: None for value in iterable}`. -/// -/// `dict.fromkeys(iterable)` is more readable and faster than `{value: None for value in iterable}`. +/// It's unnecessary to use a `dict` comprehension to build a dictionary from +/// an iterable when the value is static. /// +/// Prefer `dict.fromkeys(iterable)` over `{value: None for value in iterable}`, +/// as `dict.fromkeys` is more readable and efficient. /// /// ## Examples /// ```python @@ -32,25 +35,19 @@ pub struct UnnecessaryDictComprehensionForIterable { is_value_none_literal: bool, } -impl AlwaysFixableViolation for UnnecessaryDictComprehensionForIterable { +impl Violation for UnnecessaryDictComprehensionForIterable { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + #[derive_message_formats] fn message(&self) -> String { - if self.is_value_none_literal { - format!( - "Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable)`)" - ) - } else { - format!( - "Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`)" - ) - } + format!("Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead") } - fn fix_title(&self) -> String { + fn fix_title(&self) -> Option { if self.is_value_none_literal { - format!("Rewrite using `dict.fromkeys(iterable, value)`)",) + Some(format!("Replace with `dict.fromkeys(iterable, value)`)")) } else { - format!("Rewrite using `dict.fromkeys(iterable)`)") + Some(format!("Replace with `dict.fromkeys(iterable)`)")) } } } @@ -58,12 +55,9 @@ impl AlwaysFixableViolation for UnnecessaryDictComprehensionForIterable { /// RUF025 pub(crate) fn unnecessary_dict_comprehension_for_iterable( checker: &mut Checker, - expr: &Expr, - key: &Expr, - value: &Expr, - generators: &[Comprehension], + dict_comp: &ast::ExprDictComp, ) { - let [generator] = generators else { + let [generator] = dict_comp.generators.as_slice() else { return; }; @@ -75,54 +69,42 @@ pub(crate) fn unnecessary_dict_comprehension_for_iterable( return; } - if !generator.target.is_name_expr() { + // Don't suggest `dict.keys` if the target is not the same as the key. + if ComparableExpr::from(&generator.target) != ComparableExpr::from(dict_comp.key.as_ref()) { return; - }; - - // Don't suggest `dict.fromkeys` if key and value are binded to the same name. - if let (Expr::Name(key_name), Expr::Name(value_name)) = (key, value) { - if key_name.id == value_name.id { - return; - } } - if !has_valid_expression_type(value) { + if !is_constant(dict_comp.value.as_ref()) { return; } let mut diagnostic = Diagnostic::new( UnnecessaryDictComprehensionForIterable { - is_value_none_literal: value.is_none_literal_expr(), + is_value_none_literal: dict_comp.value.is_none_literal_expr(), }, - expr.range(), + dict_comp.range(), ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - checker - .generator() - .expr(&fix_unnecessary_dict_comprehension(value, generator)), - expr.range(), - ))); - checker.diagnostics.push(diagnostic); -} + if checker.semantic().is_builtin("dict") { + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + checker + .generator() + .expr(&fix_unnecessary_dict_comprehension( + dict_comp.value.as_ref(), + generator, + )), + dict_comp.range(), + ))); + } -// only accept `None`, `Ellipsis`, `True`, `False`, `Number`, `String`, `Bytes`, `Name` as value -fn has_valid_expression_type(node: &ast::Expr) -> bool { - matches!( - node, - ast::Expr::StringLiteral(_) - | ast::Expr::BytesLiteral(_) - | ast::Expr::NumberLiteral(_) - | ast::Expr::BooleanLiteral(_) - | ast::Expr::NoneLiteral(_) - | ast::Expr::EllipsisLiteral(_) - | ast::Expr::Name(_) - ) + checker.diagnostics.push(diagnostic); } /// Generate a [`Fix`] to replace `dict` comprehension with `dict.fromkeys`. -/// (RUF025) Convert `{n: None for n in [1,2,3]}` to `dict.fromkeys([1,2,3])` or -/// `{n: 1 for n in [1,2,3]}` to `dict.fromkeys([1,2,3], 1)`. +/// +/// For example: +/// - Given `{n: None for n in [1,2,3]}`, generate `dict.fromkeys([1,2,3])`. +/// - Given `{n: 1 for n in [1,2,3]}`, generate `dict.fromkeys([1,2,3], 1)`. fn fix_unnecessary_dict_comprehension(value: &Expr, generator: &Comprehension) -> Expr { let iterable = generator.iter.clone(); let args = Arguments { diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap index 8f166e9097164..d2f1343cc2144 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap @@ -1,224 +1,203 @@ --- source: crates/ruff_linter/src/rules/ruff/mod.rs --- -RUF025.py:5:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable)`) +RUF025.py:5:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | -3 | def foo(): +3 | def func(): 4 | numbers = [1,2,3] 5 | {n: None for n in numbers} # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 6 | -7 | def foo(): +7 | def func(): | - = help: Rewrite using `dict.fromkeys(iterable, value)`) + = help: Replace with `dict.fromkeys(iterable, value)`) ℹ Safe fix 2 2 | -3 3 | def foo(): +3 3 | def func(): 4 4 | numbers = [1,2,3] 5 |- {n: None for n in numbers} # RUF025 5 |+ dict.fromkeys(numbers) # RUF025 6 6 | -7 7 | def foo(): +7 7 | def func(): 8 8 | for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF025 -RUF025.py:8:23: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) +RUF025.py:8:23: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | -7 | def foo(): +7 | def func(): 8 | for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^ RUF025 9 | pass | - = help: Rewrite using `dict.fromkeys(iterable)`) + = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix 5 5 | {n: None for n in numbers} # RUF025 6 6 | -7 7 | def foo(): +7 7 | def func(): 8 |- for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF025 8 |+ for key, value in dict.fromkeys([1, 2, 3], 1).items(): # RUF025 9 9 | pass 10 10 | -11 11 | def foo(): +11 11 | def func(): -RUF025.py:12:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) +RUF025.py:12:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | -11 | def foo(): +11 | def func(): 12 | {n: 1.1 for n in [1,2,3]} # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 13 | -14 | def foo(): +14 | def func(): | - = help: Rewrite using `dict.fromkeys(iterable)`) + = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix 9 9 | pass 10 10 | -11 11 | def foo(): +11 11 | def func(): 12 |- {n: 1.1 for n in [1,2,3]} # RUF025 12 |+ dict.fromkeys([1, 2, 3], 1.1) # RUF025 13 13 | -14 14 | def foo(): +14 14 | def func(): 15 15 | {n: complex(3, 5) for n in [1,2,3]} # RUF025 -RUF025.py:20:7: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) +RUF025.py:20:7: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | 18 | def f(data): 19 | return data 20 | f({c: "a" for c in "12345"}) # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 21 | -22 | def foo(): +22 | def func(): | - = help: Rewrite using `dict.fromkeys(iterable)`) + = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix -17 17 | def foo(): +17 17 | def func(): 18 18 | def f(data): 19 19 | return data 20 |- f({c: "a" for c in "12345"}) # RUF025 20 |+ f(dict.fromkeys("12345", "a")) # RUF025 21 21 | -22 22 | def foo(): +22 22 | def func(): 23 23 | {n: True for n in [1,2,2]} # RUF025 -RUF025.py:23:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) +RUF025.py:23:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | -22 | def foo(): +22 | def func(): 23 | {n: True for n in [1,2,2]} # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 24 | -25 | def foo(): +25 | def func(): | - = help: Rewrite using `dict.fromkeys(iterable)`) + = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix 20 20 | f({c: "a" for c in "12345"}) # RUF025 21 21 | -22 22 | def foo(): +22 22 | def func(): 23 |- {n: True for n in [1,2,2]} # RUF025 23 |+ dict.fromkeys([1, 2, 2], True) # RUF025 24 24 | -25 25 | def foo(): +25 25 | def func(): 26 26 | {n: b'hello' for n in (1,2,2)} # RUF025 -RUF025.py:26:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) +RUF025.py:26:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | -25 | def foo(): +25 | def func(): 26 | {n: b'hello' for n in (1,2,2)} # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 27 | -28 | def foo(): +28 | def func(): | - = help: Rewrite using `dict.fromkeys(iterable)`) + = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix 23 23 | {n: True for n in [1,2,2]} # RUF025 24 24 | -25 25 | def foo(): +25 25 | def func(): 26 |- {n: b'hello' for n in (1,2,2)} # RUF025 26 |+ dict.fromkeys((1, 2, 2), b"hello") # RUF025 27 27 | -28 28 | def foo(): +28 28 | def func(): 29 29 | {n: ... for n in [1,2,3]} # RUF025 -RUF025.py:29:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) +RUF025.py:29:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | -28 | def foo(): +28 | def func(): 29 | {n: ... for n in [1,2,3]} # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 30 | -31 | def foo(): +31 | def func(): | - = help: Rewrite using `dict.fromkeys(iterable)`) + = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix 26 26 | {n: b'hello' for n in (1,2,2)} # RUF025 27 27 | -28 28 | def foo(): +28 28 | def func(): 29 |- {n: ... for n in [1,2,3]} # RUF025 29 |+ dict.fromkeys([1, 2, 3], ...) # RUF025 30 30 | -31 31 | def foo(): -32 32 | values = ["a", "b", "c"] +31 31 | def func(): +32 32 | {n: False for n in {1: "a", 2: "b"}} # RUF025 -RUF025.py:33:6: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) +RUF025.py:32:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | -31 | def foo(): -32 | values = ["a", "b", "c"] -33 | [{n: values for n in [1,2,3]}] # RUF025 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 -34 | -35 | def foo(): +31 | def func(): +32 | {n: False for n in {1: "a", 2: "b"}} # RUF025 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 +33 | +34 | def func(): | - = help: Rewrite using `dict.fromkeys(iterable)`) + = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix +29 29 | {n: ... for n in [1,2,3]} # RUF025 30 30 | -31 31 | def foo(): -32 32 | values = ["a", "b", "c"] -33 |- [{n: values for n in [1,2,3]}] # RUF025 - 33 |+ [dict.fromkeys([1, 2, 3], values)] # RUF025 -34 34 | -35 35 | def foo(): -36 36 | def f(): +31 31 | def func(): +32 |- {n: False for n in {1: "a", 2: "b"}} # RUF025 + 32 |+ dict.fromkeys({1: "a", 2: "b"}, False) # RUF025 +33 33 | +34 34 | def func(): +35 35 | {(a, b): 1 for (a, b) in [(1,2), (3, 4)]} # RUF025 -RUF025.py:40:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) - | -39 | a = f() -40 | {n: a for n in [1,2,3]} # RUF025 - | ^^^^^^^^^^^^^^^^^^^^^^^ RUF025 -41 | -42 | def foo(): +RUF025.py:35:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | - = help: Rewrite using `dict.fromkeys(iterable)`) - -ℹ Safe fix -37 37 | return 1 -38 38 | -39 39 | a = f() -40 |- {n: a for n in [1,2,3]} # RUF025 - 40 |+ dict.fromkeys([1, 2, 3], a) # RUF025 -41 41 | -42 42 | def foo(): -43 43 | {n: False for n in {1: "a", 2: "b"}} # RUF025 - -RUF025.py:43:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) - | -42 | def foo(): -43 | {n: False for n in {1: "a", 2: "b"}} # RUF025 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 +34 | def func(): +35 | {(a, b): 1 for (a, b) in [(1,2), (3, 4)]} # RUF025 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 | - = help: Rewrite using `dict.fromkeys(iterable)`) + = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix -40 40 | {n: a for n in [1,2,3]} # RUF025 -41 41 | -42 42 | def foo(): -43 |- {n: False for n in {1: "a", 2: "b"}} # RUF025 - 43 |+ dict.fromkeys({1: "a", 2: "b"}, False) # RUF025 -44 44 | -45 45 | -46 46 | # Non-violation cases: RUF025 - -RUF025.py:49:5: RUF025 [*] Unnecessary dict comprehension for iterable (rewrite using `dict.fromkeys(iterable, value)`) - | -48 | def foo(): -49 | {n: 1 for n in [1,2,3,4,5] if n < 3} # OK +32 32 | {n: False for n in {1: "a", 2: "b"}} # RUF025 +33 33 | +34 34 | def func(): +35 |- {(a, b): 1 for (a, b) in [(1,2), (3, 4)]} # RUF025 + 35 |+ dict.fromkeys([(1, 2), (3, 4)], 1) # RUF025 +36 36 | +37 37 | +38 38 | # Non-violation cases: RUF025 + +RUF025.py:41:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead + | +40 | def func(): +41 | {n: 1 for n in [1,2,3,4,5] if n < 3} # OK | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 -50 | -51 | def foo(): +42 | +43 | def func(): | - = help: Rewrite using `dict.fromkeys(iterable)`) + = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix -46 46 | # Non-violation cases: RUF025 -47 47 | -48 48 | def foo(): -49 |- {n: 1 for n in [1,2,3,4,5] if n < 3} # OK - 49 |+ dict.fromkeys([1, 2, 3, 4, 5], 1) # OK -50 50 | -51 51 | def foo(): -52 52 | {n: 1 for c in [1,2,3,4,5] for n in [1,2,3] if c < 3} # OK +38 38 | # Non-violation cases: RUF025 +39 39 | +40 40 | def func(): +41 |- {n: 1 for n in [1,2,3,4,5] if n < 3} # OK + 41 |+ dict.fromkeys([1, 2, 3, 4, 5], 1) # OK +42 42 | +43 43 | def func(): +44 44 | {n: 1 for c in [1,2,3,4,5] for n in [1,2,3] if c < 3} # OK From 5de675027362d279f653ed3b9f9d7e37e51a9c3b Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 23 Jan 2024 20:49:40 -0500 Subject: [PATCH 07/10] format fixture --- .../resources/test/fixtures/ruff/RUF025.py | 55 ++-- ..._rules__ruff__tests__RUF025_RUF025.py.snap | 270 ++++++++---------- 2 files changed, 164 insertions(+), 161 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py index e5d325b60cc08..05db2afcf1621 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py @@ -1,69 +1,88 @@ # Violation cases: RUF025 + def func(): - numbers = [1,2,3] - {n: None for n in numbers} # RUF025 + numbers = [1, 2, 3] + {n: None for n in numbers} # RUF025 + def func(): - for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF025 + for key, value in {n: 1 for n in [1, 2, 3]}.items(): # RUF025 pass + def func(): - {n: 1.1 for n in [1,2,3]} # RUF025 + {n: 1.1 for n in [1, 2, 3]} # RUF025 + def func(): - {n: complex(3, 5) for n in [1,2,3]} # RUF025 + {n: complex(3, 5) for n in [1, 2, 3]} # RUF025 + def func(): def f(data): return data - f({c: "a" for c in "12345"}) # RUF025 + + f({c: "a" for c in "12345"}) # RUF025 + def func(): - {n: True for n in [1,2,2]} # RUF025 + {n: True for n in [1, 2, 2]} # RUF025 + def func(): - {n: b'hello' for n in (1,2,2)} # RUF025 + {n: b"hello" for n in (1, 2, 2)} # RUF025 + def func(): - {n: ... for n in [1,2,3]} # RUF025 + {n: ... for n in [1, 2, 3]} # RUF025 + def func(): - {n: False for n in {1: "a", 2: "b"}} # RUF025 + {n: False for n in {1: "a", 2: "b"}} # RUF025 + def func(): - {(a, b): 1 for (a, b) in [(1,2), (3, 4)]} # RUF025 + {(a, b): 1 for (a, b) in [(1, 2), (3, 4)]} # RUF025 # Non-violation cases: RUF025 + def func(): - {n: 1 for n in [1,2,3,4,5] if n < 3} # OK + {n: 1 for n in [1, 2, 3, 4, 5] if n < 3} # OK + def func(): - {n: 1 for c in [1,2,3,4,5] for n in [1,2,3] if c < 3} # OK + {n: 1 for c in [1, 2, 3, 4, 5] for n in [1, 2, 3] if c < 3} # OK + def func(): def f(): pass - {n: f() for n in [1,2,3]} # OK + + {n: f() for n in [1, 2, 3]} # OK + def func(): - {n: n for n in [1,2,3,4,5]} # OK + {n: n for n in [1, 2, 3, 4, 5]} # OK + def func(): def f(): - return {n: 1 for c in [1,2,3,4,5] for n in [1,2,3]} # OK + return {n: 1 for c in [1, 2, 3, 4, 5] for n in [1, 2, 3]} # OK f() + def func(): def f(): return 1 a = f() - {n: a for n in [1, 2, 3]} # RUF025 + {n: a for n in [1, 2, 3]} # OK + def func(): values = ["a", "b", "c"] - [{n: values for n in [1,2,3]}] # RUF025 + [{n: values for n in [1, 2, 3]}] # OK diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap index d2f1343cc2144..cbf592ee0ced3 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap @@ -1,203 +1,187 @@ --- source: crates/ruff_linter/src/rules/ruff/mod.rs --- -RUF025.py:5:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead +RUF025.py:6:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | -3 | def func(): -4 | numbers = [1,2,3] -5 | {n: None for n in numbers} # RUF025 +4 | def func(): +5 | numbers = [1, 2, 3] +6 | {n: None for n in numbers} # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 -6 | -7 | def func(): | = help: Replace with `dict.fromkeys(iterable, value)`) ℹ Safe fix -2 2 | -3 3 | def func(): -4 4 | numbers = [1,2,3] -5 |- {n: None for n in numbers} # RUF025 - 5 |+ dict.fromkeys(numbers) # RUF025 -6 6 | -7 7 | def func(): -8 8 | for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF025 - -RUF025.py:8:23: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead - | -7 | def func(): -8 | for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF025 - | ^^^^^^^^^^^^^^^^^^^^^^^ RUF025 -9 | pass - | - = help: Replace with `dict.fromkeys(iterable)`) +3 3 | +4 4 | def func(): +5 5 | numbers = [1, 2, 3] +6 |- {n: None for n in numbers} # RUF025 + 6 |+ dict.fromkeys(numbers) # RUF025 +7 7 | +8 8 | +9 9 | def func(): + +RUF025.py:10:23: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead + | + 9 | def func(): +10 | for key, value in {n: 1 for n in [1, 2, 3]}.items(): # RUF025 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 +11 | pass + | + = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix -5 5 | {n: None for n in numbers} # RUF025 -6 6 | -7 7 | def func(): -8 |- for key, value in {n: 1 for n in [1,2,3]}.items(): # RUF025 - 8 |+ for key, value in dict.fromkeys([1, 2, 3], 1).items(): # RUF025 -9 9 | pass -10 10 | -11 11 | def func(): - -RUF025.py:12:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead - | -11 | def func(): -12 | {n: 1.1 for n in [1,2,3]} # RUF025 - | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 -13 | +7 7 | +8 8 | +9 9 | def func(): +10 |- for key, value in {n: 1 for n in [1, 2, 3]}.items(): # RUF025 + 10 |+ for key, value in dict.fromkeys([1, 2, 3], 1).items(): # RUF025 +11 11 | pass +12 12 | +13 13 | + +RUF025.py:15:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead + | 14 | def func(): +15 | {n: 1.1 for n in [1, 2, 3]} # RUF025 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 | = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix -9 9 | pass -10 10 | -11 11 | def func(): -12 |- {n: 1.1 for n in [1,2,3]} # RUF025 - 12 |+ dict.fromkeys([1, 2, 3], 1.1) # RUF025 +12 12 | 13 13 | 14 14 | def func(): -15 15 | {n: complex(3, 5) for n in [1,2,3]} # RUF025 +15 |- {n: 1.1 for n in [1, 2, 3]} # RUF025 + 15 |+ dict.fromkeys([1, 2, 3], 1.1) # RUF025 +16 16 | +17 17 | +18 18 | def func(): -RUF025.py:20:7: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead +RUF025.py:26:7: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | -18 | def f(data): -19 | return data -20 | f({c: "a" for c in "12345"}) # RUF025 +24 | return data +25 | +26 | f({c: "a" for c in "12345"}) # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 -21 | -22 | def func(): | = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix -17 17 | def func(): -18 18 | def f(data): -19 19 | return data -20 |- f({c: "a" for c in "12345"}) # RUF025 - 20 |+ f(dict.fromkeys("12345", "a")) # RUF025 -21 21 | -22 22 | def func(): -23 23 | {n: True for n in [1,2,2]} # RUF025 - -RUF025.py:23:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead - | -22 | def func(): -23 | {n: True for n in [1,2,2]} # RUF025 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 -24 | -25 | def func(): +23 23 | def f(data): +24 24 | return data +25 25 | +26 |- f({c: "a" for c in "12345"}) # RUF025 + 26 |+ f(dict.fromkeys("12345", "a")) # RUF025 +27 27 | +28 28 | +29 29 | def func(): + +RUF025.py:30:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead + | +29 | def func(): +30 | {n: True for n in [1, 2, 2]} # RUF025 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 | = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix -20 20 | f({c: "a" for c in "12345"}) # RUF025 -21 21 | -22 22 | def func(): -23 |- {n: True for n in [1,2,2]} # RUF025 - 23 |+ dict.fromkeys([1, 2, 2], True) # RUF025 -24 24 | -25 25 | def func(): -26 26 | {n: b'hello' for n in (1,2,2)} # RUF025 - -RUF025.py:26:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead - | -25 | def func(): -26 | {n: b'hello' for n in (1,2,2)} # RUF025 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 -27 | -28 | def func(): +27 27 | +28 28 | +29 29 | def func(): +30 |- {n: True for n in [1, 2, 2]} # RUF025 + 30 |+ dict.fromkeys([1, 2, 2], True) # RUF025 +31 31 | +32 32 | +33 33 | def func(): + +RUF025.py:34:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead + | +33 | def func(): +34 | {n: b"hello" for n in (1, 2, 2)} # RUF025 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 | = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix -23 23 | {n: True for n in [1,2,2]} # RUF025 -24 24 | -25 25 | def func(): -26 |- {n: b'hello' for n in (1,2,2)} # RUF025 - 26 |+ dict.fromkeys((1, 2, 2), b"hello") # RUF025 -27 27 | -28 28 | def func(): -29 29 | {n: ... for n in [1,2,3]} # RUF025 +31 31 | +32 32 | +33 33 | def func(): +34 |- {n: b"hello" for n in (1, 2, 2)} # RUF025 + 34 |+ dict.fromkeys((1, 2, 2), b"hello") # RUF025 +35 35 | +36 36 | +37 37 | def func(): -RUF025.py:29:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead +RUF025.py:38:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | -28 | def func(): -29 | {n: ... for n in [1,2,3]} # RUF025 - | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 -30 | -31 | def func(): +37 | def func(): +38 | {n: ... for n in [1, 2, 3]} # RUF025 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 | = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix -26 26 | {n: b'hello' for n in (1,2,2)} # RUF025 -27 27 | -28 28 | def func(): -29 |- {n: ... for n in [1,2,3]} # RUF025 - 29 |+ dict.fromkeys([1, 2, 3], ...) # RUF025 -30 30 | -31 31 | def func(): -32 32 | {n: False for n in {1: "a", 2: "b"}} # RUF025 +35 35 | +36 36 | +37 37 | def func(): +38 |- {n: ... for n in [1, 2, 3]} # RUF025 + 38 |+ dict.fromkeys([1, 2, 3], ...) # RUF025 +39 39 | +40 40 | +41 41 | def func(): -RUF025.py:32:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead +RUF025.py:42:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | -31 | def func(): -32 | {n: False for n in {1: "a", 2: "b"}} # RUF025 +41 | def func(): +42 | {n: False for n in {1: "a", 2: "b"}} # RUF025 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 -33 | -34 | def func(): | = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix -29 29 | {n: ... for n in [1,2,3]} # RUF025 -30 30 | -31 31 | def func(): -32 |- {n: False for n in {1: "a", 2: "b"}} # RUF025 - 32 |+ dict.fromkeys({1: "a", 2: "b"}, False) # RUF025 -33 33 | -34 34 | def func(): -35 35 | {(a, b): 1 for (a, b) in [(1,2), (3, 4)]} # RUF025 +39 39 | +40 40 | +41 41 | def func(): +42 |- {n: False for n in {1: "a", 2: "b"}} # RUF025 + 42 |+ dict.fromkeys({1: "a", 2: "b"}, False) # RUF025 +43 43 | +44 44 | +45 45 | def func(): -RUF025.py:35:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead +RUF025.py:46:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | -34 | def func(): -35 | {(a, b): 1 for (a, b) in [(1,2), (3, 4)]} # RUF025 - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 +45 | def func(): +46 | {(a, b): 1 for (a, b) in [(1, 2), (3, 4)]} # RUF025 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 | = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix -32 32 | {n: False for n in {1: "a", 2: "b"}} # RUF025 -33 33 | -34 34 | def func(): -35 |- {(a, b): 1 for (a, b) in [(1,2), (3, 4)]} # RUF025 - 35 |+ dict.fromkeys([(1, 2), (3, 4)], 1) # RUF025 -36 36 | -37 37 | -38 38 | # Non-violation cases: RUF025 +43 43 | +44 44 | +45 45 | def func(): +46 |- {(a, b): 1 for (a, b) in [(1, 2), (3, 4)]} # RUF025 + 46 |+ dict.fromkeys([(1, 2), (3, 4)], 1) # RUF025 +47 47 | +48 48 | +49 49 | # Non-violation cases: RUF025 -RUF025.py:41:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead +RUF025.py:53:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | -40 | def func(): -41 | {n: 1 for n in [1,2,3,4,5] if n < 3} # OK - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 -42 | -43 | def func(): +52 | def func(): +53 | {n: 1 for n in [1, 2, 3, 4, 5] if n < 3} # OK + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 | = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix -38 38 | # Non-violation cases: RUF025 -39 39 | -40 40 | def func(): -41 |- {n: 1 for n in [1,2,3,4,5] if n < 3} # OK - 41 |+ dict.fromkeys([1, 2, 3, 4, 5], 1) # OK -42 42 | -43 43 | def func(): -44 44 | {n: 1 for c in [1,2,3,4,5] for n in [1,2,3] if c < 3} # OK +50 50 | +51 51 | +52 52 | def func(): +53 |- {n: 1 for n in [1, 2, 3, 4, 5] if n < 3} # OK + 53 |+ dict.fromkeys([1, 2, 3, 4, 5], 1) # OK +54 54 | +55 55 | +56 56 | def func(): From d6e1395835cbe8414cbaee82c1e1911bfce661e2 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 23 Jan 2024 21:05:20 -0500 Subject: [PATCH 08/10] Re-enable name detection --- .../resources/test/fixtures/ruff/RUF025.py | 24 +++++--- ...cessary_dict_comprehension_for_iterable.rs | 51 +++++++++++++++- ..._rules__ruff__tests__RUF025_RUF025.py.snap | 61 +++++++++++++++---- 3 files changed, 112 insertions(+), 24 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py index 05db2afcf1621..8f971d2bf1f53 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF025.py @@ -46,6 +46,19 @@ def func(): {(a, b): 1 for (a, b) in [(1, 2), (3, 4)]} # RUF025 +def func(): + def f(): + return 1 + + a = f() + {n: a for n in [1, 2, 3]} # RUF025 + + +def func(): + values = ["a", "b", "c"] + [{n: values for n in [1, 2, 3]}] # RUF025 + + # Non-violation cases: RUF025 @@ -76,13 +89,4 @@ def f(): def func(): - def f(): - return 1 - - a = f() - {n: a for n in [1, 2, 3]} # OK - - -def func(): - values = ["a", "b", "c"] - [{n: values for n in [1, 2, 3]}] # OK + {(a, b): a + b for (a, b) in [(1, 2), (3, 4)]} # OK diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs index fa7ab0d0df3b0..e64092acf2557 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs @@ -2,7 +2,7 @@ use ast::ExprName; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::comparable::ComparableExpr; -use ruff_python_ast::helpers::is_constant; +use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::{self as ast, Arguments, Comprehension, Expr, ExprCall, ExprContext}; use ruff_text_size::{Ranged, TextRange}; @@ -74,7 +74,26 @@ pub(crate) fn unnecessary_dict_comprehension_for_iterable( return; } - if !is_constant(dict_comp.value.as_ref()) { + // Don't suggest `dict.fromkeys` if the value is not a constant or constant-like. + if !is_constant_like(dict_comp.value.as_ref()) { + return; + } + + // Don't suggest `dict.fromkeys` if any of the expressions in the value are defined within + // the comprehension (e.g., by the target). + if any_over_expr(dict_comp.value.as_ref(), &|expr| { + let Expr::Name(name) = expr else { + return false; + }; + + let Some(id) = checker.semantic().resolve_name(name) else { + return false; + }; + + let binding = checker.semantic().binding(id); + + dict_comp.range().contains_range(binding.range()) + }) { return; } @@ -100,6 +119,34 @@ pub(crate) fn unnecessary_dict_comprehension_for_iterable( checker.diagnostics.push(diagnostic); } +/// Returns `true` if the expression can be shared across multiple values. +/// +/// When converting from `{key: value for key in iterable}` to `dict.fromkeys(iterable, value)`, +/// the `value` is shared across all values without being evaluated multiple times. If the value +/// contains, e.g., a function call, it cannot be shared, as the function might have side effects. +/// Similarly, if the value contains a list comprehension, it cannot be shared, as `dict.fromkeys` +/// would leave each value with a reference to the same list. +fn is_constant_like(expr: &Expr) -> bool { + !any_over_expr(expr, &|expr| { + matches!( + expr, + Expr::Lambda(_) + | Expr::List(_) + | Expr::Dict(_) + | Expr::Set(_) + | Expr::ListComp(_) + | Expr::SetComp(_) + | Expr::DictComp(_) + | Expr::GeneratorExp(_) + | Expr::Await(_) + | Expr::Yield(_) + | Expr::YieldFrom(_) + | Expr::Call(_) + | Expr::NamedExpr(_) + ) + }) +} + /// Generate a [`Fix`] to replace `dict` comprehension with `dict.fromkeys`. /// /// For example: diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap index cbf592ee0ced3..71a1e8cf288f0 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap @@ -164,24 +164,61 @@ RUF025.py:46:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dic 46 |+ dict.fromkeys([(1, 2), (3, 4)], 1) # RUF025 47 47 | 48 48 | -49 49 | # Non-violation cases: RUF025 +49 49 | def func(): -RUF025.py:53:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead +RUF025.py:54:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead | -52 | def func(): -53 | {n: 1 for n in [1, 2, 3, 4, 5] if n < 3} # OK - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 +53 | a = f() +54 | {n: a for n in [1, 2, 3]} # RUF025 + | ^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 | = help: Replace with `dict.fromkeys(iterable)`) ℹ Safe fix -50 50 | -51 51 | -52 52 | def func(): -53 |- {n: 1 for n in [1, 2, 3, 4, 5] if n < 3} # OK - 53 |+ dict.fromkeys([1, 2, 3, 4, 5], 1) # OK -54 54 | +51 51 | return 1 +52 52 | +53 53 | a = f() +54 |- {n: a for n in [1, 2, 3]} # RUF025 + 54 |+ dict.fromkeys([1, 2, 3], a) # RUF025 55 55 | -56 56 | def func(): +56 56 | +57 57 | def func(): + +RUF025.py:59:6: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead + | +57 | def func(): +58 | values = ["a", "b", "c"] +59 | [{n: values for n in [1, 2, 3]}] # RUF025 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 + | + = help: Replace with `dict.fromkeys(iterable)`) + +ℹ Safe fix +56 56 | +57 57 | def func(): +58 58 | values = ["a", "b", "c"] +59 |- [{n: values for n in [1, 2, 3]}] # RUF025 + 59 |+ [dict.fromkeys([1, 2, 3], values)] # RUF025 +60 60 | +61 61 | +62 62 | # Non-violation cases: RUF025 + +RUF025.py:66:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead + | +65 | def func(): +66 | {n: 1 for n in [1, 2, 3, 4, 5] if n < 3} # OK + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 + | + = help: Replace with `dict.fromkeys(iterable)`) + +ℹ Safe fix +63 63 | +64 64 | +65 65 | def func(): +66 |- {n: 1 for n in [1, 2, 3, 4, 5] if n < 3} # OK + 66 |+ dict.fromkeys([1, 2, 3, 4, 5], 1) # OK +67 67 | +68 68 | +69 69 | def func(): From ebf534a4be49b3081e45d0fec6fb7d61d62fe9d7 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 23 Jan 2024 21:08:36 -0500 Subject: [PATCH 09/10] Fix Clippy --- .../rules/unnecessary_dict_comprehension_for_iterable.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs index e64092acf2557..423c9f7c99dee 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs @@ -81,7 +81,7 @@ pub(crate) fn unnecessary_dict_comprehension_for_iterable( // Don't suggest `dict.fromkeys` if any of the expressions in the value are defined within // the comprehension (e.g., by the target). - if any_over_expr(dict_comp.value.as_ref(), &|expr| { + let self_referential = any_over_expr(dict_comp.value.as_ref(), &|expr| { let Expr::Name(name) = expr else { return false; }; @@ -93,7 +93,8 @@ pub(crate) fn unnecessary_dict_comprehension_for_iterable( let binding = checker.semantic().binding(id); dict_comp.range().contains_range(binding.range()) - }) { + }); + if self_referential { return; } From 3cca228a66cf7ca65041f72b29c5152d83c24782 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 23 Jan 2024 21:09:58 -0500 Subject: [PATCH 10/10] Fix if --- ...ecessary_dict_comprehension_for_iterable.rs | 5 ++++- ...__rules__ruff__tests__RUF025_RUF025.py.snap | 18 ------------------ 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs index 423c9f7c99dee..6adc4f69038c9 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_dict_comprehension_for_iterable.rs @@ -65,7 +65,10 @@ pub(crate) fn unnecessary_dict_comprehension_for_iterable( // - async generator expressions, because `dict.fromkeys` is not async. // - nested generator expressions, because `dict.fromkeys` might be error-prone option at least for fixing. // - generator expressions with `if` clauses, because `dict.fromkeys` might not be valid option. - if !generator.ifs.is_empty() && generator.is_async { + if !generator.ifs.is_empty() { + return; + } + if generator.is_async { return; } diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap index 71a1e8cf288f0..519fb1b801390 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF025_RUF025.py.snap @@ -203,22 +203,4 @@ RUF025.py:59:6: RUF025 [*] Unnecessary dict comprehension for iterable; use `dic 61 61 | 62 62 | # Non-violation cases: RUF025 -RUF025.py:66:5: RUF025 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead - | -65 | def func(): -66 | {n: 1 for n in [1, 2, 3, 4, 5] if n < 3} # OK - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF025 - | - = help: Replace with `dict.fromkeys(iterable)`) - -ℹ Safe fix -63 63 | -64 64 | -65 65 | def func(): -66 |- {n: 1 for n in [1, 2, 3, 4, 5] if n < 3} # OK - 66 |+ dict.fromkeys([1, 2, 3, 4, 5], 1) # OK -67 67 | -68 68 | -69 69 | def func(): -