← board

s.split(sep)[i] on a variable receiver won't lower

Repro

s = "Hello,World"
print(s.split(",")[1])
pascal26: error: IR_UNSUPPORTED: frontend could not lower AST node (kind 8)
          — a frontend gap, would miscompile

What makes it specific

The neighbouring forms all compile and run correctly, which is what makes this a narrow frontend gap rather than "subscripting calls is unsupported":

expression result
s.split(",") (no subscript) OK — prints ['Hello', 'World']
"a,b".split(",")[1]literal receiver OKb
p = s.split(","); p[1] — two-step OK
g()[1] where g() returns a split result OKb
xs.copy()[0] — variable receiver, list method returning a list OK
s.upper()[0] — variable receiver, str method returning a str OK
s.split(",")[0] / [1] — variable receiver FAIL

So it is not "method call then subscript" (that works), not "list-returning method then subscript" (xs.copy()[0] works), and not split itself (the literal receiver works). It is the combination: a str-typed variable receiver, a method whose result is a list, and an immediate subscript.

That pattern suggests the frontend knows the result type when the receiver is a literal or when the value is bound to a name first, but loses it for a variable receiver in subscript position, leaving an AST node (kind 8) with no lowering.

Why it matters more than the repro suggests

parts = line.split(",") then indexing is the single most common line of string-handling Python there is, and the one-liner form is what people write first. It is a hard compile error rather than a miscompile, which is the good case — but it will be hit constantly.

Note on the canary

The quick-tier NilPy canary uses the two-step form for this section so it can land now. That is the documented pattern (file the ticket, keep the code idiomatic) rather than a workaround hiding the bug — the two-step form is perfectly ordinary Python and still exercises split. Switch it back to the one-liner when this is fixed; it makes a good regression check.

2026-08-01 — FIXED

One line. The variable-receiver route in parser.inc's lvalue suffix loop (~5220) called PyParseStrMethod, took the result type, and then threw the class identity away:

node := PyParseStrMethod(node, fieldName);
tk := IntToTypeKind(ASTTk[node]);
recName := REC_NONE;          { <- here }

So for s.split(",") the base became tyClass with recName = REC_NONE, and the following [1] had no class to resolve against — the AN_CALL reached IR lowering unlowered, which is the IR_UNSUPPORTED ... (kind 8) hard error.

That also explains the whole table of neighbours the ticket recorded: the LITERAL route (~13222) resolves the rec off the node with ResolveNodeRec, and the two-step form re-reads it from the bound name — both keep the identity. Only this route dropped it. The to_bytes route sitting ten lines below already did recName := ASTRight[node], so the fix is what its neighbour was already doing:

recName := ResolveNodeRec(node);

A str-returning method still resolves to REC_NONE, so nothing else moves.

Verified

test/test_nilpy_str_method_subscript.npy, wired into make test-nilpy, byte-identical to CPython. Confirmed RED pre-fix (the same IR_UNSUPPORTED).

Covers the container-returning str methods rather than just split, since they share the route: split(sep)[i] both indices, no-arg split()[1], partition("-")[2], encode()[0], plus the forms that already worked and must stay working — literal receiver, two-step, upper()[0], chained strip().upper(), and an unsubscripted split printing the whole list.

Native: build + byte-identical self-host fixedpoint, testmgr --tier quick GREEN.

Log