From d134dea4a2e59021181c4b329741a9a74190f2cf Mon Sep 17 00:00:00 2001 From: joshuaswanson Date: Mon, 30 Mar 2026 00:06:50 +0200 Subject: [PATCH] gh-146448: fix t-string parser truncating expression at != with format spec --- Lib/test/test_tstring.py | 9 +++++++++ .../2026-03-30-00-00-00.gh-issue-146448.5fce3b.rst | 2 ++ Parser/lexer/lexer.c | 13 +++++++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-03-30-00-00-00.gh-issue-146448.5fce3b.rst diff --git a/Lib/test/test_tstring.py b/Lib/test/test_tstring.py index 74653c77c55de1..2fa57ec35d7d22 100644 --- a/Lib/test/test_tstring.py +++ b/Lib/test/test_tstring.py @@ -287,5 +287,14 @@ def test_triple_quoted(self): ) self.assertEqual(fstring(t), "\n Hello,\n Python\n ") + def test_not_equal_with_format_spec(self): + # gh-146448: != in expression with format spec should not be + # confused with the ! conversion specifier + t = t"{0!=0:}" + self.assertTStringEqual(t, ("", ""), [(False, "0!=0")]) + + t = t"{0!=0:s}" + self.assertTStringEqual(t, ("", ""), [(False, "0!=0", None, "s")]) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-30-00-00-00.gh-issue-146448.5fce3b.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-30-00-00-00.gh-issue-146448.5fce3b.rst new file mode 100644 index 00000000000000..40a19df9bd0a99 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-30-00-00-00.gh-issue-146448.5fce3b.rst @@ -0,0 +1,2 @@ +Fix template string parser incorrectly truncating the ``Interpolation.str`` +attribute when the expression contains ``!=`` followed by a format spec. diff --git a/Parser/lexer/lexer.c b/Parser/lexer/lexer.c index 7f25afec302c22..ba972a37cbaddd 100644 --- a/Parser/lexer/lexer.c +++ b/Parser/lexer/lexer.c @@ -1250,8 +1250,17 @@ tok_get_normal_mode(struct tok_state *tok, tokenizer_mode* current_tok, struct t goto again; /* Read next line */ } - /* Punctuation character */ - int is_punctuation = (c == ':' || c == '}' || c == '!' || c == '{'); + /* Punctuation character. + * '!' is only treated as f-string/t-string punctuation (conversion + * specifier) when not followed by '=' (which would make it '!='). */ + int is_punctuation = (c == ':' || c == '}' || c == '{'); + if (c == '!') { + int ahead = tok_nextc(tok); + tok_backup(tok, ahead); + if (ahead != '=') { + is_punctuation = 1; + } + } if (is_punctuation && INSIDE_FSTRING(tok) && INSIDE_FSTRING_EXPR(current_tok)) { /* This code block gets executed before the curly_bracket_depth is incremented * by the `{` case, so for ensuring that we are on the 0th level, we need