[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.
This commit is contained in:
Dylan 2025-06-19 16:23:46 -05:00 committed by GitHub
parent 10a1d9f01e
commit ce0a32aadb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 919 additions and 23 deletions

View file

@ -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))

View file

@ -35,3 +35,15 @@ s = set( # outer set comment
# Test trailing comma case
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'])}"

View file

@ -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"

View file

@ -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"

View file

@ -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))}"

View file

@ -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,22 +346,28 @@ 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<Quote> {
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<Quote> {
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())?;
.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.
pub(crate) fn compute_source_row(&self, offset: TextSize) -> SourceRow {

View file

@ -236,7 +236,9 @@ pub(crate) fn fix_unnecessary_collection_call(
// below.
let mut arena: Vec<String> = 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} ");
}
}

View file

@ -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.

View file

@ -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 |

View file

@ -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"

View file

@ -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"

View file

@ -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') }"

View file

@ -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 |

View file

@ -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