← board

A nested def's default parameter wins over the caller's argument

def make():
    n = 0
    def inc(by=1):
        nonlocal n
        n += by
        return n
    return inc

f = make()
print(f(), f(2), f(10))     # CPython 1 3 13     pxx 1 2 3

Silent, and on an everyday shape — a counter, an accumulator, any helper with a tuning parameter. Found 2026-08-15 by a CPython differential sweep of closures; it was the first line of the probe.

Not limited to nonlocal: a nested def with NO captures at all has it too.

def make2():
    def h(a, b=7):
        return (a, b)
    return h
print(make2()(1, 2))        # CPython (1, 2)     pxx (1, 7)

Cause

A nested def taken as a value is LIFTED to a bound-fn: its defaults are evaluated and bound into slots after the own parameters, and pyboundfn_setown(nOwn) tells the bridge how many leading arguments the caller supplies. The bridge then CLIPS anything past nOwn — so an argument for a defaulted parameter was dropped and the bound default was used.

The mechanism that fixes this already existed: pyboundfn_setdefaults(obj, base, count, varmask) declares which bound slots are defaulted parameters and where the caller counts their positions from. Its own comment says it plainly — "only the lambda lifter calls pyboundfn_setdefaults, so every other bound-fn user — nested defs, the callback bridges — keeps the lenient behaviour". For the callback bridges that leniency is deliberate; for a nested def it was simply missed, and the sibling construct (a lambda with b=2) got it right.

PyNestedDefClosureValue now chains the same call, with base = nOwn, count = nDef (the defaults occupy bound slots 0..nDef-1, bound first) and a varmask built from the callee's parameter types, exactly as the lambda site builds it.

Two more bugs found in the same probe, both filed, neither fixed here

Gate

test/test_nilpy_nested_def_default_override.npy (+.expected, in the Makefile), byte-identical to CPython: one default with nonlocal, one with no captures, TWO defaults (each overridden separately and together), defaults alongside a capture, a nested def with no defaults as a control, and a nested def CALLED DIRECTLY rather than returned. gate.sh quick GREEN.