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
- Make
Exceptionconstructible with an optional message argument, stored in a field. str(e)/print(e)should yield that message, which is how Python code reads it back. That is the part with real surface area — it means the boxed exception has to answer the str path.e.argsis probably not worth paying for; check the corpus first.
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
- 2026-07-31 — resolved, commit c94f7ba7e2df10bc6d1ed623e6201969f6a9ac78.