← board

NilPy: x = obj.method() immediately followed by y = x leaves x undefined

Symptom

A local assigned from a class-returning METHOD CALL, then copied to another variable, fails to compile — the second statement reports undefined variable for the first local:

class A:
    def __init__(self) -> None:
        self.x: int = 0
    def mk(self) -> "A":
        return A()
a = A()
raw = a.mk()      # method call returning a class
zz = raw          # error: undefined variable (raw)

raw = a.mk() followed by print(raw.n) WORKS (raw resolves as a method receiver). Only zz = raw — a bare class-typed ident as the whole RHS of a new assignment — fails. Ctor results (a = A(); b = a) work; list/bytes-literal results (raw = [1]; zz = raw) work. The failure is specific to a method-call result copied by a plain y = x.

Root cause (narrowed, not yet fixed)

Instrumentation shows the statement raw = a.mk() NEVER reaches PyParseStatement's statement-start — the previous statement's parse over-consumes the token cursor past raw, so raw is never AllocVar'd, and zz = raw then can't resolve it. The over-consumption happens only when the RHS is a class-returning method call whose result is stored to a fresh local and a further statement follows. Reproduces during the PyCollectLocalsAST typing pre-pass (round 1). Cursor mis-tracking in the assignment-of-method-call path, likely a missing newline/statement-terminator after a class-typed method-call RHS.

Impact

Blocks uforth's file-word conformance (filetest.fth): w_read_line does raw = entry["file"].readline() then slices/copies raw. The file I/O itself is DONE (TPyFile raw syscalls, open(path,mode), read/readline/write/seek/…); only this copy-of-method-result typing bug stops READ-LINE. core.fr / coreplus / coreext / block / double / exception / facility all pass; the file set is the first blocked by this.

Repro

/tmp/w79.npy (6 lines above). ./compiler/pascal26 repro.npy /tmp/outerror: undefined variable (raw).

2026-07-22: FIRST HALF FIXED; a second file-set blocker remains

FIXED (the undefined-variable half): PyCollectModuleLocalsAST skipped a module-level x = obj.method() assignment entirely (class methods aren't registered during the token pre-scan), so x was never a symbol and a later y = x in the same collect round failed. Now the skip still forgoes parsing the RHS but records the target name (PyNoteLocalType + a scratch AllocVar so the same round resolves it). raw = a.mk(); zz = raw compiles.

REMAINING (distinct, still open): a bytes value read from a dict-sourced variant then sliced comes back mis-tagged. remainder = d["rem"] (variant, None) then remainder = raw (raw = TPyBytes from readline) then remainder[:3] → runtime TypeError: object is not subscriptable, pyvar_slice sees tag=2 (VT_INT64) not 7 (VT_OBJECT). The class→variant store tagged the bytes pointer VT_INT64 because raw itself was typed tyVariant (by the collect-skip above) rather than TPyBytes, so remainder = raw is a variant→variant 16-byte copy of a variant that was never boxed as VT_OBJECT. Root: the collect-skip types method-call-result locals tyVariant, losing the bytes class identity, and the subsequent variant-to-variant copy propagates a mis-tagged slot. Fix needs the collect to recover the method result's class (or the store to re-box). uforth READ-LINE is the only word blocked; file create/write/close verified working.

Log