Fix patchedast aborting on ''' inside a double-quoted f-string#844
Open
marlon-costa-dc wants to merge 2 commits into
Open
Fix patchedast aborting on ''' inside a double-quoted f-string#844marlon-costa-dc wants to merge 2 commits into
marlon-costa-dc wants to merge 2 commits into
Conversation
added 2 commits
July 22, 2026 10:15
end_quote_char() picked the closing delimiter by max(len(quote)) over every
quote char found anywhere in the f-string body, so a ''' sequence inside a
double-quoted f-string (e.g. f"cfg = '''\n{value}") was selected as the
delimiter. That produced a reconstructed token consume_joined_string() could
not locate, raising `ValueError: substring not found` -> MismatchedTokenError,
which aborts rename/inline for the entire module.
Pick the delimiter the f-string literally ends with (QUOTE_CHARS is ordered
longest-first, so """/''' win over "/' correctly) so the closing quote matches
the opening one regardless of quote characters appearing in the body.
Adds a regression test; full patchedast, rename and inline suites stay green.
There was a problem hiding this comment.
Pull request overview
Fixes patchedast f-string reconstruction when the f-string body contains quote sequences (notably ''') that previously confused _JoinedStr.end_quote_char(), causing consume_joined_string() failures and aborting refactorings for the whole module.
Changes:
- Update
_JoinedStr.end_quote_char()to select the closing delimiter based on what the source literally ends with (rather than the longest quote found in the body). - Add a regression test covering a double-quoted f-string containing
'''inside its body. - Document the fix in
CHANGELOG.md.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
rope/refactor/patchedast.py |
Fixes f-string closing delimiter detection to avoid mismatches caused by quote sequences in the f-string body. |
ropetest/refactor/patchedasttest.py |
Adds a regression test proving the bug and preventing future regressions. |
CHANGELOG.md |
Notes the bug fix and its impact on refactorings. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+392
to
+395
| for quote_char in QUOTE_CHARS: | ||
| if self.source.source[end - len(quote_char) : end] == quote_char: | ||
| return self.source[end - len(quote_char) : end] | ||
| return self.source[end - 1 : end] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
patchedast._JoinedStr.end_quote_char()selects the closing delimiter bymax(len(quote))over every quote character found anywhere in the f-string body. When a'''sequence appears inside a double-quoted f-string, e.g.:f"cfg = '''\n{value}"the
'''in the body is picked as the delimiter instead of the actual closing". That produces a reconstructed token thatconsume_joined_string()cannot locate, raising:which surfaces as
MismatchedTokenErrorand aborts rename / inline for the entire module — a single such f-string anywhere in a project breaks refactoring of that file.Root cause
end_quote_char()chose the quote by length precedence rather than by which delimiter the f-string literally ends with. The opening quote (start_quote_char) was already correct; the closing detection was not symmetric.Fix
Pick the delimiter the f-string literally ends with.
QUOTE_CHARSis ordered longest-first (""",''',",'), so triple-quoted strings are still matched before single-character quotes, and the closing quote now always matches the opening one regardless of quote characters in the body.Reproducer (before the fix)
Testing
test_handling_format_strings_with_triple_quote_in_bodyinropetest/refactor/patchedasttest.py(red before, green after).ropetest/refactor/= 944 passed, 7 skipped, 1 xfailed.patchedasttest.py= 141 passed, 6 skipped.Checklist