A one-element unpack target binds nothing
Found 2026-09-20 (frankH) censusing That Space Program under NilPy.
tsp/stars.py:25 is (n,) = struct.unpack_from("<I", data, 6) and is that
file's only wall.
def f():
return [5]
(n,) = f() # pxx: undefined variable (n)
print(n)
a, = f() # pxx: undefined variable (a) -- no parentheses, same failure
print(a)
(b, c) = [1, 2] # compiles
print(b + c)
CPython prints 5, 5, 3.
The two controls are the finding. The reading everyone reaches for first is that
the PARENTHESES confuse the target scan; the bare a, = f() spelling rules that
out, and (b, c) = f() compiling rules out the other side. What is left is the
element COUNT.
When this is fixed, the fixture must carry BOTH spellings — parenthesised and bare — because a fix that special-cases the parenthesised form would pass a fixture written with only that one, and the bare form is the shape that proves the scan is arity-driven rather than punctuation-driven.
Resolution (2026-09-20, frankH)
Fixed. tupleTarget — set by the trailing COMMA and by nothing else — replaces
nTargets > 1 at the two gates that decide whether a single value is INDEXED or
stored whole, and PySkipOneTupleTargetGroup admits both parenthesised
spellings. (c) = v was refused on the pin and at HEAD alike and is fixed with
it: it binds the whole value, as c = v does, and it is the sibling that makes
the comma readable as the discriminator — a fixture asserting (c,) without
asserting (c) cannot show that the comma is what decides.
The first cut broke (b, c) = xs, which had always worked, and the reason
belongs here rather than only in the commit: the target loop's nested-group arm
ends in Continue, which jumps to the until and skips any tail. The rule
had been written as a tail, so it existed for the plain-name spelling only. The
remedy was to move it into the until EXPRESSION, which every arm reaches by
construction. That is normalise-dont-special-case.md's second-path rule
arriving inside ONE LOOP rather than across two files — grepping for the other
file could not have found it, and the fixture did, because it asserts the
neighbouring shapes and not only the two being fixed.
Fixture: test/test_nilpy_a_one_element_unpack_target_binds_the_element.npy,
eleven rows against CPython 3.14.4, carrying both one-element spellings, both
no-comma forms, both multi-element forms, the starred target, the nested group,
and struct.unpack_from — the shape it was found on. Pinned control reds at the
one-tuple with the original undefined variable (n). tsp/stars.py compiles.
Log: fixed and closed, commit dc17f567e. That sha is the FIX — the parser change, the fixture and the Makefile row — and the close rode in the same commit; there is no separate close to cite.