Repro
x = 1024
a = x + 1
print(~a) # pxx 4 CPython -1026
print(not a) # pxx True CPython False
if not a:
print("branch: falsy") # pxx takes THIS one
else:
print("branch: truthy") # CPython takes this one
What varies, measured
| spelling | not v |
~v |
CPython |
|---|---|---|---|
x = 1024; a = x + 1 |
True | 4 | False / -1026 |
same, then a = 1025 (plain literal) |
True | 3458764513820540932 | False / -1026 |
b = (1024) + 1 (literal binop) |
True | 8070450532247928836 | False / -1026 |
c = int(1025) (a call) |
False | -1026 | agrees |
y = 128 (a literal, never arithmetic) |
False | -129 | agrees |
inside a def, same shapes |
same as above | same | — |
+, -, * and >> all trigger it, so it is not the operator. A name that has
EVER been assigned from a binop stays broken even after a later assignment from
a bare literal, which points at the name's inferred type rather than at the
value flowing through it.
0x3000000000000004 and 0x7000000000000004 are a tag in the high nibble over
a small payload — so ~ is complementing a BOX (variant / promo-int handle),
not the integer inside it. ~a = 4 in the first row is the same shape with the
tag cleared: whatever it reads, it is not 1025.
not returning True for every one of them is consistent with reading the same
non-integer and finding it falsy — and not 128 and not y are both correct,
so the truthiness path itself is fine.
Why prio 70
It is silent and it moves control flow. if not count: and if not (mask >> k):
are ordinary Python; both take the wrong branch here with nothing printed. CPython
compatibility is one-directional (nilpy-semantics-divergences.md) and this is
the direction that is a defect: working CPython code runs wrong.
Suspected site
pyparser.inc's opIsArith decision (around the notOp := Integer(ASTIVal[left])
block) chooses bitwise-vs-logical not from the operator id of the operand node.
When the operand is a NAME rather than a binop, ASTIVal[left] is not an operator
id at all — it is whatever that node kind keeps there — so the branch is taken on
a field that means something else. That is a guess from reading and it is the
thing to disprove first; the boxed-value tags above may point somewhere lower.
Found
2026-08-31, while verifying that the tkIdent -> tkShrLogical rename
(bug-a-shr-reaches-the-ir-spelled-as-tkident) had not disturbed that
opIsArith line. It had not — the pinned compiler produces byte-identical
wrong answers, so this predates the rename entirely. It is unrelated to the
shift work except that a >> probe is what put it on screen.