← board

A keyword argument through a procedural field needs a plain receiver

def named(a, b=10):
    return a + b

class H:
    def __init__(self):
        self.fn = named
call pxx CPython
h = H(); h.fn(1, b=2) 3 3
def g(): return H() then g().fn(1, b=2) 3 3
H().fn(1, b=2) error: undefined variable (b) 3
hs = [H()]; hs[0].fn(1, b=2) error: undefined variable (b) 3
all four of the above with POSITIONAL args 3 3

So it is not "a fresh receiver" — a call RESULT is fine. It is a constructor call or a subscript as the receiver, and only with a keyword argument.

Found by

The sibling sweep of [[bug-n-a-field-assigned-a-module-level-def-has-no-inferable-type]], which made obj.fn(...) on a statically-typed receiver resolve at all. Before that fix every cell in the table failed with H has no method .fn(), so this combination has never worked and is not a regression — three of the four cells now do, and this is the fourth.

Why it is filed rather than folded in

The symptom — a keyword NAME parsed as an ordinary expression identifier — is exactly the one [[bug-nilpy-a-keyword-call-through-a-statically-unknown-callee-does-not-compile]] reported, and that ticket's own guidance is the right frame here too:

That decision should depend on the SYNTAX (an identifier followed by = at argument level is always a keyword argument in Python), not on whether a candidate callee was resolved.

PyMakeVariantFieldCall parses its arguments with a bare ParseArgExpr loop and has no keyword handling of its own, so the two cells that DO work are reaching keyword support from their surrounding context rather than from the builder. Fixing the builder is likely the whole job — but it is a change to argument parsing shared by every callable-field call, which is a different blast radius from the field-typing fix it was found under, and deserves its own gate rather than riding along.

Gate

All four receiver spellings above, positional and keyword, plus a field rebound to a different function (the reason the field carries no static signature), plus test_nilpy_callable_field_all_shapes and test_nilpy_field_holding_a_def green.


2026-09-11, frankB — it is a CORPUS wall, plus two rows this ticket did not have

Measured at compiler 73219e9ca3b7. I nearly filed this a second time under a WRONG generalisation; tools/progress.sh check's NEAR-DUP row is what stopped it, and the correction is worth recording because the wrong framing is the tempting one.

I had it as "a callable FIELD does not take keywords, while a variable, a dict value and a list element do". That is false, and this ticket's title had it right all along: the field is not the discriminator, the RECEIVER EXPRESSION is. One callable field, five receiver shapes:

receiver obj.b(room=2, laden=5)
a plain name, h.b(...) correct('made', 5, 2)
a function call, g().b(...) correct
a list subscript, hs[0].b(...) error: undefined variable (room)
a dict subscript, hd['k'].b(...) error: undefined variable (room)
a constructor call, K(make).b(...) error: undefined variable (room)

The dict-subscript row is new here; the ticket named a subscript without saying which, and both spellings refuse. Written OUT of declaration order on purpose — b(laden=5, room=2) and b(5, 2) agree, so an in-order row cannot tell a working door from one that merely drops the names.

It is a corpus wall now

lekkerzeilen/traffic.py:375, and it is the real remaining wall in that module:

self.boat = (KINDS[kind].build(laden=wanted, room=room)
             if KINDS[kind].cargo else KINDS[kind].build())

build is in Kind.__slots__ (traffic.py:766) holding vessel.spits, vessel.barge and so on. Varied to find the boundary: positional arguments compile, hoisting the receiver to a local does NOT help, removing the conditional expression does NOT help. Which fits this ticket exactly — hoisting to a local would have helped if the field were the problem, and it does not, because KINDS[kind] is still a subscript at the point that matters.

The :277 arity error in front of it is a SUBJECT-ONLY artefact and not a bug: traffic.py does not import world, so exactly one class declares nearest and the compiler takes its statically-resolved arm. Compile traffic beside world and :277 is gone and :375 is the wall.

Two messages, one construct

Worth knowing before anyone counts these as two walls:

context message
the corpus, traffic.py:375 expected ')' before '='
a two-module reduction of the same shape undefined variable (laden)

Same call, same door, discriminated by what else is linked — the message-level version of the cascade artefact.

Positive control for whoever fixes it

The plain-name and function-call receivers must KEEP working, out of declaration order, and a dict of two functions with different parameter names must still bind per-callee (alpha(laden, room) and beta(width, depth) in one dict). test/test_nilpy_double_star_at_a_callable_value_call.npy pins the ** spelling of the same family and is the file to extend.