Cls(*xs, kw=v) — the constructor arm of the star-follower fix
What works and what does not
Fixed on 2026-09-12 (PyStarTrailingKwMinSlot + a cap in PyStarExpandCallArgs):
| shape | site | result |
|---|---|---|
f(*xs, kw=v) free function |
PyStarMixedForwardCall |
worked before |
obj.m(*xs, kw=v) |
PyStarUnpackMethodArgs |
fixed |
Cls().m(*xs, kw=v) |
PyParseClassMethodCall |
fixed |
pick().n(*xs, kw=v) |
PyParseVariantMethod |
fixed |
Cls(*xs, kw=v) |
PyClassCreate |
still refused ← this ticket |
CPython accepts all five. test/test_nilpy_a_keyword_argument_after_a_star_unpack.npy
covers the four that work and says in its own header not to add a ctor row.
Why this one was left, and it is not difficulty
The cap is already computed and already correct for this callee — the site passes
a literal -1 to decline it, with the reason inline. What differs is the
bookkeeping on the other side of the call:
- Every METHOD site resolves a keyword through
PyKwArgIndex, which answers a 1-based parameter slot and is exactly what the cap is expressed in. PyClassCreateresolves a keyword to a field index (kwFld := kwPk - 1), falls back toFindUFieldfor the dataclass shape, has a third arm for a ctor taking**kwargs(the key travels as-(node+1)onASTIVal), and maintainsnArgs,kwAny,kwExtraHead/kwExtraLastthat the star expansion does not touch.
So the method fix's shape does not transfer by inspection, and the failure mode if it is wrong is the one this fix was careful to avoid: an argument that lands in the wrong slot, at run time, with no diagnostic. A refusal is the better wrong answer until someone can verify it.
How to do it
- Add a ctor row to a differential —
Ctor(*xs, forced=9)against__init__(self, a, b, forced=0), plus the dataclass shape (no__init__, keyword naming a FIELD) and the**kwargsctor, because those are three different arms of the same loop and only the first resembles a method. - Pass
PyStarTrailingKwMinSlot(ctorPi)instead of-1at the site, and work out whatnArgsmust be afterwards — the expansion appendstotal - firstSlotpositional args without incrementing it. - The positive control is that the pin, and HEAD before the change, refuse it.
What must stay refused either way
A trailing POSITIONAL (Cls(*xs, 5)) and a trailing **mapping. Those need the
star's run-time length to know which slot the follower lands on, which a
compile-time expansion does not have — see
feature-n-a-method-call-cannot-take-an-argument-after-a-star-unpack, which
keeps that half.