enumerate, zip, dict.items and most_common build pairs that print as lists
print(list(enumerate(["a", "b"]))) # CPython: [(0, 'a'), (1, 'b')] pxx: [[0, 'a'], [1, 'b']]
print(list(zip([1, 2], ["a", "b"]))) # CPython: [(1, 'a'), (2, 'b')] pxx: [[1, 'a'], [2, 'b']]
d = {"a": 1, "b": 2}
print(d.items()) # CPython: dict_items([('a', 1), ('b', 2)])
# pxx: [['a', 1], ['b', 2]]
NilPy has ONE sequence representation, so a tuple is a TPyList carrying
FIsTuple — set by the frontend wherever a tuple DISPLAY was written, and the
only thing that makes (1, 2) render with parentheses rather than brackets
(that is what bug-nilpy-str-of-tuple-is-empty established). The four pylib
routines that manufacture pairs never set it:
pylib.pas — pyenumerate (≈2717), pyzip (≈2737), TPyDict.most_common
(≈2933), TPyDict.itemlist (≈2984), each pair := TPyList.Create; with no
pair.FIsTuple := True.
Iteration and unpacking are unaffected — for i, x in enumerate(xs) works —
so this is confined to rendering, which is why it survived: nothing crashes,
the output is merely not Python's.
Also, separately, the VIEW objects have no repr
d.keys() / d.values() / d.items() print as bare lists rather than
dict_keys([...]) / dict_values([...]) / dict_items([...]). That is a
different thing from the tuple flag (a missing view type, not a missing flag)
and is worth its own decision about whether NilPy wants view objects at all —
it is recorded here only so the two are not confused when someone diffs
d.items() against CPython.
Shape of a fix
pair.FIsTuple := True; at the four sites. Cheap, but check the one place the
flag changes MEANING rather than rendering: pypercent_format reads
FIsTuple to decide whether the right-hand side is an argument LIST or a
single value, so "%s" % <pair> would start unpacking. Grep the corpus for a
% whose right side is an enumerate/zip/items element before landing it.
FIXED (this session)
pair.FIsTuple := True; added at all four sites (pyenumerate, pyzip,
TPyDict.itemlist, TPyDict.most_common). Checked the pypercent_format
concern: no corpus/test source has a % format whose right side is one of
these pairs, and making them correctly tuple-tagged is the CORRECT direction
anyway — real Python's "%s" % t on a tuple attempts positional unpacking too,
so this fix only makes that path match CPython rather than introduce a new
divergence.
Gate
make test-nilpy + a .npy printing enumerate, zip, items and
most_common results, expectation taken from CPython's own output.
Log
- 2026-07-31 — resolved, commit 0049c17e8.