NilPy: dispatch a method call on a VARIANT receiver at RUNTIME
Raised 2026-07-20 during the uforth drive, twice.
The problem
A method call on a value with no static class — wordlists.get(wid, {}).append(w)
— is resolved by NAME across every declared class. When two unrelated classes
declare the name, the frontend cannot decide and errors:
Nil Python: .append() on a dynamically-typed value is ambiguous
How it has been dodged so far, and why that runs out
- TPyList.get / TPyBytes.get -> .at. Legitimate: Python lists and
bytearrays have no
.get, so those were internal accessors squatting on the real TPyDict API. Removing the collision was correct, not a workaround. - append/extend: a NARROW documented preference for TPyList (pyparser.inc,
search TPyBytes in the variant-method resolver). This one IS a workaround.
Python genuinely has both
list.appendandbytearray.append, so neither can be renamed. It leans on two facts: TPyList.append takes a Variant and so accepts anything, and a bytearray receiver is nearly always a statically-typed local that never reaches this path.
Dodge 2 is silently wrong for a dynamically-typed bytearray receiver. There is no third rename available — the next collision has to be solved properly.
Shape
The variant already carries VT_OBJECT plus a real class pointer, and is tests
against a class already work (isinstance is built on them). So the call can
lower to a runtime chain: test the receiver's class, dispatch to that class's
method, and fall through to a "no method" error. Costs one compare per
candidate, only on calls that are actually ambiguous — everything with a static
class keeps its direct call.
Consider also using the ARGUMENT types to narrow the candidate set first: for
.append(w) with a class-typed w, TPyBytes.append(Integer) cannot match, so
only TPyList survives and no runtime test is needed.
Recon 2026-07-31 — confirmed still genuinely open, not stale
Given how many other tickets this session turned out to already be fixed, checked this one for real too, rather than assuming: it is NOT stale. The exact silent-wrong-value case the ticket predicts is measured and reproducible:
def get_container(flag):
return bytearray() if flag else [1, 2]
c = get_container(True)
c.append(99)
print(c) # CPython: bytearray(b'c') pxx: b'\x01'
.append(99) on a dynamically-typed bytearray receiver dispatches to the
"append prefers TPyList" workaround and silently produces the wrong value
— no error, no diagnostic. The argument-type-narrowing idea in this
ticket's own "Shape" section does NOT resolve this specific case: an
Integer argument (99) is accepted by BOTH TPyBytes.append(Integer) and
TPyList.append(Variant) (Integer coerces to Variant), so only a genuine
RUNTIME class test on the receiver disambiguates it — the harder half this
ticket already anticipated. Not attempted this pass: it needs new runtime
type-dispatch codegen (test the receiver's class tag, branch to the
matching method), not a quick patch, and is real new machinery rather than
a stale-ticket verification like most of what else got closed today.
Gate
test-nilpy green with a .npy case that calls an ambiguous method name on a
dynamically-typed receiver of EACH candidate class and gets the right one
(diffed against CPython) + --tier quick + self-host byte-identical.