https://github.com/python/cpython/commit/97e85f6adf7213707903cdb00346cf6cbf837c2a commit: 97e85f6adf7213707903cdb00346cf6cbf837c2a branch: 3.13 author: Miss Islington (bot) <[email protected]> committer: serhiy-storchaka <[email protected]> date: 2026-09-17T09:05:10Z summary:
[3.13] gh-157451: Fix gettext.c2py() for unary "!" combined with a binary operator (GH-157453) (GH-157665) In C the unary "!" operator binds tighter than binary operators, but "not" binds looser in Python, so "!n + 1" was translated to "not n + 1". "!" as the right operand of a binary operator, for example "2 * !n", produced invalid Python code and raised SyntaxError. (cherry picked from commit 01192a80a6e32730d7c6f4fd3bcb1893bce29426) Co-authored-by: Madan Kumar <[email protected]> Co-authored-by: Serhiy Storchaka <[email protected]> files: A Misc/NEWS.d/next/Library/2026-09-14-00-30-00.gh-issue-157451.k7Qm2x.rst M Lib/gettext.py M Lib/test/test_gettext.py diff --git a/Lib/gettext.py b/Lib/gettext.py index 62cff81b7b3d496..e4ead175874b8f5 100644 --- a/Lib/gettext.py +++ b/Lib/gettext.py @@ -135,6 +135,9 @@ def _parse(tokens, priority=-1): except ValueError: raise _error(nexttok) from None result = '%s%d' % (result, value) + # Unary '!' binds tighter than binary operators in C, unlike 'not'. + if result.startswith('not '): + result = '(%s)' % result nexttok = next(tokens) j = 100 diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py index 681de239d8cc28c..5f369970d1941f1 100644 --- a/Lib/test/test_gettext.py +++ b/Lib/test/test_gettext.py @@ -661,6 +661,23 @@ def test_negation(self): self.assertEqual(f(1), 0) self.assertEqual(f(2), 0) + def test_negation_precedence(self): + # gh-157451: in C the unary '!' binds tighter than any binary + # operator, so '!n + 1' is '(!n) + 1', not '!(n + 1)'. + f = gettext.c2py('!n + 1') + self.assertEqual(f(0), 2) + self.assertEqual(f(1), 1) + self.assertEqual(gettext.c2py('!n < 3')(0), 1) + self.assertEqual(gettext.c2py('!n * 2')(0), 2) + self.assertEqual(gettext.c2py('!n * 2')(1), 0) + # Double negation still normalises to 0/1 (C semantics). + self.assertEqual(gettext.c2py('!!n')(5), 1) + self.assertEqual(gettext.c2py('!!n')(0), 0) + # '!' as a right operand also binds tighter than the binary operator, + # so '2 * !n + 1' is '2 * (!n) + 1' rather than a SyntaxError. + self.assertEqual(gettext.c2py('2 * !n + 1')(0), 3) + self.assertEqual(gettext.c2py('2 * !n + 1')(1), 1) + def test_nested_condition_operator(self): self.assertEqual(gettext.c2py('n?1?2:3:4')(0), 4) self.assertEqual(gettext.c2py('n?1?2:3:4')(1), 2) diff --git a/Misc/NEWS.d/next/Library/2026-09-14-00-30-00.gh-issue-157451.k7Qm2x.rst b/Misc/NEWS.d/next/Library/2026-09-14-00-30-00.gh-issue-157451.k7Qm2x.rst new file mode 100644 index 000000000000000..45e3fedf4ca3278 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-14-00-30-00.gh-issue-157451.k7Qm2x.rst @@ -0,0 +1,3 @@ +Fix :mod:`gettext` plural form selection when the ``Plural-Forms`` expression +combines the unary ``!`` operator with a binary operator, for example +``!n + 1`` or ``2 * !n``. The latter raised :exc:`SyntaxError`. _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
