← board

Two node consumers know AN_CALL but not its virtual sibling

Filed 2026-08-26 while fixing [[bug-n-a-subscript-inside-a-base-class-skips-the-subclass-override]].

Honest status: found by grep, not by a failing program. Neither line below has been turned into a repro, and the priority reflects that. Do not write a symptom into this ticket that has not been measured.

The pattern

AN_CALL and AN_VIRTUAL_CALL carry the callee's proc index in the same field (ASTIVal); the virtual one additionally holds its VMT slot in ASTRight. Most consumers therefore test both, and the codebase spells it (ASTKind[n] = AN_CALL) or (ASTKind[n] = AN_VIRTUAL_CALL) in ir.inc, symtab.inc, ast_arena.inc and several places in pasparser_lval.inc.

Two do not:

site test what the sibling arm does for AN_CALL
pasparser_expr.inc:9023 (NodeEnumIdOf) ASTKind[node] = AN_CALL reads ProcRetEnumId[ASTIVal[node]] — "a call node has no symbol and no field, so the callee's row is the only place the result's enum identity survives"
pyparser.inc:35419 (ASTKind[node] = AN_CALL) and (CurTok.Kind in [tkDot, tkLBrack]) binds the receiver to a hidden temp via PyEvalOnce, because an IR value node is a SUBTREE and reusing one RE-EMITS the expression

If the reasoning in those two comments is right, then for a virtual call the first loses the enum identity of a method result (so if obj.kind() == Red compares against something with no enum row), and the second re-evaluates the receiver once per chain link (obj.make().a.b) — which that site's own header calls out as silent, and worse the longer the chain.

Why it is not attributable to the dunder-dispatch fix

PyParseMethodCallArgs has emitted AN_VIRTUAL_CALL for a hierarchy class since virtual slots landed, so obj.make() has been a virtual call all along. The dunder fix widened which nodes are virtual; it did not create the asymmetry. Confirm against pinned v376 before treating any repro as a regression.

First step

Write the two repros — an enum-returning method on a class with a subclass, and a chained call on the same — and measure them on pinned v376. If they pass, the comments are describing a hazard that some other mechanism already covers, and the right outcome is to close this ticket saying so. That is a real possible answer here; a grep finding is a hypothesis, not a bug.

Gate

Whatever the repros show. If it is a bug: both sites accept both kinds, plus a test-core row per site pairing the non-virtual arm with the virtual one, .expected from CPython.