From ce0a32aadb16c9e2aea74013b5f0e105c2737253 Mon Sep 17 00:00:00 2001 From: Dylan Date: Thu, 19 Jun 2025 16:23:46 -0500 Subject: [PATCH] [`flake8-comprehensions`] Handle template strings for comprehension fixes (#18710) Essentially this PR ensures that when we do fixes like this: ```diff - t"{set(f(x) for x in foo)}" + t"{ {f(x) for x in foo} }" ``` we are correctly adding whitespace around the braces. This logic is already in place for f-strings and just needed to be generalized to interpolated strings. --- .../fixtures/flake8_comprehensions/C401.py | 10 + .../fixtures/flake8_comprehensions/C403.py | 14 +- .../fixtures/flake8_comprehensions/C405.py | 9 + .../fixtures/flake8_comprehensions/C408.py | 10 + .../fixtures/flake8_comprehensions/C417.py | 5 + crates/ruff_linter/src/checkers/ast/mod.rs | 33 ++- .../src/rules/flake8_comprehensions/fixes.rs | 14 +- ...8_comprehensions__tests__C401_C401.py.snap | 167 ++++++++++++- ...8_comprehensions__tests__C403_C403.py.snap | 136 ++++++++++ ...8_comprehensions__tests__C405_C405.py.snap | 225 +++++++++++++++++ ...8_comprehensions__tests__C408_C408.py.snap | 234 ++++++++++++++++++ ...low_dict_calls_with_keyword_arguments.snap | 45 ++++ ...8_comprehensions__tests__C417_C417.py.snap | 35 +++ crates/ruff_python_semantic/src/model.rs | 5 + 14 files changed, 919 insertions(+), 23 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C401.py b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C401.py index 7d7ee4c556..27721d8116 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C401.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C401.py @@ -36,9 +36,19 @@ set( # some more ) +# t-strings +print(t"Hello {set(f(a) for a in 'abc')} World") +print(t"Hello { set(f(a) for a in 'abc') } World") +small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" +print(t"Hello {set(a for a in range(3))} World") +print(t"{set(a for a in 'abc') - set(a for a in 'ab')}") +print(t"{ set(a for a in 'abc') - set(a for a in 'ab') }") + + # Not built-in set. def set(*args, **kwargs): return None set(2 * x for x in range(3)) set(x for x in range(3)) + diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C403.py b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C403.py index c1a9feb24c..6c1efbb449 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C403.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C403.py @@ -34,4 +34,16 @@ s = set( # outer set comment )))) # Test trailing comma case -s = set([x for x in range(3)],) \ No newline at end of file +s = set([x for x in range(3)],) + +s = t"{set([x for x in 'ab'])}" +s = t'{set([x for x in "ab"])}' + +def f(x): + return x + +s = t"{set([f(x) for x in 'ab'])}" + +s = t"{ set([x for x in 'ab']) | set([x for x in 'ab']) }" +s = t"{set([x for x in 'ab']) | set([x for x in 'ab'])}" + diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C405.py b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C405.py index 9c250811b0..adef711093 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C405.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C405.py @@ -24,3 +24,12 @@ f"{set(['a', 'b']) - set(['a'])}" f"{ set(['a', 'b']) - set(['a']) }" f"a {set(['a', 'b']) - set(['a'])} b" f"a { set(['a', 'b']) - set(['a']) } b" + +t"{set([1,2,3])}" +t"{set(['a', 'b'])}" +t'{set(["a", "b"])}' + +t"{set(['a', 'b']) - set(['a'])}" +t"{ set(['a', 'b']) - set(['a']) }" +t"a {set(['a', 'b']) - set(['a'])} b" +t"a { set(['a', 'b']) - set(['a']) } b" diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C408.py b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C408.py index da0ebcaf71..c1ac839e27 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C408.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C408.py @@ -27,3 +27,13 @@ dict( tuple( # comment ) + +t"{dict(x='y')}" +t'{dict(x="y")}' +t"{dict()}" +t"a {dict()} b" + +t"{dict(x='y') | dict(y='z')}" +t"{ dict(x='y') | dict(y='z') }" +t"a {dict(x='y') | dict(y='z')} b" +t"a { dict(x='y') | dict(y='z') } b" diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C417.py b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C417.py index 8380774d9f..dd7b93f890 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C417.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_comprehensions/C417.py @@ -70,3 +70,8 @@ list(map(lambda: 1, "xyz")) list(map(lambda x, y: x, [(1, 2), (3, 4)])) list(map(lambda: 1, "xyz")) list(map(lambda x, y: x, [(1, 2), (3, 4)])) + +# When inside t-string, then the fix should be surrounded by whitespace +_ = t"{set(map(lambda x: x % 2 == 0, nums))}" +_ = t"{dict(map(lambda v: (v, v**2), nums))}" + diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 11447e0de9..9dc7c6fa7b 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -37,8 +37,8 @@ use ruff_python_ast::str::Quote; use ruff_python_ast::visitor::{Visitor, walk_except_handler, walk_pattern}; use ruff_python_ast::{ self as ast, AnyParameterRef, ArgOrKeyword, Comprehension, ElifElseClause, ExceptHandler, Expr, - ExprContext, InterpolatedStringElement, Keyword, MatchCase, ModModule, Parameter, Parameters, - Pattern, PythonVersion, Stmt, Suite, UnaryOp, + ExprContext, ExprFString, ExprTString, InterpolatedStringElement, Keyword, MatchCase, + ModModule, Parameter, Parameters, Pattern, PythonVersion, Stmt, Suite, UnaryOp, }; use ruff_python_ast::{PySourceType, helpers, str, visitor}; use ruff_python_codegen::{Generator, Stylist}; @@ -323,7 +323,8 @@ impl<'a> Checker<'a> { /// Return the preferred quote for a generated `StringLiteral` node, given where we are in the /// AST. fn preferred_quote(&self) -> Quote { - self.f_string_quote_style().unwrap_or(self.stylist.quote()) + self.interpolated_string_quote_style() + .unwrap_or(self.stylist.quote()) } /// Return the default string flags a generated `StringLiteral` node should use, given where we @@ -345,21 +346,27 @@ impl<'a> Checker<'a> { ast::FStringFlags::empty().with_quote_style(self.preferred_quote()) } - /// Returns the appropriate quoting for f-string by reversing the one used outside of - /// the f-string. + /// Returns the appropriate quoting for interpolated strings by reversing the one used outside of + /// the interpolated string. /// - /// If the current expression in the context is not an f-string, returns ``None``. - pub(crate) fn f_string_quote_style(&self) -> Option { - if !self.semantic.in_f_string() { + /// If the current expression in the context is not an interpolated string, returns ``None``. + pub(crate) fn interpolated_string_quote_style(&self) -> Option { + if !self.semantic.in_interpolated_string() { return None; } - // Find the quote character used to start the containing f-string. - let ast::ExprFString { value, .. } = self - .semantic + // Find the quote character used to start the containing interpolated string. + self.semantic .current_expressions() - .find_map(|expr| expr.as_f_string_expr())?; - Some(value.iter().next()?.quote_style().opposite()) + .find_map(|expr| match expr { + Expr::FString(ExprFString { value, .. }) => { + Some(value.iter().next()?.quote_style().opposite()) + } + Expr::TString(ExprTString { value, .. }) => { + Some(value.iter().next()?.quote_style().opposite()) + } + _ => None, + }) } /// Returns the [`SourceRow`] for the given offset. diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs index fdddf2e5bb..32d850820b 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs @@ -236,7 +236,9 @@ pub(crate) fn fix_unnecessary_collection_call( // below. let mut arena: Vec = vec![]; - let quote = checker.f_string_quote_style().unwrap_or(stylist.quote()); + let quote = checker + .interpolated_string_quote_style() + .unwrap_or(stylist.quote()); // Quote each argument. for arg in &call.args { @@ -317,7 +319,7 @@ pub(crate) fn pad_expression( locator: &Locator, semantic: &SemanticModel, ) -> String { - if !semantic.in_f_string() { + if !semantic.in_interpolated_string() { return content; } @@ -349,7 +351,7 @@ pub(crate) fn pad_start( locator: &Locator, semantic: &SemanticModel, ) -> String { - if !semantic.in_f_string() { + if !semantic.in_interpolated_string() { return content.into(); } @@ -370,7 +372,7 @@ pub(crate) fn pad_end( locator: &Locator, semantic: &SemanticModel, ) -> String { - if !semantic.in_f_string() { + if !semantic.in_interpolated_string() { return content.into(); } @@ -798,10 +800,10 @@ pub(crate) fn fix_unnecessary_map( let mut content = tree.codegen_stylist(stylist); - // If the expression is embedded in an f-string, surround it with spaces to avoid + // If the expression is embedded in an interpolated string, surround it with spaces to avoid // syntax errors. if matches!(object_type, ObjectType::Set | ObjectType::Dict) { - if parent.is_some_and(Expr::is_f_string_expr) { + if parent.is_some_and(|expr| expr.is_f_string_expr() || expr.is_t_string_expr()) { content = format!(" {content} "); } } diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C401_C401.py.snap b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C401_C401.py.snap index 7ca2ce2725..c2af13db5e 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C401_C401.py.snap +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C401_C401.py.snap @@ -346,7 +346,7 @@ C401.py:32:1: C401 [*] Unnecessary generator (rewrite as a set comprehension) 37 | | ) | |_^ C401 38 | -39 | # Not built-in set. +39 | # t-strings | = help: Rewrite as a set comprehension @@ -364,5 +364,166 @@ C401.py:32:1: C401 [*] Unnecessary generator (rewrite as a set comprehension) 37 |-) 35 |+ } 38 36 | -39 37 | # Not built-in set. -40 38 | def set(*args, **kwargs): +39 37 | # t-strings +40 38 | print(t"Hello {set(f(a) for a in 'abc')} World") + +C401.py:40:16: C401 [*] Unnecessary generator (rewrite as a set comprehension) + | +39 | # t-strings +40 | print(t"Hello {set(f(a) for a in 'abc')} World") + | ^^^^^^^^^^^^^^^^^^^^^^^^ C401 +41 | print(t"Hello { set(f(a) for a in 'abc') } World") +42 | small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" + | + = help: Rewrite as a set comprehension + +ℹ Unsafe fix +37 37 | ) +38 38 | +39 39 | # t-strings +40 |-print(t"Hello {set(f(a) for a in 'abc')} World") + 40 |+print(t"Hello { {f(a) for a in 'abc'} } World") +41 41 | print(t"Hello { set(f(a) for a in 'abc') } World") +42 42 | small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" +43 43 | print(t"Hello {set(a for a in range(3))} World") + +C401.py:41:17: C401 [*] Unnecessary generator (rewrite as a set comprehension) + | +39 | # t-strings +40 | print(t"Hello {set(f(a) for a in 'abc')} World") +41 | print(t"Hello { set(f(a) for a in 'abc') } World") + | ^^^^^^^^^^^^^^^^^^^^^^^^ C401 +42 | small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" +43 | print(t"Hello {set(a for a in range(3))} World") + | + = help: Rewrite as a set comprehension + +ℹ Unsafe fix +38 38 | +39 39 | # t-strings +40 40 | print(t"Hello {set(f(a) for a in 'abc')} World") +41 |-print(t"Hello { set(f(a) for a in 'abc') } World") + 41 |+print(t"Hello { {f(a) for a in 'abc'} } World") +42 42 | small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" +43 43 | print(t"Hello {set(a for a in range(3))} World") +44 44 | print(t"{set(a for a in 'abc') - set(a for a in 'ab')}") + +C401.py:42:17: C401 [*] Unnecessary generator (rewrite as a set comprehension) + | +40 | print(t"Hello {set(f(a) for a in 'abc')} World") +41 | print(t"Hello { set(f(a) for a in 'abc') } World") +42 | small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C401 +43 | print(t"Hello {set(a for a in range(3))} World") +44 | print(t"{set(a for a in 'abc') - set(a for a in 'ab')}") + | + = help: Rewrite as a set comprehension + +ℹ Unsafe fix +39 39 | # t-strings +40 40 | print(t"Hello {set(f(a) for a in 'abc')} World") +41 41 | print(t"Hello { set(f(a) for a in 'abc') } World") +42 |-small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" + 42 |+small_nums = t"{ {a if a < 6 else 0 for a in range(3)} }" +43 43 | print(t"Hello {set(a for a in range(3))} World") +44 44 | print(t"{set(a for a in 'abc') - set(a for a in 'ab')}") +45 45 | print(t"{ set(a for a in 'abc') - set(a for a in 'ab') }") + +C401.py:43:16: C401 [*] Unnecessary generator (rewrite using `set()`) + | +41 | print(t"Hello { set(f(a) for a in 'abc') } World") +42 | small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" +43 | print(t"Hello {set(a for a in range(3))} World") + | ^^^^^^^^^^^^^^^^^^^^^^^^ C401 +44 | print(t"{set(a for a in 'abc') - set(a for a in 'ab')}") +45 | print(t"{ set(a for a in 'abc') - set(a for a in 'ab') }") + | + = help: Rewrite using `set()` + +ℹ Unsafe fix +40 40 | print(t"Hello {set(f(a) for a in 'abc')} World") +41 41 | print(t"Hello { set(f(a) for a in 'abc') } World") +42 42 | small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" +43 |-print(t"Hello {set(a for a in range(3))} World") + 43 |+print(t"Hello {set(range(3))} World") +44 44 | print(t"{set(a for a in 'abc') - set(a for a in 'ab')}") +45 45 | print(t"{ set(a for a in 'abc') - set(a for a in 'ab') }") +46 46 | + +C401.py:44:10: C401 [*] Unnecessary generator (rewrite using `set()`) + | +42 | small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" +43 | print(t"Hello {set(a for a in range(3))} World") +44 | print(t"{set(a for a in 'abc') - set(a for a in 'ab')}") + | ^^^^^^^^^^^^^^^^^^^^^ C401 +45 | print(t"{ set(a for a in 'abc') - set(a for a in 'ab') }") + | + = help: Rewrite using `set()` + +ℹ Unsafe fix +41 41 | print(t"Hello { set(f(a) for a in 'abc') } World") +42 42 | small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" +43 43 | print(t"Hello {set(a for a in range(3))} World") +44 |-print(t"{set(a for a in 'abc') - set(a for a in 'ab')}") + 44 |+print(t"{set('abc') - set(a for a in 'ab')}") +45 45 | print(t"{ set(a for a in 'abc') - set(a for a in 'ab') }") +46 46 | +47 47 | + +C401.py:44:34: C401 [*] Unnecessary generator (rewrite using `set()`) + | +42 | small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" +43 | print(t"Hello {set(a for a in range(3))} World") +44 | print(t"{set(a for a in 'abc') - set(a for a in 'ab')}") + | ^^^^^^^^^^^^^^^^^^^^ C401 +45 | print(t"{ set(a for a in 'abc') - set(a for a in 'ab') }") + | + = help: Rewrite using `set()` + +ℹ Unsafe fix +41 41 | print(t"Hello { set(f(a) for a in 'abc') } World") +42 42 | small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" +43 43 | print(t"Hello {set(a for a in range(3))} World") +44 |-print(t"{set(a for a in 'abc') - set(a for a in 'ab')}") + 44 |+print(t"{set(a for a in 'abc') - set('ab')}") +45 45 | print(t"{ set(a for a in 'abc') - set(a for a in 'ab') }") +46 46 | +47 47 | + +C401.py:45:11: C401 [*] Unnecessary generator (rewrite using `set()`) + | +43 | print(t"Hello {set(a for a in range(3))} World") +44 | print(t"{set(a for a in 'abc') - set(a for a in 'ab')}") +45 | print(t"{ set(a for a in 'abc') - set(a for a in 'ab') }") + | ^^^^^^^^^^^^^^^^^^^^^ C401 + | + = help: Rewrite using `set()` + +ℹ Unsafe fix +42 42 | small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" +43 43 | print(t"Hello {set(a for a in range(3))} World") +44 44 | print(t"{set(a for a in 'abc') - set(a for a in 'ab')}") +45 |-print(t"{ set(a for a in 'abc') - set(a for a in 'ab') }") + 45 |+print(t"{ set('abc') - set(a for a in 'ab') }") +46 46 | +47 47 | +48 48 | # Not built-in set. + +C401.py:45:35: C401 [*] Unnecessary generator (rewrite using `set()`) + | +43 | print(t"Hello {set(a for a in range(3))} World") +44 | print(t"{set(a for a in 'abc') - set(a for a in 'ab')}") +45 | print(t"{ set(a for a in 'abc') - set(a for a in 'ab') }") + | ^^^^^^^^^^^^^^^^^^^^ C401 + | + = help: Rewrite using `set()` + +ℹ Unsafe fix +42 42 | small_nums = t"{set(a if a < 6 else 0 for a in range(3))}" +43 43 | print(t"Hello {set(a for a in range(3))} World") +44 44 | print(t"{set(a for a in 'abc') - set(a for a in 'ab')}") +45 |-print(t"{ set(a for a in 'abc') - set(a for a in 'ab') }") + 45 |+print(t"{ set(a for a in 'abc') - set('ab') }") +46 46 | +47 47 | +48 48 | # Not built-in set. diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C403_C403.py.snap b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C403_C403.py.snap index 57b06b06d6..df5f910c3a 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C403_C403.py.snap +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C403_C403.py.snap @@ -319,6 +319,8 @@ C403.py:37:5: C403 [*] Unnecessary list comprehension (rewrite as a set comprehe 36 | # Test trailing comma case 37 | s = set([x for x in range(3)],) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ C403 +38 | +39 | s = t"{set([x for x in 'ab'])}" | = help: Rewrite as a set comprehension @@ -328,3 +330,137 @@ C403.py:37:5: C403 [*] Unnecessary list comprehension (rewrite as a set comprehe 36 36 | # Test trailing comma case 37 |-s = set([x for x in range(3)],) 37 |+s = {x for x in range(3)} +38 38 | +39 39 | s = t"{set([x for x in 'ab'])}" +40 40 | s = t'{set([x for x in "ab"])}' + +C403.py:39:8: C403 [*] Unnecessary list comprehension (rewrite as a set comprehension) + | +37 | s = set([x for x in range(3)],) +38 | +39 | s = t"{set([x for x in 'ab'])}" + | ^^^^^^^^^^^^^^^^^^^^^^ C403 +40 | s = t'{set([x for x in "ab"])}' + | + = help: Rewrite as a set comprehension + +ℹ Unsafe fix +36 36 | # Test trailing comma case +37 37 | s = set([x for x in range(3)],) +38 38 | +39 |-s = t"{set([x for x in 'ab'])}" + 39 |+s = t"{ {x for x in 'ab'} }" +40 40 | s = t'{set([x for x in "ab"])}' +41 41 | +42 42 | def f(x): + +C403.py:40:8: C403 [*] Unnecessary list comprehension (rewrite as a set comprehension) + | +39 | s = t"{set([x for x in 'ab'])}" +40 | s = t'{set([x for x in "ab"])}' + | ^^^^^^^^^^^^^^^^^^^^^^ C403 +41 | +42 | def f(x): + | + = help: Rewrite as a set comprehension + +ℹ Unsafe fix +37 37 | s = set([x for x in range(3)],) +38 38 | +39 39 | s = t"{set([x for x in 'ab'])}" +40 |-s = t'{set([x for x in "ab"])}' + 40 |+s = t'{ {x for x in "ab"} }' +41 41 | +42 42 | def f(x): +43 43 | return x + +C403.py:45:8: C403 [*] Unnecessary list comprehension (rewrite as a set comprehension) + | +43 | return x +44 | +45 | s = t"{set([f(x) for x in 'ab'])}" + | ^^^^^^^^^^^^^^^^^^^^^^^^^ C403 +46 | +47 | s = t"{ set([x for x in 'ab']) | set([x for x in 'ab']) }" + | + = help: Rewrite as a set comprehension + +ℹ Unsafe fix +42 42 | def f(x): +43 43 | return x +44 44 | +45 |-s = t"{set([f(x) for x in 'ab'])}" + 45 |+s = t"{ {f(x) for x in 'ab'} }" +46 46 | +47 47 | s = t"{ set([x for x in 'ab']) | set([x for x in 'ab']) }" +48 48 | s = t"{set([x for x in 'ab']) | set([x for x in 'ab'])}" + +C403.py:47:9: C403 [*] Unnecessary list comprehension (rewrite as a set comprehension) + | +45 | s = t"{set([f(x) for x in 'ab'])}" +46 | +47 | s = t"{ set([x for x in 'ab']) | set([x for x in 'ab']) }" + | ^^^^^^^^^^^^^^^^^^^^^^ C403 +48 | s = t"{set([x for x in 'ab']) | set([x for x in 'ab'])}" + | + = help: Rewrite as a set comprehension + +ℹ Unsafe fix +44 44 | +45 45 | s = t"{set([f(x) for x in 'ab'])}" +46 46 | +47 |-s = t"{ set([x for x in 'ab']) | set([x for x in 'ab']) }" + 47 |+s = t"{ {x for x in 'ab'} | set([x for x in 'ab']) }" +48 48 | s = t"{set([x for x in 'ab']) | set([x for x in 'ab'])}" +49 49 | + +C403.py:47:34: C403 [*] Unnecessary list comprehension (rewrite as a set comprehension) + | +45 | s = t"{set([f(x) for x in 'ab'])}" +46 | +47 | s = t"{ set([x for x in 'ab']) | set([x for x in 'ab']) }" + | ^^^^^^^^^^^^^^^^^^^^^^ C403 +48 | s = t"{set([x for x in 'ab']) | set([x for x in 'ab'])}" + | + = help: Rewrite as a set comprehension + +ℹ Unsafe fix +44 44 | +45 45 | s = t"{set([f(x) for x in 'ab'])}" +46 46 | +47 |-s = t"{ set([x for x in 'ab']) | set([x for x in 'ab']) }" + 47 |+s = t"{ set([x for x in 'ab']) | {x for x in 'ab'} }" +48 48 | s = t"{set([x for x in 'ab']) | set([x for x in 'ab'])}" +49 49 | + +C403.py:48:8: C403 [*] Unnecessary list comprehension (rewrite as a set comprehension) + | +47 | s = t"{ set([x for x in 'ab']) | set([x for x in 'ab']) }" +48 | s = t"{set([x for x in 'ab']) | set([x for x in 'ab'])}" + | ^^^^^^^^^^^^^^^^^^^^^^ C403 + | + = help: Rewrite as a set comprehension + +ℹ Unsafe fix +45 45 | s = t"{set([f(x) for x in 'ab'])}" +46 46 | +47 47 | s = t"{ set([x for x in 'ab']) | set([x for x in 'ab']) }" +48 |-s = t"{set([x for x in 'ab']) | set([x for x in 'ab'])}" + 48 |+s = t"{ {x for x in 'ab'} | set([x for x in 'ab'])}" +49 49 | + +C403.py:48:33: C403 [*] Unnecessary list comprehension (rewrite as a set comprehension) + | +47 | s = t"{ set([x for x in 'ab']) | set([x for x in 'ab']) }" +48 | s = t"{set([x for x in 'ab']) | set([x for x in 'ab'])}" + | ^^^^^^^^^^^^^^^^^^^^^^ C403 + | + = help: Rewrite as a set comprehension + +ℹ Unsafe fix +45 45 | s = t"{set([f(x) for x in 'ab'])}" +46 46 | +47 47 | s = t"{ set([x for x in 'ab']) | set([x for x in 'ab']) }" +48 |-s = t"{set([x for x in 'ab']) | set([x for x in 'ab'])}" + 48 |+s = t"{set([x for x in 'ab']) | {x for x in 'ab'} }" +49 49 | diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C405_C405.py.snap b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C405_C405.py.snap index b3c7e4b5be..b8df7cf5b0 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C405_C405.py.snap +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C405_C405.py.snap @@ -322,6 +322,7 @@ C405.py:24:5: C405 [*] Unnecessary list literal (rewrite as a set literal) 24 |+f"{ {'a', 'b'} - set(['a']) }" 25 25 | f"a {set(['a', 'b']) - set(['a'])} b" 26 26 | f"a { set(['a', 'b']) - set(['a']) } b" +27 27 | C405.py:24:23: C405 [*] Unnecessary list literal (rewrite as a set literal) | @@ -341,6 +342,7 @@ C405.py:24:23: C405 [*] Unnecessary list literal (rewrite as a set literal) 24 |+f"{ set(['a', 'b']) - {'a'} }" 25 25 | f"a {set(['a', 'b']) - set(['a'])} b" 26 26 | f"a { set(['a', 'b']) - set(['a']) } b" +27 27 | C405.py:25:6: C405 [*] Unnecessary list literal (rewrite as a set literal) | @@ -359,6 +361,8 @@ C405.py:25:6: C405 [*] Unnecessary list literal (rewrite as a set literal) 25 |-f"a {set(['a', 'b']) - set(['a'])} b" 25 |+f"a { {'a', 'b'} - set(['a'])} b" 26 26 | f"a { set(['a', 'b']) - set(['a']) } b" +27 27 | +28 28 | t"{set([1,2,3])}" C405.py:25:24: C405 [*] Unnecessary list literal (rewrite as a set literal) | @@ -377,6 +381,8 @@ C405.py:25:24: C405 [*] Unnecessary list literal (rewrite as a set literal) 25 |-f"a {set(['a', 'b']) - set(['a'])} b" 25 |+f"a {set(['a', 'b']) - {'a'} } b" 26 26 | f"a { set(['a', 'b']) - set(['a']) } b" +27 27 | +28 28 | t"{set([1,2,3])}" C405.py:26:7: C405 [*] Unnecessary list literal (rewrite as a set literal) | @@ -384,6 +390,8 @@ C405.py:26:7: C405 [*] Unnecessary list literal (rewrite as a set literal) 25 | f"a {set(['a', 'b']) - set(['a'])} b" 26 | f"a { set(['a', 'b']) - set(['a']) } b" | ^^^^^^^^^^^^^^^ C405 +27 | +28 | t"{set([1,2,3])}" | = help: Rewrite as a set literal @@ -393,6 +401,9 @@ C405.py:26:7: C405 [*] Unnecessary list literal (rewrite as a set literal) 25 25 | f"a {set(['a', 'b']) - set(['a'])} b" 26 |-f"a { set(['a', 'b']) - set(['a']) } b" 26 |+f"a { {'a', 'b'} - set(['a']) } b" +27 27 | +28 28 | t"{set([1,2,3])}" +29 29 | t"{set(['a', 'b'])}" C405.py:26:25: C405 [*] Unnecessary list literal (rewrite as a set literal) | @@ -400,6 +411,8 @@ C405.py:26:25: C405 [*] Unnecessary list literal (rewrite as a set literal) 25 | f"a {set(['a', 'b']) - set(['a'])} b" 26 | f"a { set(['a', 'b']) - set(['a']) } b" | ^^^^^^^^^^ C405 +27 | +28 | t"{set([1,2,3])}" | = help: Rewrite as a set literal @@ -409,3 +422,215 @@ C405.py:26:25: C405 [*] Unnecessary list literal (rewrite as a set literal) 25 25 | f"a {set(['a', 'b']) - set(['a'])} b" 26 |-f"a { set(['a', 'b']) - set(['a']) } b" 26 |+f"a { set(['a', 'b']) - {'a'} } b" +27 27 | +28 28 | t"{set([1,2,3])}" +29 29 | t"{set(['a', 'b'])}" + +C405.py:28:4: C405 [*] Unnecessary list literal (rewrite as a set literal) + | +26 | f"a { set(['a', 'b']) - set(['a']) } b" +27 | +28 | t"{set([1,2,3])}" + | ^^^^^^^^^^^^ C405 +29 | t"{set(['a', 'b'])}" +30 | t'{set(["a", "b"])}' + | + = help: Rewrite as a set literal + +ℹ Unsafe fix +25 25 | f"a {set(['a', 'b']) - set(['a'])} b" +26 26 | f"a { set(['a', 'b']) - set(['a']) } b" +27 27 | +28 |-t"{set([1,2,3])}" + 28 |+t"{ {1,2,3} }" +29 29 | t"{set(['a', 'b'])}" +30 30 | t'{set(["a", "b"])}' +31 31 | + +C405.py:29:4: C405 [*] Unnecessary list literal (rewrite as a set literal) + | +28 | t"{set([1,2,3])}" +29 | t"{set(['a', 'b'])}" + | ^^^^^^^^^^^^^^^ C405 +30 | t'{set(["a", "b"])}' + | + = help: Rewrite as a set literal + +ℹ Unsafe fix +26 26 | f"a { set(['a', 'b']) - set(['a']) } b" +27 27 | +28 28 | t"{set([1,2,3])}" +29 |-t"{set(['a', 'b'])}" + 29 |+t"{ {'a', 'b'} }" +30 30 | t'{set(["a", "b"])}' +31 31 | +32 32 | t"{set(['a', 'b']) - set(['a'])}" + +C405.py:30:4: C405 [*] Unnecessary list literal (rewrite as a set literal) + | +28 | t"{set([1,2,3])}" +29 | t"{set(['a', 'b'])}" +30 | t'{set(["a", "b"])}' + | ^^^^^^^^^^^^^^^ C405 +31 | +32 | t"{set(['a', 'b']) - set(['a'])}" + | + = help: Rewrite as a set literal + +ℹ Unsafe fix +27 27 | +28 28 | t"{set([1,2,3])}" +29 29 | t"{set(['a', 'b'])}" +30 |-t'{set(["a", "b"])}' + 30 |+t'{ {"a", "b"} }' +31 31 | +32 32 | t"{set(['a', 'b']) - set(['a'])}" +33 33 | t"{ set(['a', 'b']) - set(['a']) }" + +C405.py:32:4: C405 [*] Unnecessary list literal (rewrite as a set literal) + | +30 | t'{set(["a", "b"])}' +31 | +32 | t"{set(['a', 'b']) - set(['a'])}" + | ^^^^^^^^^^^^^^^ C405 +33 | t"{ set(['a', 'b']) - set(['a']) }" +34 | t"a {set(['a', 'b']) - set(['a'])} b" + | + = help: Rewrite as a set literal + +ℹ Unsafe fix +29 29 | t"{set(['a', 'b'])}" +30 30 | t'{set(["a", "b"])}' +31 31 | +32 |-t"{set(['a', 'b']) - set(['a'])}" + 32 |+t"{ {'a', 'b'} - set(['a'])}" +33 33 | t"{ set(['a', 'b']) - set(['a']) }" +34 34 | t"a {set(['a', 'b']) - set(['a'])} b" +35 35 | t"a { set(['a', 'b']) - set(['a']) } b" + +C405.py:32:22: C405 [*] Unnecessary list literal (rewrite as a set literal) + | +30 | t'{set(["a", "b"])}' +31 | +32 | t"{set(['a', 'b']) - set(['a'])}" + | ^^^^^^^^^^ C405 +33 | t"{ set(['a', 'b']) - set(['a']) }" +34 | t"a {set(['a', 'b']) - set(['a'])} b" + | + = help: Rewrite as a set literal + +ℹ Unsafe fix +29 29 | t"{set(['a', 'b'])}" +30 30 | t'{set(["a", "b"])}' +31 31 | +32 |-t"{set(['a', 'b']) - set(['a'])}" + 32 |+t"{set(['a', 'b']) - {'a'} }" +33 33 | t"{ set(['a', 'b']) - set(['a']) }" +34 34 | t"a {set(['a', 'b']) - set(['a'])} b" +35 35 | t"a { set(['a', 'b']) - set(['a']) } b" + +C405.py:33:5: C405 [*] Unnecessary list literal (rewrite as a set literal) + | +32 | t"{set(['a', 'b']) - set(['a'])}" +33 | t"{ set(['a', 'b']) - set(['a']) }" + | ^^^^^^^^^^^^^^^ C405 +34 | t"a {set(['a', 'b']) - set(['a'])} b" +35 | t"a { set(['a', 'b']) - set(['a']) } b" + | + = help: Rewrite as a set literal + +ℹ Unsafe fix +30 30 | t'{set(["a", "b"])}' +31 31 | +32 32 | t"{set(['a', 'b']) - set(['a'])}" +33 |-t"{ set(['a', 'b']) - set(['a']) }" + 33 |+t"{ {'a', 'b'} - set(['a']) }" +34 34 | t"a {set(['a', 'b']) - set(['a'])} b" +35 35 | t"a { set(['a', 'b']) - set(['a']) } b" + +C405.py:33:23: C405 [*] Unnecessary list literal (rewrite as a set literal) + | +32 | t"{set(['a', 'b']) - set(['a'])}" +33 | t"{ set(['a', 'b']) - set(['a']) }" + | ^^^^^^^^^^ C405 +34 | t"a {set(['a', 'b']) - set(['a'])} b" +35 | t"a { set(['a', 'b']) - set(['a']) } b" + | + = help: Rewrite as a set literal + +ℹ Unsafe fix +30 30 | t'{set(["a", "b"])}' +31 31 | +32 32 | t"{set(['a', 'b']) - set(['a'])}" +33 |-t"{ set(['a', 'b']) - set(['a']) }" + 33 |+t"{ set(['a', 'b']) - {'a'} }" +34 34 | t"a {set(['a', 'b']) - set(['a'])} b" +35 35 | t"a { set(['a', 'b']) - set(['a']) } b" + +C405.py:34:6: C405 [*] Unnecessary list literal (rewrite as a set literal) + | +32 | t"{set(['a', 'b']) - set(['a'])}" +33 | t"{ set(['a', 'b']) - set(['a']) }" +34 | t"a {set(['a', 'b']) - set(['a'])} b" + | ^^^^^^^^^^^^^^^ C405 +35 | t"a { set(['a', 'b']) - set(['a']) } b" + | + = help: Rewrite as a set literal + +ℹ Unsafe fix +31 31 | +32 32 | t"{set(['a', 'b']) - set(['a'])}" +33 33 | t"{ set(['a', 'b']) - set(['a']) }" +34 |-t"a {set(['a', 'b']) - set(['a'])} b" + 34 |+t"a { {'a', 'b'} - set(['a'])} b" +35 35 | t"a { set(['a', 'b']) - set(['a']) } b" + +C405.py:34:24: C405 [*] Unnecessary list literal (rewrite as a set literal) + | +32 | t"{set(['a', 'b']) - set(['a'])}" +33 | t"{ set(['a', 'b']) - set(['a']) }" +34 | t"a {set(['a', 'b']) - set(['a'])} b" + | ^^^^^^^^^^ C405 +35 | t"a { set(['a', 'b']) - set(['a']) } b" + | + = help: Rewrite as a set literal + +ℹ Unsafe fix +31 31 | +32 32 | t"{set(['a', 'b']) - set(['a'])}" +33 33 | t"{ set(['a', 'b']) - set(['a']) }" +34 |-t"a {set(['a', 'b']) - set(['a'])} b" + 34 |+t"a {set(['a', 'b']) - {'a'} } b" +35 35 | t"a { set(['a', 'b']) - set(['a']) } b" + +C405.py:35:7: C405 [*] Unnecessary list literal (rewrite as a set literal) + | +33 | t"{ set(['a', 'b']) - set(['a']) }" +34 | t"a {set(['a', 'b']) - set(['a'])} b" +35 | t"a { set(['a', 'b']) - set(['a']) } b" + | ^^^^^^^^^^^^^^^ C405 + | + = help: Rewrite as a set literal + +ℹ Unsafe fix +32 32 | t"{set(['a', 'b']) - set(['a'])}" +33 33 | t"{ set(['a', 'b']) - set(['a']) }" +34 34 | t"a {set(['a', 'b']) - set(['a'])} b" +35 |-t"a { set(['a', 'b']) - set(['a']) } b" + 35 |+t"a { {'a', 'b'} - set(['a']) } b" + +C405.py:35:25: C405 [*] Unnecessary list literal (rewrite as a set literal) + | +33 | t"{ set(['a', 'b']) - set(['a']) }" +34 | t"a {set(['a', 'b']) - set(['a'])} b" +35 | t"a { set(['a', 'b']) - set(['a']) } b" + | ^^^^^^^^^^ C405 + | + = help: Rewrite as a set literal + +ℹ Unsafe fix +32 32 | t"{set(['a', 'b']) - set(['a'])}" +33 33 | t"{ set(['a', 'b']) - set(['a']) }" +34 34 | t"a {set(['a', 'b']) - set(['a'])} b" +35 |-t"a { set(['a', 'b']) - set(['a']) } b" + 35 |+t"a { set(['a', 'b']) - {'a'} } b" diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C408_C408.py.snap b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C408_C408.py.snap index 973aa65bc4..e9519927ac 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C408_C408.py.snap +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C408_C408.py.snap @@ -354,6 +354,8 @@ C408.py:28:1: C408 [*] Unnecessary `tuple()` call (rewrite as a literal) 28 | / tuple( # comment 29 | | ) | |_^ C408 +30 | +31 | t"{dict(x='y')}" | = help: Rewrite as a literal @@ -364,3 +366,235 @@ C408.py:28:1: C408 [*] Unnecessary `tuple()` call (rewrite as a literal) 28 |-tuple( # comment 28 |+( # comment 29 29 | ) +30 30 | +31 31 | t"{dict(x='y')}" + +C408.py:31:4: C408 [*] Unnecessary `dict()` call (rewrite as a literal) + | +29 | ) +30 | +31 | t"{dict(x='y')}" + | ^^^^^^^^^^^ C408 +32 | t'{dict(x="y")}' +33 | t"{dict()}" + | + = help: Rewrite as a literal + +ℹ Unsafe fix +28 28 | tuple( # comment +29 29 | ) +30 30 | +31 |-t"{dict(x='y')}" + 31 |+t"{ {'x': 'y'} }" +32 32 | t'{dict(x="y")}' +33 33 | t"{dict()}" +34 34 | t"a {dict()} b" + +C408.py:32:4: C408 [*] Unnecessary `dict()` call (rewrite as a literal) + | +31 | t"{dict(x='y')}" +32 | t'{dict(x="y")}' + | ^^^^^^^^^^^ C408 +33 | t"{dict()}" +34 | t"a {dict()} b" + | + = help: Rewrite as a literal + +ℹ Unsafe fix +29 29 | ) +30 30 | +31 31 | t"{dict(x='y')}" +32 |-t'{dict(x="y")}' + 32 |+t'{ {"x": "y"} }' +33 33 | t"{dict()}" +34 34 | t"a {dict()} b" +35 35 | + +C408.py:33:4: C408 [*] Unnecessary `dict()` call (rewrite as a literal) + | +31 | t"{dict(x='y')}" +32 | t'{dict(x="y")}' +33 | t"{dict()}" + | ^^^^^^ C408 +34 | t"a {dict()} b" + | + = help: Rewrite as a literal + +ℹ Unsafe fix +30 30 | +31 31 | t"{dict(x='y')}" +32 32 | t'{dict(x="y")}' +33 |-t"{dict()}" + 33 |+t"{ {} }" +34 34 | t"a {dict()} b" +35 35 | +36 36 | t"{dict(x='y') | dict(y='z')}" + +C408.py:34:6: C408 [*] Unnecessary `dict()` call (rewrite as a literal) + | +32 | t'{dict(x="y")}' +33 | t"{dict()}" +34 | t"a {dict()} b" + | ^^^^^^ C408 +35 | +36 | t"{dict(x='y') | dict(y='z')}" + | + = help: Rewrite as a literal + +ℹ Unsafe fix +31 31 | t"{dict(x='y')}" +32 32 | t'{dict(x="y")}' +33 33 | t"{dict()}" +34 |-t"a {dict()} b" + 34 |+t"a { {} } b" +35 35 | +36 36 | t"{dict(x='y') | dict(y='z')}" +37 37 | t"{ dict(x='y') | dict(y='z') }" + +C408.py:36:4: C408 [*] Unnecessary `dict()` call (rewrite as a literal) + | +34 | t"a {dict()} b" +35 | +36 | t"{dict(x='y') | dict(y='z')}" + | ^^^^^^^^^^^ C408 +37 | t"{ dict(x='y') | dict(y='z') }" +38 | t"a {dict(x='y') | dict(y='z')} b" + | + = help: Rewrite as a literal + +ℹ Unsafe fix +33 33 | t"{dict()}" +34 34 | t"a {dict()} b" +35 35 | +36 |-t"{dict(x='y') | dict(y='z')}" + 36 |+t"{ {'x': 'y'} | dict(y='z')}" +37 37 | t"{ dict(x='y') | dict(y='z') }" +38 38 | t"a {dict(x='y') | dict(y='z')} b" +39 39 | t"a { dict(x='y') | dict(y='z') } b" + +C408.py:36:18: C408 [*] Unnecessary `dict()` call (rewrite as a literal) + | +34 | t"a {dict()} b" +35 | +36 | t"{dict(x='y') | dict(y='z')}" + | ^^^^^^^^^^^ C408 +37 | t"{ dict(x='y') | dict(y='z') }" +38 | t"a {dict(x='y') | dict(y='z')} b" + | + = help: Rewrite as a literal + +ℹ Unsafe fix +33 33 | t"{dict()}" +34 34 | t"a {dict()} b" +35 35 | +36 |-t"{dict(x='y') | dict(y='z')}" + 36 |+t"{dict(x='y') | {'y': 'z'} }" +37 37 | t"{ dict(x='y') | dict(y='z') }" +38 38 | t"a {dict(x='y') | dict(y='z')} b" +39 39 | t"a { dict(x='y') | dict(y='z') } b" + +C408.py:37:5: C408 [*] Unnecessary `dict()` call (rewrite as a literal) + | +36 | t"{dict(x='y') | dict(y='z')}" +37 | t"{ dict(x='y') | dict(y='z') }" + | ^^^^^^^^^^^ C408 +38 | t"a {dict(x='y') | dict(y='z')} b" +39 | t"a { dict(x='y') | dict(y='z') } b" + | + = help: Rewrite as a literal + +ℹ Unsafe fix +34 34 | t"a {dict()} b" +35 35 | +36 36 | t"{dict(x='y') | dict(y='z')}" +37 |-t"{ dict(x='y') | dict(y='z') }" + 37 |+t"{ {'x': 'y'} | dict(y='z') }" +38 38 | t"a {dict(x='y') | dict(y='z')} b" +39 39 | t"a { dict(x='y') | dict(y='z') } b" + +C408.py:37:19: C408 [*] Unnecessary `dict()` call (rewrite as a literal) + | +36 | t"{dict(x='y') | dict(y='z')}" +37 | t"{ dict(x='y') | dict(y='z') }" + | ^^^^^^^^^^^ C408 +38 | t"a {dict(x='y') | dict(y='z')} b" +39 | t"a { dict(x='y') | dict(y='z') } b" + | + = help: Rewrite as a literal + +ℹ Unsafe fix +34 34 | t"a {dict()} b" +35 35 | +36 36 | t"{dict(x='y') | dict(y='z')}" +37 |-t"{ dict(x='y') | dict(y='z') }" + 37 |+t"{ dict(x='y') | {'y': 'z'} }" +38 38 | t"a {dict(x='y') | dict(y='z')} b" +39 39 | t"a { dict(x='y') | dict(y='z') } b" + +C408.py:38:6: C408 [*] Unnecessary `dict()` call (rewrite as a literal) + | +36 | t"{dict(x='y') | dict(y='z')}" +37 | t"{ dict(x='y') | dict(y='z') }" +38 | t"a {dict(x='y') | dict(y='z')} b" + | ^^^^^^^^^^^ C408 +39 | t"a { dict(x='y') | dict(y='z') } b" + | + = help: Rewrite as a literal + +ℹ Unsafe fix +35 35 | +36 36 | t"{dict(x='y') | dict(y='z')}" +37 37 | t"{ dict(x='y') | dict(y='z') }" +38 |-t"a {dict(x='y') | dict(y='z')} b" + 38 |+t"a { {'x': 'y'} | dict(y='z')} b" +39 39 | t"a { dict(x='y') | dict(y='z') } b" + +C408.py:38:20: C408 [*] Unnecessary `dict()` call (rewrite as a literal) + | +36 | t"{dict(x='y') | dict(y='z')}" +37 | t"{ dict(x='y') | dict(y='z') }" +38 | t"a {dict(x='y') | dict(y='z')} b" + | ^^^^^^^^^^^ C408 +39 | t"a { dict(x='y') | dict(y='z') } b" + | + = help: Rewrite as a literal + +ℹ Unsafe fix +35 35 | +36 36 | t"{dict(x='y') | dict(y='z')}" +37 37 | t"{ dict(x='y') | dict(y='z') }" +38 |-t"a {dict(x='y') | dict(y='z')} b" + 38 |+t"a {dict(x='y') | {'y': 'z'} } b" +39 39 | t"a { dict(x='y') | dict(y='z') } b" + +C408.py:39:7: C408 [*] Unnecessary `dict()` call (rewrite as a literal) + | +37 | t"{ dict(x='y') | dict(y='z') }" +38 | t"a {dict(x='y') | dict(y='z')} b" +39 | t"a { dict(x='y') | dict(y='z') } b" + | ^^^^^^^^^^^ C408 + | + = help: Rewrite as a literal + +ℹ Unsafe fix +36 36 | t"{dict(x='y') | dict(y='z')}" +37 37 | t"{ dict(x='y') | dict(y='z') }" +38 38 | t"a {dict(x='y') | dict(y='z')} b" +39 |-t"a { dict(x='y') | dict(y='z') } b" + 39 |+t"a { {'x': 'y'} | dict(y='z') } b" + +C408.py:39:21: C408 [*] Unnecessary `dict()` call (rewrite as a literal) + | +37 | t"{ dict(x='y') | dict(y='z') }" +38 | t"a {dict(x='y') | dict(y='z')} b" +39 | t"a { dict(x='y') | dict(y='z') } b" + | ^^^^^^^^^^^ C408 + | + = help: Rewrite as a literal + +ℹ Unsafe fix +36 36 | t"{dict(x='y') | dict(y='z')}" +37 37 | t"{ dict(x='y') | dict(y='z') }" +38 38 | t"a {dict(x='y') | dict(y='z')} b" +39 |-t"a { dict(x='y') | dict(y='z') } b" + 39 |+t"a { dict(x='y') | {'y': 'z'} } b" diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C408_C408.py_allow_dict_calls_with_keyword_arguments.snap b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C408_C408.py_allow_dict_calls_with_keyword_arguments.snap index 17911683f1..9e7e51df20 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C408_C408.py_allow_dict_calls_with_keyword_arguments.snap +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C408_C408.py_allow_dict_calls_with_keyword_arguments.snap @@ -129,6 +129,8 @@ C408.py:28:1: C408 [*] Unnecessary `tuple()` call (rewrite as a literal) 28 | / tuple( # comment 29 | | ) | |_^ C408 +30 | +31 | t"{dict(x='y')}" | = help: Rewrite as a literal @@ -139,3 +141,46 @@ C408.py:28:1: C408 [*] Unnecessary `tuple()` call (rewrite as a literal) 28 |-tuple( # comment 28 |+( # comment 29 29 | ) +30 30 | +31 31 | t"{dict(x='y')}" + +C408.py:33:4: C408 [*] Unnecessary `dict()` call (rewrite as a literal) + | +31 | t"{dict(x='y')}" +32 | t'{dict(x="y")}' +33 | t"{dict()}" + | ^^^^^^ C408 +34 | t"a {dict()} b" + | + = help: Rewrite as a literal + +ℹ Unsafe fix +30 30 | +31 31 | t"{dict(x='y')}" +32 32 | t'{dict(x="y")}' +33 |-t"{dict()}" + 33 |+t"{ {} }" +34 34 | t"a {dict()} b" +35 35 | +36 36 | t"{dict(x='y') | dict(y='z')}" + +C408.py:34:6: C408 [*] Unnecessary `dict()` call (rewrite as a literal) + | +32 | t'{dict(x="y")}' +33 | t"{dict()}" +34 | t"a {dict()} b" + | ^^^^^^ C408 +35 | +36 | t"{dict(x='y') | dict(y='z')}" + | + = help: Rewrite as a literal + +ℹ Unsafe fix +31 31 | t"{dict(x='y')}" +32 32 | t'{dict(x="y")}' +33 33 | t"{dict()}" +34 |-t"a {dict()} b" + 34 |+t"a { {} } b" +35 35 | +36 36 | t"{dict(x='y') | dict(y='z')}" +37 37 | t"{ dict(x='y') | dict(y='z') }" diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C417_C417.py.snap b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C417_C417.py.snap index 6f7c3e0881..3a8148e299 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C417_C417.py.snap +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/snapshots/ruff_linter__rules__flake8_comprehensions__tests__C417_C417.py.snap @@ -325,3 +325,38 @@ C417.py:49:1: C417 [*] Unnecessary `map()` usage (rewrite using a generator expr 50 50 | 51 51 | # See https://github.com/astral-sh/ruff/issues/14808 52 52 | # The following should be Ok since + +C417.py:75:8: C417 [*] Unnecessary `map()` usage (rewrite using a set comprehension) + | +74 | # When inside t-string, then the fix should be surrounded by whitespace +75 | _ = t"{set(map(lambda x: x % 2 == 0, nums))}" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C417 +76 | _ = t"{dict(map(lambda v: (v, v**2), nums))}" + | + = help: Replace `map()` with a set comprehension + +ℹ Unsafe fix +72 72 | list(map(lambda x, y: x, [(1, 2), (3, 4)])) +73 73 | +74 74 | # When inside t-string, then the fix should be surrounded by whitespace +75 |-_ = t"{set(map(lambda x: x % 2 == 0, nums))}" + 75 |+_ = t"{ {x % 2 == 0 for x in nums} }" +76 76 | _ = t"{dict(map(lambda v: (v, v**2), nums))}" +77 77 | + +C417.py:76:8: C417 [*] Unnecessary `map()` usage (rewrite using a dict comprehension) + | +74 | # When inside t-string, then the fix should be surrounded by whitespace +75 | _ = t"{set(map(lambda x: x % 2 == 0, nums))}" +76 | _ = t"{dict(map(lambda v: (v, v**2), nums))}" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C417 + | + = help: Replace `map()` with a dict comprehension + +ℹ Unsafe fix +73 73 | +74 74 | # When inside t-string, then the fix should be surrounded by whitespace +75 75 | _ = t"{set(map(lambda x: x % 2 == 0, nums))}" +76 |-_ = t"{dict(map(lambda v: (v, v**2), nums))}" + 76 |+_ = t"{ {v: v**2 for v in nums} }" +77 77 | diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index 043d98cff5..6576b74437 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -1944,6 +1944,11 @@ impl<'a> SemanticModel<'a> { self.flags.intersects(SemanticModelFlags::T_STRING) } + /// Return `true` if the model is in an f-string or t-string. + pub const fn in_interpolated_string(&self) -> bool { + self.in_f_string() || self.in_t_string() + } + /// Return `true` if the model is in an f-string replacement field. pub const fn in_interpolated_string_replacement_field(&self) -> bool { self.flags