dict(zip(...)) is empty
print(dict(zip("ab", [1, 2]))) # CPython {'a': 1, 'b': 2} pxx {}
print(dict(zip(["a","b"], [1, 2]))) # CPython {'a': 1, 'b': 2} pxx {}
Silent. list(zip("ab", [1,2])) is correct, dict([("a",1),("b",2)]) is
correct, and the for k, v in zip(...) loop is correct — so the pairs exist and
the constructor is the only thing that loses them.
Found 2026-08-15 by a CPython differential sweep (tools/pydiff.py), one probe
after [[bug-nilpy-builtins-over-a-user-iterable-answer-empty]] — the same
defect one container over, which is exactly what that ticket's "one concept, N
copies" note predicts.
Cause
dict(const v: Variant) had ONE arm:
if o is TPyDict then begin Result := dict(TPyDict(o)); Exit; end;
...
Result := TPyDict.Create; { None / non-mapping }
Python's dict() takes either a mapping OR an iterable of (key, value) pairs.
zip answers a cursor (TPyIter), there was no overload for one, so the call
boxed it into a variant, reached the arm above, matched nothing and fell into
the empty-dict fallback. The pair walk itself already existed —
dict(TPyList) is TPyDict.update.
Fix
The variant arm now routes any non-dict object through pyseq_of_obj (pylib's
one object-to-sequence chain, introduced by the ticket above) and hands the
result to dict(TPyList). A dict(it: TPyIter) overload was added beside it so
a statically-typed cursor never has to be boxed to find its meaning.
That covers zip, map, filter, enumerate, iter(...), a range, and a
user class with __iter__, because it is the same chain every other container
constructor now uses.
Not changed: dict(5) still answers {} where CPython raises TypeError. That
fallback predates this and is its own (unfiled) divergence — worth a ticket if
the empty answer ever hides something, but widening it here would have been an
unrelated behaviour change in the same commit.
Gate
test/test_nilpy_dict_over_any_iterable.npy (+.expected, in the Makefile),
byte-identical to CPython: dict(zip(...)) in three spellings, map, filter,
enumerate, an explicit iter(...), a user iterable, a list of pairs, a list
of two-element lists, dict(**kwargs), dict(<dict>), and the empty cases.
gate.sh quick GREEN. Pinned (the change is in compiler/builtin/**).