← board

not on a C pointer is always True

import "/usr/include/SDL2/SDL.h"

SDL_Init(SDL_INIT_VIDEO)
w = SDL_CreateWindow("t", 0, 0, 64, 48, SDL_WINDOW_OPENGL)   # succeeds, non-NULL
expression pxx correct
w != 0 True True
w == 0 False False
"then" if w else "else" then then
not w True False
not (w != 0) False False

Measured at 120eeb39f, binary 7cf02ff177ca, repro compiled with -dSDL_DISABLE_IMMINTRIN_H. The NULL case (not n on a NULL pointer) answers True, which is right — so the operator is not inverted, it is constant.

Why this one is worth its prio

if not p: is THE null check in Python-shaped C interop, and this makes it fire on success. Nothing errors, nothing warns, and the program reports a failure that did not happen — the expensive shape CLAUDE.md describes, a plausible wrong value far from the cause.

It cost a session directly. lekkerzeilen/platform/_pxx.py has

handle = SDL_CreateWindow(title, ..., flags)
if not handle:
    raise _error("SDL_CreateWindow failed")

and raised that every time, while a probe in the same function printed retry handle truthy: True one line earlier. SDL_GetError() was empty, which was the instrument telling the truth and being read as a second failure — an error path reached without an error. Three hypotheses were measured and discarded first (the tuple default gl_version=(3, 3) unpacking, the open_window = _backend.open_window alias, and the window flags and position constants, all three of which were correct) because the one thing nobody doubts is a not.

if p being right is what hides it: the same file has working truthiness a few lines away, so the construct looks supported.

Where to look

The not arm of the NilPy unary-operator lowering, versus whatever the conditional/branch path uses to test a value for truth — the branch path has a pointer case and not does not reach it. Likely candidates are a not that assumes a Boolean operand and emits a Boolean complement against a value that is a pointer width, or one that tests a tag rather than the value.

The population IS just the pointer — table written, and it aims the fix. not was measured against CPython over every other operand type and agrees on all 18 rows: True/False, 0/1/-1, 0.0/2.5, ''/'x', []/[1], {}/{'a':1}, None, and each of int/str through a variable rather than a literal (so the constant folder is not what is being tested). So this is not a broken not — it is a not with no C-pointer case, and everything native is correct. Fix the missing case; do not touch the working ones.

RESOLVED 2026-09-11 by frankuser

Fixed as the FOURTH instance of one bug, not as a pointer bug. not lowered to AN_NOT (Pascal's bitwise complement) whenever PyNumeric said the operand was numeric, and PyNumeric lists only tyInteger, tyInt64 and the floats. So every sized/unsigned integer, size_t, and tyPointer got COMPLEMENTED, and a complement of a non-zero value is non-zero, i.e. TRUE. String, container and object each shipped as "always True" before this; the pointer was the fourth.

PyNotTestsAgainstZero now names the widths explicitly and lowers not x to x = 0 for all of them, tyPointer included.

Verified against the ticket's own reported shape rather than a probe of my choosing:

import "/usr/include/string.h"
p = strchr("hello", 122)   # 'z' is absent -> NULL
print("not p =", not p)    # True   (was True, correctly, by accident)
q = strchr("hello", 101)   # 'e' is present -> non-NULL
print("not q =", not q)    # False  (was TRUE -- the defect)

Both rows now correct. Note which row is load-bearing: the NULL row passed before the fix too, so a fixture asserting only not <null> would have certified the bug. The non-NULL row is the whole test.

Regression test test/test_nilpy_not_on_a_c_width_integer wired against $(COMPILER) and NOT $(PXX_STABLE), because the pinned compiler FAILS it on exactly the not nonzero row -- which is what makes it a regression test rather than a restatement. It will stay that way until someone pins.

Log