← board

A fixed parameter before *args segfaults

Repro

class P:
    def __init__(self, tag, *rest):
        self.tag = tag
        self.n = len(rest)
    def show(self):
        print("P", self.tag, self.n)

o = P("u", 9)
o.show()

CPython prints P u 1. pxx compiles clean and SEGFAULTS.

Confirmed pre-existing at stable_linux_amd64/default/pinned (v256), so it is not the class-as-a-value work.

The boundary, measured

shape result
def __init__(self, *args) works (S(1, 2)n == 2)
def __init__(self, tag, *rest) segfault

So it is not *args in a constructor and not construction — it is a fixed parameter before the star that breaks. Worth checking the plain-def twin (def g(a, *rest)) before deciding the fix belongs to the ctor path: if that fails too, the packing is wrong wherever the star index is not 1, and the constructor is only where it was noticed.

Why it rates a 55

It is the standard thin-wrapper shape — def __init__(self, name, *opts) — and it crashes rather than diagnosing, which is the expensive kind. It is also narrow: the star-index-0 case works, so this is very likely one off-by-one in the argument packing rather than missing machinery.

[[feature-nilpy-star-args-kwargs]] is the broad callee-side *args/**kwargs feature (unfinished). This is filed separately because it is a SILENT CRASH in a shape that already parses and already half-works, not a missing feature.

Gate

make test-nilpy + self-host byte-identical, with a .npy case covering (self, a, *rest), (self, a, b, *rest) and the plain-def twin, diffed against CPython via tools/pydiff.py run.

Resolution (2026-08-11)

The ticket's boundary was measured through the wrong door

It recorded def __init__(self, *args) as working. It is not — that row was measured through a class VALUE (k = S; k(1, 2)), which goes through pyeval's PyClassRefNew and packs the star itself. Ordinary construction S(1, 2) segfaulted too. So the real statement is broader and simpler:

*args on a constructor was never packed at all.

shape before
def g(a, *rest) — plain def works
def m(self, a, *rest) — ordinary method works
def __init__(self, *args) segfault
def __init__(self, a, *rest) segfault

The two working twins are what said the packing machinery is fine and one path skipped it — devdocs/dev/normalise-dont-special-case.md's shape exactly.

Cause

PyClassCreate assembles the construction call itself — GetMem size node, then the user arguments — and, as its own comment already said about the DEFAULTS loop, "never passes through the shared path". That shared path is also where PyPackStarArgs runs, so the surplus arguments were emitted as ordinary positionals and the callee read one of them as its packed TPyList pointer.

A probe settled it in one build: instrumenting PyPackStarArgs to Error on entry, compiling the repro produced no error at all — the packer was never reached, rather than reached with wrong indices.

Fix

PyPackStarArgs gained a selfSlots parameter and PyClassCreate calls it with 1.

That offset is the whole subtlety. Everything in the packer counts in PARAMETER space — si/ki are signature indices, the container node carries an explicit slot, PyFillDefaultsUpTo walks slots — except pos and nKept, which are derived from the argument chain. A method call carries Self as the chain's first node so those already line up; a construction does not, so both now start at selfSlots. Without it even the star-only ctor stayed broken, because pos >= si compared a chain position against a signature index counting Self. Every pre-existing call site passes 0 and is unchanged.

Packing runs before the trailing-defaults loop, which then finds the slots filled and does nothing — the packer fills the gap itself, keyword bindings included.

Verified

test/test_nilpy_star_args_ctor.npy (.expected from CPython), wired into make test-nilpy: star-only at arities 0-3 (and type(args).__name__ is tuple), one and two fixed parameters before the star, a DEFAULTED parameter before it (omitted, given, overrun), and the plain-def and ordinary-METHOD twins kept as the controls that name which path was short. Gate: tools/gate.sh quick GREEN + make test-nilpy.

Found in the same sweep, filed not fixed

[[bug-nilpy-kwargs-and-star-unpack-at-a-construction-are-refused]] — C(**kw), C(*xs), and a keyword colliding with a same-named field when the ctor takes **kw. Same cause one step further along (the construction path still does not reach the kwargs container or the call-site unpack), but all three are a DIAGNOSTIC rather than a crash, so they are their own ticket at a lower priority.

Also seen and not filed: self.args = args — storing the star tuple in a field — reports "cannot infer the type of field self.args". That is field-type inference for a star parameter, which is [[feature-n-nilpy-ast-typing-module-scope]]'s territory rather than an argument-passing bug.

Log