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]

Reply via email to