← board

int() and float() ignore __int__ / __float__

class V:
    def __init__(self, n): self.n = n
    def __int__(self): return self.n
    def __float__(self): return float(self.n)

print(int(V(4)), float(V(4)))
# CPython 4 4.0
# pxx     129729065648272 129729065648328.0

Silent, and the printed value is the instance POINTER, so it changes per run — the tell of a handle read as a number. Found 2026-08-15 by a CPython differential sweep of the numeric dunder surface, in the same pass that found [[bug-nilpy-int-of-a-division-reads-the-doubles-bits]].

bool, abs and unary minus over the same class were already correct (__bool__, __abs__, __neg__ are dispatched), which is what makes this two missing members of a protocol the frontend otherwise implements rather than an absent feature.

Fix

Both conversion intercepts now check for a user class first and call the dunder, the same way the truthiness path calls __bool__. A class declaring NEITHER gets a run-time TypeError naming it, in CPython's own message shape (PyNoIntError / PyNoFloatError, beside the PyNoSetitemError pair they are modelled on) — at RUN time, so try: int(o) / except TypeError: still compiles.

pylib's own container classes are excluded, keeping their existing paths.

Gate

test/test_nilpy_int_float_dunders.npy (+.expected, in the Makefile), byte-identical to CPython: both dunders through a named receiver, a fresh construction and a call result; a class declaring only __int__ (whose float() must still raise); a class declaring neither, for both conversions; and the ordinary int/float/str/expression conversions unchanged. gate.sh quick GREEN, pinned v331.