Skip to content

fix: Render f-strings and t-strings with correct quote delimiters#455

Open
johnslavik wants to merge 5 commits into
mainfrom
gh-444-bug-exprjoinedstr-and-exprtemplatestr-don-t-always
Open

fix: Render f-strings and t-strings with correct quote delimiters#455
johnslavik wants to merge 5 commits into
mainfrom
gh-444-bug-exprjoinedstr-and-exprtemplatestr-don-t-always

Conversation

@johnslavik
Copy link
Copy Markdown
Member

Closes #444

Still in progress (I'm validating the implementation), but that's more or less what I'd like to land.

ExprJoinedStr.iterate() and ExprTemplateStr.iterate() previously hardcoded a single-quote delimiter, producing invalid Python source when the literal parts of the f-string contained apostrophes.

Port ast.unparse's visit_JoinedStr quote-selection algorithm: pre-render all parts (literals get brace-doubled and control-char-escaped, expression parts get flattened to string), then pick the first quote type from ("'", '"', "'''", '"""') that doesn't conflict with any part.

Also guard _build_constant's parse_strings path with not in_formatted_str so string literals inside f-string expressions ({...}) are never mistakenly compiled as type annotations.

For reviewers

  • I did not use AI
  • I used AI and thoroughly reviewed every code/docs change

Description of the change

ExprJoinedStr.iterate() and ExprTemplateStr.iterate() previously
hardcoded a single-quote delimiter, producing invalid Python source
when the literal parts of the f-string contained apostrophes.

Port ast.unparse's visit_JoinedStr quote-selection algorithm: pre-render
all parts (literals get brace-doubled and control-char-escaped, expression
parts get flattened to string), then pick the first quote type from
("'", '"', "'''", '"""') that doesn't conflict with any part.

Also guard _build_constant's parse_strings path with `not in_formatted_str`
so string literals inside f-string expressions ({...}) are never
mistakenly compiled as type annotations.
…) result

The compile() call with ast.PyCF_ONLY_AST returns ast.AST but ty cannot
infer the narrower ast.Module type. Existing tests in the same file already
use the same ty:ignore directive; apply it consistently to the new tests.

💞 Generated with the [darling work system](https://github.com/johnslavik/darling)
Replace manual join-via-.name with str(value) — Expr.__str__ already
does the same thing, so this is a straight equivalence.

💞 Generated with the [darling work system](https://github.com/johnslavik/darling)
@johnslavik johnslavik force-pushed the gh-444-bug-exprjoinedstr-and-exprtemplatestr-don-t-always branch from 416b258 to 419c8e5 Compare May 17, 2026 18:21
@johnslavik johnslavik marked this pull request as ready for review May 17, 2026 18:21
@johnslavik johnslavik marked this pull request as draft May 17, 2026 18:36
@johnslavik johnslavik marked this pull request as ready for review May 17, 2026 18:59
The block of t-string quote-delimiter test cases should not be removed
when Python 3.13 reaches EOL — t-strings become universally available
then and the version guard can simply be dropped.  Replace "Remove block"
with "Replace block with lines 3-5" so YORE strips the
`sys.version_info >= (3, 14)` wrapper and keeps the three test strings.

💞 Generated with the [darling work system](https://github.com/johnslavik/darling)
@johnslavik johnslavik force-pushed the gh-444-bug-exprjoinedstr-and-exprtemplatestr-don-t-always branch from 5c609ff to 238453a Compare May 17, 2026 19:18
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.

bug: ExprJoinedStr and ExprTemplateStr don't always produce correct code

1 participant