gh-157451: Fix gettext.c2py precedence of unary ! before a binary operator - #157453
serhiy-storchaka merged 4 commits into
Conversation
…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.
c2py is not a documented function, so the :func: cross-reference failed the docs nit check. Use an inline literal instead.
|
Thanks for the catch, this has been there since the parser was written. The fix can be simpler. Nothing but the if result.startswith('not '):
result = '(%s)' % resultPlease also add a test with |
Since only the '!' loop can prefix `not ` onto result, wrap the operand
once before the binary-operator loop instead of counting negations. Also
add a test with '!' as a right operand ('2 * !n + 1'), which previously
generated a SyntaxError.
|
Thanks — that's much cleaner. I've switched to the single-wrap approach ( |
Co-authored-by: Serhiy Storchaka <[email protected]>
serhiy-storchaka
left a comment
There was a problem hiding this comment.
LGTM. 👍 I tweaked a comment and the NEWS entry.
|
Thanks @winklemad for the PR, and @serhiy-storchaka for merging it 🌮🎉.. I'm working now to backport this PR to: 3.13, 3.14, 3.15. |
|
GH-157663 is a backport of this pull request to the 3.15 branch. |
|
GH-157664 is a backport of this pull request to the 3.14 branch. |
|
GH-157665 is a backport of this pull request to the 3.13 branch. |
…nary 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 01192a8) Co-authored-by: Madan Kumar <[email protected]> Co-authored-by: Serhiy Storchaka <[email protected]>
…nary 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 01192a8) Co-authored-by: Madan Kumar <[email protected]> Co-authored-by: Serhiy Storchaka <[email protected]>
In C the unary
!operator binds tighter than every binary operator, so!n + 1means(!n) + 1.gettext.c2py's parser (_parseinLib/gettext.py) emitted a barenotprefix and then appended the binary operators to the whole string, so!n + 1compiled tonot n + 1— i.e.not (n + 1)— because Python'snotbinds looser than arithmetic and comparison operators.This selects the wrong plural form for any
.mocatalog whosePlural-Formsrule applies!before a binary operator.The fix negates the operand as a self-contained parenthesised unit before the binary-operator loop, so
!n + 1compiles to(not n) + 1. Double negation still normalises to 0/1 as in C (!!n→(not (not n))).The existing
test_negationonly covered!!!nwith no trailing binary operator — the one form that happened to work — so the precedence bug wasn't pinned. Addedtest_negation_precedencecovering!before+/</*and double negation.!before a binary operator (wrong operator precedence) #157451./python -m test test_gettext→ SUCCESS (74 tests); the new test fails onmain(AssertionError: 0 != 2) and passes with the change.Misc/NEWS.dentry.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.
!before a binary operator (wrong operator precedence) #157451