← board

hash(x) is not implemented

print(hash("ab"), hash(1), hash((1, 2)))
pascal26: error: undefined variable (hash)

CPython prints three integers (the string one is salted per process, the int one is the value itself, the tuple one is derived from its elements).

What it should answer

pylib already computes exactly this: PyVarHashKey(p: PPyVarRec): NativeUInt is the dict's own key hash, written to mirror PyVarEq arm for arm — int family by value, strings by content, tuples/lists by element, a user object through its __hash__. So hash(x) is a thin frontend intercept over the routine that already exists, and it is the natural home for the "a __hash__ returning a VARIANT still has to fold to an integer" rule that the sibling ticket needs.

Do not promise CPython's exact numbers. CPython salts string hashing per process (PYTHONHASHSEED), so hash("ab") is not reproducible even between two CPython runs. What must hold is the INVARIANT: equal values hash equal, within one run. A pydiff test must therefore compare hash(a) == hash(b) and hash(a) == hash(a), never the literal number.

Gate

make test-nilpy + self-host byte-identical, and a .npy case asserting the invariant (equal ints / equal strings / equal tuples / two __eq__-equal user objects all hash equal, and hash(x) == hash(x)), diffed against CPython with tools/pydiff.py run.

Resolution (2026-08-11)

pyhash_v exposes PyVarHashKey — the dict's own key hash — and a frontend intercept lowers hash(x) to it, gated on procIdx < 0 so a user def hash shadows it like every other builtin here. Nothing new was computed: the value already existed and simply had no name in the language.

Matches CPython on every row, and every row is an INVARIANT rather than a number (CPython salts string hashing per process, so a literal expectation is untestable by construction):

Gate: make test-nilpy EXIT=0, gate.sh quick GREEN. New test/test_nilpy_hash_builtin.npy. Needs a pin before other lanes see it (compiler/builtin).

Log