← board

What was measured

2026-09-13. Found while fixing bug-n-a-method-s-non-constant-default-is-none-when-the-call-is-inside-the-class — this is the same concept (fill an omitted argument for an intra-class call) reached by a different receiver spelling, which is why it is filed rather than left as a loose end.

class C:
    def m(self, n=30):
        return n

    def inside_alias(self):
        o = self
        return o.m()

    def inside_new(self):
        o = C()
        return o.m()

def outside_alias(c):
    o = c
    return o.m()

c = C()
print("A", c.m())                  # 30   both
o = c
print("B", o.m())                  # 30   both
print("C", outside_alias(c))       # 30   both
print("D", c.inside_new())         # 30   both
print("E", c.inside_alias())       # 30 CPython, SEGFAULT pin v408 AND HEAD

Row E is the whole ticket, and rows A–D are there because each of them is the same call through a route that works — so the ticket cannot be read as "method calls through a local are broken".

Narrowed further: with an EXPLICIT argument the same route is fine, and a parameter with no default is fine too.

def a_explicit(self):   o = self;  return o.m(5)        # 5  both
def b_nodefault(self):  o = self;  return o.nodefault(5)# 5  both
def c_omitted(self):    o = self;  return o.m()         # SEGFAULT

So the crash needs BOTH the o = self receiver and an omitted defaulted argument.

What is wrong

PXXDBG=n.locals says it in one line:

C.via_local  o tk=6 rec=0 | sym=<none>

tk=6 is tyClass, and rec=0 is REC_NONE — a statically class-typed local with no class behind it. That combination is the broken state: being tyClass picks the STATIC dispatch route, and the static route's omitted-argument fill needs the receiver's class to complete the call. o = C() records the class and works; a PARAMETER is tyVariant and takes the runtime-dispatch route, which works too.

The AST is not where it goes wrong, and this is worth saying because it is the first place anyone will look: PXXDBG=a.ast for the working self.m() and the crashing o.m() are structurally identical — both AN_VIRTUAL_CALL with the same proc index and the same [receiver, AN_INT_LIT 30] argument chain. The difference is entirely in what the receiver SYMBOL records.

The inference arm that handles w = self.lookup(k) is at pyparser.inc ~5835 (bug-nilpy-self-method-call-loses-return-class) and covers a POSTFIX chain on self. A bare o = self has no postfix and does not reach it.

Why prio 35

Nothing measured asks for it. o = self is a rare spelling — it appeared here only because it was written as a probe to vary the receiver route while holding the callee fixed. The crash is loud rather than silent, which is the better failure. Raise it if real source turns up.