GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Escaped quotes cause source comments to leak into debug f-string output and t-string metadata · Issue #154711 · python/cpython

Escaped quotes cause source comments to leak into debug f-string output and t-string metadata #154711

New issue
Copy link
New issue
Copy link
Open
Open
Escaped quotes cause source comments to leak into debug f-string output and t-string metadata#154711
Copy link
Labels
interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)topic-parsertype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@kid-lxy
kid-lxy
opened on Jul 26, 2026
Issue body actions

Bug report

Bug description:

When an f-string or t-string replacement expression contains a backslash-escaped quote, a following source comment can be copied into runtime output or t-string metadata.

Observed effects:

  • A debug f-string includes the source comment in the resulting str.
  • A debug t-string includes the comment in Template.strings.
  • A debug t-string includes both the debug = and the comment inInterpolation.expression.
  • A non-debug t-string also retains the comment inInterpolation.expression.

The replacement expression itself evaluates to the correct value. The problemappears to be in reconstruction of the replacement-field source text.

Reproducer

f_result = f"{'\'' = # c
}"

t_debug = t"{'a\'b' = # c
}"
t_plain = t"{'a\'b' # c
}"

print("debug f-string result:", repr(f_result))
print("\ndebug t-string strings:", repr(t_debug.strings))
print("\ndebug t-string expression:", repr(t_debug.interpolations[0].expression))
print("\nnon-debug t-string expression:", repr(t_plain.interpolations[0].expression))

Actual behavior

On CPython main commit b86a41c, the output contains the source text # c in all four observed values.

debug f-string result: '\'\\\'\' = # c\n"\'"'

debug t-string strings: ("'a\\'b' = # c\n", '')

debug t-string expression: "'a\\'b' = # c"

non-debug t-string expression: "'a\\'b' # c"

Possible cause

The behavior appears to be caused by inconsistent escaped-character handling between two scans in _PyLexer_set_ftstring_expr() in Parser/lexer/string.c.

The initial scan that detects a comment outside a string literal explicitly skips a backslash and the character following it:

if (ch == '\\') {
    i++;
    continue;
}

However, after a comment has been detected, the subsequent reconstruction pass that removes comments appears not to perform the equivalent skip. It changes in_string whenever it encounters a quote:

if (ch == '"' || ch == '\'') {
    if (!in_string) {
        in_string = 1;
        quote_char = ch;
    }
    else if (ch == quote_char) {
        in_string = 0;
    }
}
else if (ch == '#' && !in_string) {
    /* skip the comment */
}

For:

'a\'b' = # c

the second pass may interpret the escaped quote as the end of the string and the real closing quote as the beginning of another string:

'       begin string
\       treated as an ordinary character
'       incorrectly treated as end of string
b
'       incorrectly treated as beginning of another string
#       incorrectly considered to be inside a string

Consequently, the # is not recognized by the comment-removal branch and the comment is copied into the reconstructed text.
This also explains why the same input pattern affects both f-strings and t-strings: both use the same replacement-expression reconstruction logic.
This is a proposed root cause based on the observed behavior and source review, not a confirmed diagnosis.

CPython versions tested on:

CPython main branch, 3.16

Operating systems tested on:

Linux

Linked PRs

  • [3.13] gh-154711: Strip comments after escaped quotes in f-string debug text #154914
  • gh-154711: Add tests for comments after escaped quotes in f-strings and t-strings #158220
  • [3.13] gh-154711: Strip comments after escaped quotes in f-string debug text #158310

Activity

  1. kid-lxy
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Jul 26, 2026
  2. picnixz
    added
    interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)
    on Jul 26, 2026
  3. picnixz commented on Jul 26, 2026

    @picnixz
    picnixz
    on Jul 26, 2026
    Member
    More actions

    Mmh I think it is actually deliberate? at least it seems consistent. Does it reproduce in older versions?

    EDIT: wait I think I missed something. It may be a legitimate issue.

  4. picnixz
    added
    pendingThe issue will be closed if no feedback is providedThe issue will be closed if no feedback is provided
    topic-parser
    and removed
    pendingThe issue will be closed if no feedback is providedThe issue will be closed if no feedback is provided
    on Jul 26, 2026
  5. tekinertekin commented on Jul 29, 2026

    @tekinertekin
    tekinertekin
    on Jul 29, 2026
    More actions

    @picnixz — about this being intentional: I don't think it can be. On 3.12 the comment is stripped correctly, both escaped and unescaped. I had to use the multiline form to compare, since the single-line version in the report is a SyntaxError before 3.14:

    a = f"""{'ab' = # c
    }"""
    print(repr(a))    # "'ab' = \n'ab'"
    
    b = f"""{'a\'b' = # c
    }"""
    print(repr(b))    # '\'a\\\'b\' = \n"a\'b"'
    
    c = f"""{'a\'b' =
    }"""
    print(repr(c))    # same as b
    print(b == c)     # True
    

    In the escaped case the result is byte-identical whether or not the comment is present, so stripping it is the established behaviour and the leak on main is new. I couldn't run 3.14/3.16 locally, so I haven't reproduced the reported output myself — that part is still the reporter's.

    The pointer to _PyLexer_set_ftstring_expr() in Parser/lexer/string.c looks right to me: the two scans disagree on backslash handling when reconstructing the replacement-field source. I'd like to work on this if it's free. Does that match your reading?

  6. dqqFA8995 commented on Jul 29, 2026

    @dqqFA8995
    dqqFA8995
    on Jul 29, 2026 via email
    More actions
    Thanks for checking against older releases—that comparison is helpful. If 3.12 produces identical output regardless of whether the comment is present, that strongly suggests comment stripping has been the intended behavior, and the behavior on main is likely a regression rather than a deliberate change. I also agree that _PyLexer_set_ftstring_expr() looks like the right place to investigate. The mismatch in escaped-character handling between the initial scan and the reconstruction pass would explain why the issue only appears with escaped quotes and why it affects both f-strings and t-strings through the shared reconstruction logic. It would be good to confirm that by bisecting to the commit that introduced the regression or by adding a regression test before changing the lexer logic. That would help ensure the fix matches the intended behavior and doesn't alter any newly introduced semantics.
    …
    On Wed, Jul 29, 2026 at 3:59 PM Tekin Ertekin ***@***.***> wrote: *tekinertekin* left a comment (python/cpython#154711) <#154711 (comment)> @picnixz <https://github.com/picnixz> — about this being intentional: I don't think it can be. On 3.12 the comment is stripped correctly, both escaped and unescaped. I had to use the multiline form to compare, since the single-line version in the report is a SyntaxError before 3.14: a = f"""{'ab' = # c }""" print(repr(a)) # "'ab' = \n'ab'" b = f"""{'a\'b' = # c }""" print(repr(b)) # '\'a\\\'b\' = \n"a\'b"' c = f"""{'a\'b' = }""" print(repr(c)) # same as b print(b == c) # True In the escaped case the result is byte-identical whether or not the comment is present, so stripping it is the established behaviour and the leak on main is new. I couldn't run 3.14/3.16 locally, so I haven't reproduced the reported output myself — that part is still the reporter's. The pointer to _PyLexer_set_ftstring_expr() in Parser/lexer/string.c looks right to me: the two scans disagree on backslash handling when reconstructing the replacement-field source. I'd like to work on this if it's free. Does that match your reading? — Reply to this email directly, view it on GitHub <#154711?email_source=notifications&email_token=CI4YCMB2E3E2QJTO66ERN6L5HINONA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJSGAZTCNJQGY42M4TFMFZW63VKON2WE43DOJUWEZLEUVSXMZLOOSWGM33PORSXEX3DNRUWG2Y#issuecomment-5120315069>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/CI4YCMAEADS2BXU3EQPQO735HINONAVCNFSNUABEKJSXA33TNF2G64TZHM4DCNJZHA4TMMJ3JFZXG5LFHM2DSNZZGUYTENJUGOQXMAQ> . Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS <https://github.com/notifications/mobile/ios/CI4YCMH6YATEB5S65EFOJOT5HINONA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJSGAZTCNJQGY42M4TFMFZW63VKON2WE43DOJUWEZLEUVSXMZLOOSVGM33PORSXEX3JN5ZQ> and Android <https://github.com/notifications/mobile/android/CI4YCMHQ3NSHLJU3PNLJFET5HINONA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJSGAZTCNJQGY42M4TFMFZW63VKON2WE43DOJUWEZLEUVSXMZLOOSXGM33PORSXEX3BNZSHE33JMQ>. Download it today! You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
  7. ArneshBanerjee
    added a commit that references this issue on Sep 26, 2026

    [3.13] pythongh-154711: Strip comments after escaped quotes in f-stri…

    2877218
  8. pablogsal
    added a commit that references this issue on Sep 29, 2026

    [3.13] gh-154711: Strip comments after escaped quotes in f-string deb…

    4192ead
  9. ArneshBanerjee
    added a commit that references this issue on Oct 9, 2026

    Merge branch 'main' into pythongh-154711-tests-main

    5ee3d05
  10. kevinprimeraquecreo-stack
    added this to Project_VKon Oct 18, 2026
  11. github-project-automation
    moved this to Todo in Project_VKon Oct 18, 2026
  12. github-project-automation
    moved this from Todo to In progress in Project_VKon Oct 18, 2026
  13. kevinprimeraquecreo-stack
    added this to Project_VKon Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)topic-parsertype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.