← board

A variadic def reached through a function VALUE

def h(*a):
    return len(a)

k = h
print(k(1, 2, 3))        # CPython 3      pxx 1     (richer bodies: SIGSEGV)

Measured 2026-08-15 during a CPython differential sweep.

Root cause

Packing surplus arguments into a *args callee's single TPyList parameter is work the call site does — PyPackStarArgs, at a written call. A call through a function value has no call site to do it in: the arity is only known at run time, and the value carried nothing that even said the callee collects. So pybound_callv3 dispatched through function(const a0, a1, a2: Variant) into a body whose real signature is function(l: TPyList). The first Variant was read as the list pointer — len of a boxed 1 is 1, and anything that dereferenced it faulted.

Three representations of a callable, and all three were wrong in their own way:

shape what it built before
k = h (module def) pybound_new pair loose args
c.take (bound method) pybound_new pair, via RTTI loose args
return wrapper (nested def, nothing to capture) a bare AN_PROCADDR loose args, and no place to record a star at all

The last is the decorator shape, which is why this is not an exotic case: def wrapper(*args) returned from a decorator is the single commonest variadic in Python.

The fix

pybound_new_star(code, recv, isFunc, starIdx) — the pair plus the callee's OWN star position (Self excluded). pybound_new is now a call to it with -1, so nothing else moved. PyBoundCallStar does at run time what PyPackStarArgs does at compile time: everything from starIdx on becomes a TPyList, marked as a TUPLE (unmarked, print(args) shows brackets and type(args).__name__ answers list), then dispatches through a signature family that declares the fixed Variants and the list — function/procedure × receiver/no-receiver × star position 0..3.

Emitted from three places: PyMakeFuncValueFor and PyMakeBoundMethod (the latter converting from signature space, Self at 0), and pylib's own attribute-read path, where the index is already in the RTTI meth Flags word. The bare-address arm in ParseFactor now routes a variadic def through PyMakeFuncValueFor instead, since an address cannot carry the star.

What is still open

Gate

make compiler/pascal26 + tools/gate.sh quick GREEN; pinned v332 (pylib is a compiled program's runtime). test/test_nilpy_star_args_as_a_function_value.npy, byte-identical to CPython: arity 0..3 through a value, the tuple the body sees, fixed parameters before the star, an explicit -> None procedure, a bound method (receiver kept), a nested def returned as a value, and dispatch out of a dict.