diff --git a/Lib/gettext.py b/Lib/gettext.py index 2f77f0e849e9ae..1fbdacc3eada7d 100644 --- a/Lib/gettext.py +++ b/Lib/gettext.py @@ -119,8 +119,9 @@ def _error(value): def _parse(tokens, priority=-1): result = '' nexttok = next(tokens) + negations = 0 while nexttok == '!': - result += 'not ' + negations += 1 nexttok = next(tokens) if nexttok == '(': @@ -136,6 +137,11 @@ def _parse(tokens, priority=-1): except ValueError: raise _error(nexttok) from None result = '%s%d' % (result, value) + # In C the unary '!' binds tighter than any binary operator, but Python's + # 'not' binds looser, so negate the operand as a parenthesised unit before + # the binary-operator loop below ('!n + 1' means '(!n) + 1', not '!(n + 1)'). + for _ in range(negations): + result = '(not %s)' % result nexttok = next(tokens) j = 100 diff --git a/Lib/test/test_gettext.py b/Lib/test/test_gettext.py index 9ad37909a8ec4e..6e09fe8f58a8cf 100644 --- a/Lib/test/test_gettext.py +++ b/Lib/test/test_gettext.py @@ -613,6 +613,19 @@ 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) + 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 00000000000000..cbeb545a061b4c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-14-00-30-00.gh-issue-157451.k7Qm2x.rst @@ -0,0 +1,4 @@ +Fix :func:`gettext.c2py` mistranslating the unary ``!`` operator when it is +followed by a binary operator (for example ``!n + 1``). ``!`` binds tighter +than any binary operator in C, so the operand is now negated as a +self-contained unit, matching the C semantics used by ``Plural-Forms`` rules.