← board

Summary

RESOLVED 2026-09-11 — now a catchable TypeError, byte-identical to CPython on the try/except row. Inert for $(PXX_STABLE) consumers until a pin.

pyeval's PyHostCall reports too-few-arguments with writeln + Halt(1). CPython raises TypeError, which a program can catch. So a NilPy program that wraps a dynamically dispatched call in try/except TypeError cannot catch it — the process exits instead, with a message on stdout that is not a Python traceback and carries no line number.

Measured 2026-09-11, compiler f9fb672ee109

program pxx CPython
def f(d): d.get() on a dict pyeval: too few args to get (need 1, got 0), rc=1 TypeError: get expected at least 1 argument, got 0
def f(xs): xs.index() on a list too few args to index (need 1, got 0), rc=1 TypeError: index expected at least 1 argument, got 0
det.analyze(1) vs analyze(self, chords, fn, sections=None) too few args to analyze (need 3, got 1), rc=1 TypeError: Det.analyze() missing 1 required positional argument: 'fn'

The DIAGNOSIS agrees in all three rows. Only the delivery differs: an uncatchable exit versus an exception.

Why it is filed now, and why the prio is not lower than it looks

It was already reachable — every call the open-world NAME fall-through defers lands here. It became more reachable on 2026-09-11: the arity fall-through (bug-n-a-method-call-is-refused-on-arity-from-the-candidates-compiled-so-far-so-import-order-decides) deliberately routes a class of calls to this path that used to be refused at compile time. That trade is the right one — a refusal that import order decides is worse than a loud run-time error — but it moves programs onto a path whose failure mode is an exit, so the exit is now this frontend's problem rather than an edge of it.

Fix direction

raise TypeError.Create(...) in place of the writeln/Halt(1) pair in PyHostCall, wording it the way CPython does. TypeError is already raised a few lines below in PyDynMethN for the callable-field keyword refusal, so the class and the unit are both already there.

The one thing to check before changing it: PyHostCall is reached from the Pascal side as well as from NilPy, and a raise crossing a boundary that today gets a Halt is a different control flow for those callers. Establish who else calls it before swapping the mechanism — the message is the cheap half.

Not the same as too MANY arguments

Surplus arguments are dropped rather than reported — nargs above n is not checked at all, and CPython raises there too. Measured only in passing while looking at the too-few path, so it is recorded as unverified: establish it before fixing it.

RESOLVED 2026-09-11, same day it was filed

compiler/builtin/pyeval.pas, PyHostCall: the writeln + Halt(1) pair becomes raise TypeError.Create(...).

The prerequisite this ticket set for itself was one grep and it passed. Every caller of PyHostCall is in that unit — five of them — so no Pascal-side caller outside it sees the control-flow change, and a Halt is unrecoverable, so nothing could have been relying on it to return. Filing it with an unchecked prerequisite was the same shape as the arity ticket's cost estimate; checking it took a minute.

before after CPython
det.analyze(1) pyeval: too few args to analyze (need 3, got 1), process exits TypeError: analyze() missing positional argument(s): its parameter list holds 3 and only 1 could be bound TypeError: Det.analyze() missing 1 required positional argument: 'fn'
the same wrapped in try/except TypeError uncatchable, program dies caught: TypeError then still running caught: TypeError then still running

The catch row is byte-identical to CPython.

The count is deliberately NOT called "required", and the first draft got it wrong

The first wording said missing 2 required positional argument(s) where CPython says missing 1. All this path knows is mi^.Arity, which counts DEFAULTED parameters too. The gap itself is real — defaults are already bound by the time the check runs, measured: d.zqx(1) against zqx(self, a, b=5) answers 6 under both pxx and CPython, and det.analyze(1, 2) against analyze(self, chords, fn, sections=None) answers 7 under both. So the message now states only what the runtime can see. A diagnostic that over-counts is the same defect as one that under-reports, and it would have shipped inside a commit whose headline was "make the message truthful".

Inert until a pin

This is compiler/builtin/**. Programs built with $(PXX_STABLE) keep the Halt until the next pin carries it. make compiler/pascal26 prints converged at 465845b20d1e, the same sha as before the wording edit — the compiler binary is unaffected by it, which the stamp said first and a cleared stamp confirmed.

Not fixed here, and it was measured only in passing

Surplus arguments are still dropped rather than reported; nargs above n is not checked. CPython raises there too. Recorded as unverified in the body above and still unverified — establish it before fixing it.

Log