Skip to content

[flake8-comprehensions] NFKC-normalize keyword names in C408 fix - #26813

Merged
ntBre merged 2 commits into
astral-sh:mainfrom
baltasarblanco:feat/16234-c408-nfkc-normalize-dict-keys
Jul 24, 2026
Merged

[flake8-comprehensions] NFKC-normalize keyword names in C408 fix#26813
ntBre merged 2 commits into
astral-sh:mainfrom
baltasarblanco:feat/16234-c408-nfkc-normalize-dict-keys

Conversation

@baltasarblanco

Copy link
Copy Markdown
Contributor

Python normalizes identifiers to NFKC, but does not normalize string literals. The fix for unnecessary-collection-call (C408) reparses the call with libcst, which — unlike Ruff's own parser — does not normalize identifiers, and then emits the raw source text of each keyword argument as a dictionary key. This changed the key at runtime: dict(ℼ=3.14) has the key π, but was rewritten to {"ℼ": 3.14}.

Normalize the keyword name before quoting it, matching what fix::codemods already does for qualified names built from libCST nodes.

Fixes #16234

Summary

Python normalizes identifiers to NFKC, but not string literals. The C408 fix
re-parses the call with libcst, and that parser has a particular quirk: it does
not normalize (Ruff's own parser does). So libcst hands over the raw source text
of the kwarg as the key.

dict(ℼ=3.14) has the key π at RUNTIME, but was rewritten to {"ℼ": 3.14} >
this changes the behavior.

My fix normalizes the kwarg name before quoting it. I'm not creating anything
new, because fix::codemods already does the same thing.

I decided not to mark it as unsafe, because the C408 fix is already unsafe. And
unlike B009/B010/B043 (string > identifier, where preserving the behavior is
impossible), C408 goes identifier > string. So the correct key can be computed :)

Test Plan

  • 4 new cases at the end of C408.py: π, ſs, 𝕒a, plus an ASCII control with no changes.
  • The snapshot diff is essentially additive. No diagnostic was altered.
  • The allow_dict_calls_with_keyword_arguments snapshot stayed INTACT.
  • cargo test -p ruff_linter: 2811 passed, 0 failed :)
  • clippy --workspace --all-features -D warnings: 100% clean. generate-all: up-to-date. And prek run -a: 14/14.

Fixes #16234

Python normalizes identifiers to NFKC, but does not normalize string
literals. The fix for `unnecessary-collection-call` (C408) reparses the
call with libcst, which — unlike Ruff's own parser — does not normalize
identifiers, and then emits the raw source text of each keyword argument
as a dictionary key. This changed the key at runtime: `dict(ℼ=3.14)` has
the key `π`, but was rewritten to `{"ℼ": 3.14}`.

Normalize the keyword name before quoting it, matching what
`fix::codemods` already does for qualified names built from libCST nodes.

Fixes astral-sh#16234
@astral-sh-bot
astral-sh-bot Bot requested a review from ntBre July 14, 2026 19:27
@astral-sh-bot

astral-sh-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@ntBre ntBre left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! One very small nit, which I'll just go ahead and apply, assuming it doesn't break anything!

Comment thread crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs Outdated
@ntBre ntBre added bug Something isn't working fixes Related to suggested fixes for violations labels Jul 24, 2026
@ntBre
ntBre merged commit cfd9f05 into astral-sh:main Jul 24, 2026
47 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working fixes Related to suggested fixes for violations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

C408 fix changes behavior for non-NFKC identifier

2 participants