← board

NilPy: Exception(msg) — the root class takes no arguments

Left out of [[feature-nilpy-exceptions]] (landed cc833c2d) because uforth's shape — class ForthThrow(Exception) with its own __init__ and self.code — works without it. try / except / finally / raise are all in.

Repro

try:
    raise Exception("neg")
except Exception:
    print("caught")
pascal26:2: error: undefined variable (Exception)

Exception is a lazily registered EMPTY shell (PyEnsureExceptionClass in pyparser.inc): it exists as a class for inheritance and for handler matching, but has no constructor, so it is not callable, and there is no name binding for it in expression position.

Shape

Measure before building: census how many corpus sites actually construct a bare Exception with a message versus a typed subclass carrying its own fields.

Gate

test-nilpy green with a .npy case diffed against CPython + --tier quick + self-host byte-identical.

Census (2026-07-21) — it IS needed, broadly

uforth.py has 85 raises of built-in exception classes with a message (Exception/ValueError/RuntimeError/KeyError/TypeError/IndexError(msg)) in its NilPy-compiled body, not just typed ForthThrow subclasses. So the initial "works without it" is wrong for the full port: the built-in exception classes must be constructible with a message and answer str()/print(). pylib ALREADY provides the runtime (Exception has msg + Create(m); ValueError etc. are subclasses that inherit it) — the gap is frontend wiring: PyEnsureExceptionClass registers an EMPTY UClass, and the built-in names are not callable in expression position. Prio bumped 45 -> 55.

2026-07-21 — PARTIAL: print(e) landed; construction/catch/field already worked

Re-tested with the current compiler: raise Exception("m") construction, except matching, and e.msg field access ALL already work (ticket's "undefined variable" was stale). The remaining gap was the message READBACK.

Landed: print(e) of an Exception (or subclass) now shows its message — PyReprContainer detects an exception-typed print arg (FindUClass('Exception') + IsSubclassOf) and reads the .msg field instead of the instance pointer. Test test/test_nilpy_exception_print.npy; test-nilpy + quick green, self-host byte-identical.

UPDATE: str(e) now landed too (same Exception->.msg rewrite in the str() call lowering in parser.inc). print(e) + str(e) + ".." + str(e) all show the message. STILL OPEN: only f"{e}" (the f-string hole path) yields empty — the str()-call / f-string-hole lowering of a class instance does not route through .msg. The str() call-lowering site was not located in this pass (str is recognised as returning tyString at 189/355 but the value lowering is elsewhere). Next: find where str(<classinstance>) lowers and apply the same Exception→.msg rewrite (or give the frontend Exception UClass a real str path). Keep this ticket open until str(e)/f-string parity lands.

Log