Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,12 @@ def list():
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"

# https://github.com/astral-sh/ruff/issues/16234
# Python normalizes identifiers to NFKC, but does not normalize string literals, so the fix has to
# normalize the keyword name to preserve the dictionary key at runtime. The character "ℼ" normalizes
# to "π", and "ſ" normalizes to "s".
dict(ℼ=3.14)
dict(ſ=1)
dict(𝕒=1, b=2)
dict(a=1, b=2) # already NFKC-normalized: unchanged
8 changes: 7 additions & 1 deletion crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use ruff_python_ast::{self as ast, Expr, ExprCall};
use ruff_python_codegen::Stylist;
use ruff_python_semantic::SemanticModel;
use ruff_text_size::{Ranged, TextRange};
use unicode_normalization::UnicodeNormalization;

use crate::Locator;
use crate::cst::helpers::{negate, space};
Expand Down Expand Up @@ -241,14 +242,19 @@ pub(crate) fn fix_unnecessary_collection_call(
.unwrap_or(stylist.quote());

// Quote each argument.
//
// Python normalizes identifiers to NFKC, but string literals are not normalized. Emitting the
// raw source text of a keyword argument would change the dictionary key at runtime, so the
// name has to be normalized. See https://github.com/astral-sh/ruff/issues/16234.
for arg in &call.args {
let quoted = format!(
"{}{}{}",
quote,
arg.keyword
.as_ref()
.expect("Expected dictionary argument to be kwarg")
.value,
.value
.nfkc(),
quote,
);
arena.push(quoted);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -529,12 +529,15 @@ C408 [*] Unnecessary `dict()` call (rewrite as a literal)
38 | t"a {dict(x='y') | dict(y='z')} b"
39 | t"a { dict(x='y') | dict(y='z') } b"
| ^^^^^^^^^^^
40 |
41 | # https://github.com/astral-sh/ruff/issues/16234
|
help: Rewrite as a literal
|
38 | t"a {dict(x='y') | dict(y='z')} b"
- t"a { dict(x='y') | dict(y='z') } b"
39 + t"a { {'x': 'y'} | dict(y='z') } b"
40 |
|
note: This is an unsafe fix and may change runtime behavior

Expand All @@ -545,11 +548,86 @@ C408 [*] Unnecessary `dict()` call (rewrite as a literal)
38 | t"a {dict(x='y') | dict(y='z')} b"
39 | t"a { dict(x='y') | dict(y='z') } b"
| ^^^^^^^^^^^
40 |
41 | # https://github.com/astral-sh/ruff/issues/16234
|
help: Rewrite as a literal
|
38 | t"a {dict(x='y') | dict(y='z')} b"
- t"a { dict(x='y') | dict(y='z') } b"
39 + t"a { dict(x='y') | {'y': 'z'} } b"
40 |
|
note: This is an unsafe fix and may change runtime behavior

C408 [*] Unnecessary `dict()` call (rewrite as a literal)
--> C408.py:45:1
|
43 | # normalize the keyword name to preserve the dictionary key at runtime. The character "ℼ" normalizes
44 | # to "π", and "ſ" normalizes to "s".
45 | dict(ℼ=3.14)
| ^^^^^^^^^^^^
46 | dict(ſ=1)
47 | dict(𝕒=1, b=2)
|
help: Rewrite as a literal
|
44 | # to "π", and "ſ" normalizes to "s".
- dict(ℼ=3.14)
45 + {"π": 3.14}
46 | dict(ſ=1)
|
note: This is an unsafe fix and may change runtime behavior

C408 [*] Unnecessary `dict()` call (rewrite as a literal)
--> C408.py:46:1
|
44 | # to "π", and "ſ" normalizes to "s".
45 | dict(ℼ=3.14)
46 | dict(ſ=1)
| ^^^^^^^^^
47 | dict(𝕒=1, b=2)
48 | dict(a=1, b=2) # already NFKC-normalized: unchanged
|
help: Rewrite as a literal
|
45 | dict(ℼ=3.14)
- dict(ſ=1)
46 + {"s": 1}
47 | dict(𝕒=1, b=2)
|
note: This is an unsafe fix and may change runtime behavior

C408 [*] Unnecessary `dict()` call (rewrite as a literal)
--> C408.py:47:1
|
45 | dict(ℼ=3.14)
46 | dict(ſ=1)
47 | dict(𝕒=1, b=2)
| ^^^^^^^^^^^^^^
48 | dict(a=1, b=2) # already NFKC-normalized: unchanged
|
help: Rewrite as a literal
|
46 | dict(ſ=1)
- dict(𝕒=1, b=2)
47 + {"a": 1, "b": 2}
48 | dict(a=1, b=2) # already NFKC-normalized: unchanged
|
note: This is an unsafe fix and may change runtime behavior

C408 [*] Unnecessary `dict()` call (rewrite as a literal)
--> C408.py:48:1
|
46 | dict(ſ=1)
47 | dict(𝕒=1, b=2)
48 | dict(a=1, b=2) # already NFKC-normalized: unchanged
| ^^^^^^^^^^^^^^
|
help: Rewrite as a literal
|
47 | dict(𝕒=1, b=2)
- dict(a=1, b=2) # already NFKC-normalized: unchanged
48 + {"a": 1, "b": 2} # already NFKC-normalized: unchanged
|
note: This is an unsafe fix and may change runtime behavior
Loading