← board

A field typed across a semicolon takes the NEXT statement's type

Found 2026-08-27 while fixing [[bug-n-the-old-style-iteration-protocol-reaches-only-the-for-loop]] — a one-line __init__ written in a throwaway repro failed for a reason that had nothing to do with iteration. Pre-existing at stable_linux_amd64/default/pinned.

class C:
    def __init__(self, items): self.items = items; self.i = 0


c = C([1, 2])
print(c.items, c.i)     # CPython [1, 2] 0    pxx TypeError: expected a number, got object

Measured boundary

shape pxx
self.items = items; self.i = 0 fails
self.i = 0; self.items = items (reversed) ok
self.items = items; self.i = "x" ok
self.items = items; self.j = items ok
self.items = items (alone) ok
self.x = a; self.y = 0 where a is an int ok

It is not the semicolon form and not the one-line body: it is the field taking its type from a token that belongs to the NEXT statement. The rows that "work" work by luck — the wrong type happened to be compatible.

Cause and fix

The self.NAME = expr pre-pass computed its right-hand side's end with

while (k2 < bodyEnd) and not (Tokens[k2].Kind in [tkNewline, tkDedent]) do Inc(k2);

tkSemicolon was missing, so the expression scanner was handed items ; self . i = 0 and answered from the 0. PyBlkRhsEndsAt, three functions above in the same file, already lists tkSemicolon among the statement terminators — the knowledge was in the file, just not at this scan.

Fixed alongside [[bug-n-a-field-takes-its-type-from-the-first-token-of-its-right-hand-side]], which owns the same span: one terminator set, computed once, used by every arm.

Gate

test/test_nilpy_field_type_from_whole_rhs.npy (classes S and S2), diffed against CPython.

Log