diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C420.py b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C420.py index 8f971d2bf1..03a6f44628 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C420.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C420.py @@ -90,3 +90,14 @@ def func(): def func(): {(a, b): a + b for (a, b) in [(1, 2), (3, 4)]} # OK + +# https://github.com/astral-sh/ruff/issues/18764 +{ # 1 +a # 2 +: # 3 +None # 4 +for # 5 +a # 6 +in # 7 +iterable # 8 +} # 9 diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs index d7defe6c8b..b4bf05e41a 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/rules/unnecessary_dict_comprehension_for_iterable.rs @@ -1,4 +1,5 @@ use ast::ExprName; +use ruff_diagnostics::Applicability; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::helpers::any_over_expr; @@ -31,6 +32,19 @@ use crate::{Edit, Fix, FixAvailability, Violation}; /// dict.fromkeys(iterable, 1) /// ``` /// +/// ## Fix safety +/// This rule's fix is marked as unsafe if there's comments inside the dict comprehension, +/// as comments may be removed. +/// +/// For example, the fix would be marked as unsafe in the following case: +/// ```python +/// { # comment 1 +/// a: # comment 2 +/// None # comment 3 +/// for a in iterable # comment 4 +/// } +/// ``` +/// /// ## References /// - [Python documentation: `dict.fromkeys`](https://docs.python.org/3/library/stdtypes.html#dict.fromkeys) #[derive(ViolationMetadata)] @@ -121,7 +135,7 @@ pub(crate) fn unnecessary_dict_comprehension_for_iterable( ); if checker.semantic().has_builtin_binding("dict") { - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + let edit = Edit::range_replacement( checker .generator() .expr(&fix_unnecessary_dict_comprehension( @@ -129,7 +143,15 @@ pub(crate) fn unnecessary_dict_comprehension_for_iterable( generator, )), dict_comp.range(), - ))); + ); + diagnostic.set_fix(Fix::applicable_edit( + edit, + if checker.comment_ranges().intersects(dict_comp.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }, + )); } } diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C420_C420.py.snap b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C420_C420.py.snap index 2a45889f36..0914f762bf 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C420_C420.py.snap +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C420_C420.py.snap @@ -202,3 +202,34 @@ C420.py:59:6: C420 [*] Unnecessary dict comprehension for iterable; use `dict.fr 60 60 | 61 61 | 62 62 | # Non-violation cases: RUF025 + +C420.py:95:1: C420 [*] Unnecessary dict comprehension for iterable; use `dict.fromkeys` instead + | + 94 | # https://github.com/astral-sh/ruff/issues/18764 + 95 | / { # 1 + 96 | | a # 2 + 97 | | : # 3 + 98 | | None # 4 + 99 | | for # 5 +100 | | a # 6 +101 | | in # 7 +102 | | iterable # 8 +103 | | } # 9 + | |_^ C420 + | + = help: Replace with `dict.fromkeys(iterable, value)`) + +ℹ Unsafe fix +92 92 | {(a, b): a + b for (a, b) in [(1, 2), (3, 4)]} # OK +93 93 | +94 94 | # https://github.com/astral-sh/ruff/issues/18764 +95 |-{ # 1 +96 |-a # 2 +97 |-: # 3 +98 |-None # 4 +99 |-for # 5 +100 |-a # 6 +101 |-in # 7 +102 |-iterable # 8 +103 |-} # 9 + 95 |+dict.fromkeys(iterable) # 9