← board

exec(src, env) refuses a host call unless env has a "vm" key

Repro, diffed against CPython

b = B()
env1 = {"push": b.push}                 # no "vm"
env2 = {"vm": b, "push": b.push}
exec("push(41 + 1)", env2)   # ok, both
exec("push(41 + 2)", env1)   # CPython: fine.  pxx: dies
pxx:      with-vm 42
          pyeval: host call push but no "vm" in globals
CPython:  with-vm 42
          without-vm 43

Why it matters

The ticket that built this interpreter states the contract as "semantics matching CPython's explicit-dict form: no ambient scope capture, the host passes name -> value bindings". A binding named vm is not part of that contract — it is uforth's variable name, and it has leaked out of the corpus that drove development into the general surface.

Any other consumer writes {"canvas": c, "draw": c.draw} or {"doc": d, "emit": d.emit} and every host call fails, with a message that names an identifier the caller never wrote and cannot guess the significance of.

It is at least LOUD — it errors rather than silently calling the wrong thing.

Where to look

The host-call path in compiler/builtin/pyeval.pas looks the receiver up by the fixed name vm instead of taking it from the bound method's own {recv, method-ref} pair, which is where it already is: push resolves correctly as a bound method, so the receiver is in hand before the lookup happens.

Gate

make test-nilpy + self-host byte-identical, plus a .npy running the repro above with env keys that are NOT called vm, diffed against CPython.

Log