← board

A tuple used as a dict KEY never matches on lookup

d = {}
d[(1, 2)] = "a"
print(d[(1, 2)])

CPython: a. pxx: KeyError.

Storing works; the lookup does not find it. A tuple is a TPyList here, so the key comparison is presumably by identity (or by a hash of the handle) rather than by CONTENTS — the same distinction that bug-a-nilpy-container-equality-compares-identity fixed for the == operator and that pylist_eq implements. The dict's own key path did not get it.

Coordinate keys are ordinary Python (grid[(x, y)], memo tables, caches keyed by a pair), so this is worth fixing beyond the case that surfaced it.

Where it surfaced

songformatter's render_backend.py:135 writes px[ix, iy] — PIL pixel access, a 2-D subscript that Python turns into a tuple key. The PARSE of that form on a dynamically-typed receiver was a separate gap and is fixed; this ticket is only about the key never matching. That code is unreachable under pxx (PIL is absent), so nothing is blocked on it today.

Gate

make test-nilpy plus a .npy storing and reading tuple keys, including two distinct tuples with equal contents, diffed against CPython.

Log

Fix

PyVarHashKey (pylib.pas) gained a VT_OBJECT arm for a TPyList payload: the hash is now a sequence hash over the ELEMENTS, seeded by the length and recursing through PyVarHashKey, so it mirrors PyVarEq's element-wise compare arm for arm. Any other object keeps the identity hash, which is what PyVarEq's identity compare for non-lists requires.

Verified against CPython: the ticket's repro, a 3x3 coordinate grid keyed by (x, y), keys built fresh at lookup vs stored from a variable, a nested tuple key, mixed element types in one key, 1 / "1" / (1,) as distinct keys in one dict, overwrite-in-place, iteration, dict.get with a tuple key, and a 200-key stress with 231 membership probes. test/test_nilpy_tuple_dict_key.npy covers the set and is registered in test-nilpy / test-core.

Found while testing it, filed separately: [[bug-nilpy-method-chaining-on-a-call-result]], [[bug-nilpy-str-of-tuple-is-empty]].