Skip to content

gh-157451: Fix gettext.c2py precedence of unary ! before a binary operator - #157453

Open
winklemad wants to merge 1 commit into
python:mainfrom
winklemad:gh-157451-gettext-c2py-negation-precedence
Open

winklemad wants to merge 1 commit into
python:mainfrom
winklemad:gh-157451-gettext-c2py-negation-precedence

Conversation

@winklemad

@winklemad winklemad commented Sep 13, 2026

Copy link
Copy Markdown

In C the unary ! operator binds tighter than every binary operator, so !n + 1 means (!n) + 1. gettext.c2py's parser (_parse in Lib/gettext.py) emitted a bare not prefix and then appended the binary operators to the whole string, so !n + 1 compiled to not n + 1 — i.e. not (n + 1) — because Python's not binds looser than arithmetic and comparison operators.

>>> import gettext
>>> gettext.c2py('!n + 1')(0)   # was 0; C: (!0)+1 == 2
2
>>> gettext.c2py('!n < 3')(0)   # was 0; C: (!0)<3 == 1
1

This selects the wrong plural form for any .mo catalog whose Plural-Forms rule applies ! before a binary operator.

The fix negates the operand as a self-contained parenthesised unit before the binary-operator loop, so !n + 1 compiles to (not n) + 1. Double negation still normalises to 0/1 as in C (!!n(not (not n))).

The existing test_negation only covered !!!n with no trailing binary operator — the one form that happened to work — so the precedence bug wasn't pinned. Added test_negation_precedence covering ! before +/</* and double negation.

AI-tools disclosure (per the devguide policy): I used an AI assistant to help locate the bug and draft the patch and test. I verified the behaviour and the fix against a locally built interpreter, understand the change, and stand by it.

…nary operator

In C the unary `!` operator binds tighter than every binary operator, so
`!n + 1` means `(!n) + 1`. `c2py`'s parser emitted a bare `not ` prefix and
then appended the binary operators to the whole string, so `!n + 1` compiled
to `not n + 1` — i.e. `not (n + 1)` — because Python's `not` binds looser than
arithmetic and comparison operators.

Negate the operand as a self-contained parenthesised unit before the
binary-operator loop, so `!n + 1` compiles to `(not n) + 1`. Double negation
still normalises to 0/1 as in C (`!!n` -> `(not (not n))`).

Add a regression test; the existing test only covered `!!!n` with no trailing
binary operator, which is the one form that happened to work.
@winklemad
winklemad requested a review from tomasr8 as a code owner September 13, 2026 19:08
@python-cla-bot

python-cla-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gettext.c2py mistranslates unary ! before a binary operator (wrong operator precedence)

1 participant