Skip to content

Conversation

@danparizher
Copy link
Contributor

Summary

Fix FLY002 to bail on transformations when mixing raw and non-raw strings. Mixing them can cause syntax errors, null bytes, or incorrect carriage-return behavior.

Closes #21082

Problem Analysis

When the first string in a join is raw (r"") and later strings are not:

  1. The code applies the raw flag to the entire result
  2. Non-raw string escape sequences are not processed
  3. This leads to:
    • Syntax errors (e.g., "".join((r"", '"'))r""")
    • Null bytes (e.g., "".join((r"", "\0")))
    • Behavior changes (e.g., carriage returns or unexpected characters)

Approach

Added a check in build_fstring to verify all strings have the same raw status. If not, the transformation is skipped:

  1. Collect raw status for all string literals in the join
  2. Return None early if raw statuses differ
  3. Otherwise, proceed with the transformation

Testing

Added tests covering:

  • nok14-nok18: Mixed raw/non-raw (not transformed)
  • ok7, ok8: All raw (transformed)
  • ok9, ok10: All non-raw (transformed)

Ran with problematic examples from the issue; transformations are skipped as expected.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 27, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@dscorbett
Copy link

Joining raw and non-raw strings is not the problem. The fix for FLY002 works fine for some combinations:

$ cat >fly002.py <<'# EOF'
print("".join(('"', r"@")))
print("".join((r"@", "!")))
# EOF

$ python fly002.py
"@
@!

$ ruff check --isolated fly002.py --select FLY002 --unsafe-fixes --fix
Found 2 errors (2 fixed, 0 remaining).

$ cat fly002.py
print('"')
print(r"!")

$ python fly002.py
"@
@!

Removing support for such combinations would need to be done in preview, but it would be more helpful to instead keep the support and fix the bugs. In particular, that means that if the fix’s output string cannot be represented as a raw string, it should be represented as a non-raw string, even if the first input string is raw.

nok15 = "".join((r"", "\\")) # First is raw, second has backslash - would break syntax
nok16 = "".join((r"", "\0")) # First is raw, second has null byte - would introduce null bytes
nok17 = "".join((r"", "\r")) # First is raw, second has carriage return - would change behavior
nok18 = "".join((r"", "\\r")) # First is raw, second has carriage return escape - would change behavior

Choose a reason for hiding this comment

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

"\\r" does not have a carriage return escape: it has a backslash escape followed by a literal ⟨r⟩. FLY002 doesn’t change its behavior.

Refines the static_join_to_fstring Flynt rule to better handle cases where raw and non-raw strings are joined, avoiding unsafe raw string representations when content could break syntax or change behavior. Updates test fixtures and snapshots to reflect improved diagnostics and suggestions for problematic joins.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Raw strings break the FLY002 fix

2 participants