← board

The measurement

Three rows, one file, compiler 7990405c2:

class Outer:
    class Bare:
        V = 5
    def m(self):
        return 1

print("A", Outer().m())     # pxx 1        CPython 1
print("B", Bare.V)          # pxx 5        CPython NameError
print("C", Outer.Bare.V)    # pxx REFUSED  CPython 5

Row C:

pascal26:9: error: class method not found: Bare
  near: ( "C" , Outer . Bare >>> . V )

Drop row C and the program compiles and prints A 1 / B 5.

What is actually wrong, and it is not the hoisting

The nested class IS parsed and IS registered -- at the wrong scope. So this is a placement bug, not a missing feature: nothing needs to learn how to parse a class inside a class body, and the machinery that builds the class already ran.

Accepting Bare at module scope is upward compatibility (CLAUDE.md, Track N: NilPy is upward compatible with CPython, one direction) and needs no change. What must be added is the class-level binding, so Outer.Bare resolves. Whether the module-level name then goes away is a separate question and the answer is probably no, since removing it can only break programs that compile today.

The diagnostic is correct about something else

class method not found: Bare is the house failure mode in one line. The lookup that ran was a METHOD lookup, because a nested class is not registered as a class-level attribute of any kind -- so the resolver reached the only class-level door it has and reported truthfully about that door. A reader chasing "method" looks at method registration, which is working. Whoever takes this should start at the class-member registration in PyRegisterClassMembers / PyParseClass and ask where a nested class statement's result is bound, not at method lookup.

Ranking, and why it is not higher

Low reach, measured rather than assumed. lekkerzeilen uses indented class statements only under tests/, and those are mostly function-local classes, which is a different construct. No module in the demo's import graph reaches this. It is a compile-time refusal, so it cannot join the silent-wrong-value population that earns a p70+ in this lane.

What this ticket is NOT

An earlier session (this one, pre-compaction) carried a claim that a class whose body contains a nested class loses its own method-default evaluation, via PyClsEvalCi/Lo/Hi being clobbered by the recursive PyParseClass. That does not reproduce and is not filed: with a nested class present, with and without methods on the nested class, Outer().m() still evaluates its default correctly and answers 7. The three globals ARE an implicit return channel from PyParseClass to its caller and the recursion does overwrite them -- so the mechanism is real and the observable is not. Anyone who rediscovers the globals should measure before filing; that is the whole reason this section exists.