diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/dict_iter_missing_items.py b/crates/ruff_linter/resources/test/fixtures/pylint/dict_iter_missing_items.py index 18a5037c8365b..4f850f267db15 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/dict_iter_missing_items.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/dict_iter_missing_items.py @@ -1,5 +1,10 @@ +from typing import Any + + d = {1: 1, 2: 2} d_tuple = {(1, 2): 3, (4, 5): 6} +d_tuple_annotated: Any = {(1, 2): 3, (4, 5): 6} +d_tuple_incorrect_tuple = {(1,): 3, (4, 5): 6} l = [1, 2] s1 = {1, 2} s2 = {1, 2, 3} @@ -8,10 +13,10 @@ for k, v in d: pass -# False positive, since the keys are all tuples this is valid -for a, b in d_tuple: +for k, v in d_tuple_incorrect_tuple: pass + # Non errors for k, v in d.items(): pass @@ -21,3 +26,7 @@ pass for i, v in s1.intersection(s2): pass +for a, b in d_tuple: + pass +for a, b in d_tuple_annotated: + pass diff --git a/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs b/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs index 310859b0e36f9..f73b62bf73b8b 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/dict_iter_missing_items.rs @@ -65,6 +65,24 @@ pub(crate) fn dict_iter_missing_items(checker: &mut Checker, target: &Expr, iter return; } + // If we can reliably determine that a dictionary has keys that are tuples of two we don't warn + if let Some(statement) = binding.statement(checker.semantic()) { + if let Some(assignment) = statement.as_assign_stmt() { + if let Some(dict_expr) = assignment.value.as_dict_expr() { + if dict_expr.keys.iter().all(|elt| { + elt.as_ref().is_some_and(|x| { + if let Some(tuple) = x.as_tuple_expr() { + return tuple.elts.len() == 2; + } + false + }) + }) { + return; + } + } + } + }; + let mut diagnostic = Diagnostic::new(DictIterMissingItems, iter.range()); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( format!("{}.items()", name.id), diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1141_dict_iter_missing_items.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1141_dict_iter_missing_items.py.snap index f2bd51eda5fba..15f2e1295a625 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1141_dict_iter_missing_items.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE1141_dict_iter_missing_items.py.snap @@ -1,42 +1,43 @@ --- source: crates/ruff_linter/src/rules/pylint/mod.rs --- -dict_iter_missing_items.py:8:13: PLE1141 [*] Call `items()` when unpacking a dictionary for iteration - | -7 | # Errors -8 | for k, v in d: - | ^ PLE1141 -9 | pass - | - = help: Add a call to `.items()` +dict_iter_missing_items.py:13:13: PLE1141 [*] Call `items()` when unpacking a dictionary for iteration + | +12 | # Errors +13 | for k, v in d: + | ^ PLE1141 +14 | pass + | + = help: Add a call to `.items()` ℹ Safe fix -5 5 | s2 = {1, 2, 3} -6 6 | -7 7 | # Errors -8 |-for k, v in d: - 8 |+for k, v in d.items(): -9 9 | pass -10 10 | -11 11 | # False positive, since the keys are all tuples this is valid +10 10 | s2 = {1, 2, 3} +11 11 | +12 12 | # Errors +13 |-for k, v in d: + 13 |+for k, v in d.items(): +14 14 | pass +15 15 | +16 16 | for k, v in d_tuple_incorrect_tuple: -dict_iter_missing_items.py:12:13: PLE1141 [*] Call `items()` when unpacking a dictionary for iteration +dict_iter_missing_items.py:16:13: PLE1141 [*] Call `items()` when unpacking a dictionary for iteration | -11 | # False positive, since the keys are all tuples this is valid -12 | for a, b in d_tuple: - | ^^^^^^^ PLE1141 -13 | pass +14 | pass +15 | +16 | for k, v in d_tuple_incorrect_tuple: + | ^^^^^^^^^^^^^^^^^^^^^^^ PLE1141 +17 | pass | = help: Add a call to `.items()` ℹ Safe fix -9 9 | pass -10 10 | -11 11 | # False positive, since the keys are all tuples this is valid -12 |-for a, b in d_tuple: - 12 |+for a, b in d_tuple.items(): -13 13 | pass -14 14 | -15 15 | # Non errors +13 13 | for k, v in d: +14 14 | pass +15 15 | +16 |-for k, v in d_tuple_incorrect_tuple: + 16 |+for k, v in d_tuple_incorrect_tuple.items(): +17 17 | pass +18 18 | +19 19 |