https://github.com/python/cpython/commit/4733ac0118baf10b106762c78cae4d885fac6f42
commit: 4733ac0118baf10b106762c78cae4d885fac6f42
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-09-17T09:08:09Z
summary:

[3.14] gh-157451: Fix gettext.c2py() for unary "!" combined with a binary 
operator (GH-157453) (GH-157664)

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 6c11ab2b1eb5709..8cb0288d3d1b377 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 9ad37909a8ec4e0..b18489f34ae4806 100644
--- a/Lib/test/test_gettext.py
+++ b/Lib/test/test_gettext.py
@@ -613,6 +613,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