← board

uses sysutils, pylib fails to compile; uses pylib, sysutils is fine

Pre-existing (reproduces on stable_linux_amd64/default/pinned). A two-line program is enough:

program t;
uses sysutils, pylib;      { swap the two and it compiles }
begin
  WriteLn(1);
end.
error: undefined variable (msg)
  near:  AnsiString   begin msg >>>  m

The failure is inside pylib's own Exception.Create, where msg := m no longer resolves msg to the class's field. pylib declares an Exception class and so does sysutils; with sysutils compiled first, pylib's constructor body binds against the wrong class and its field vanishes. Which unit is named first should not change whether a library compiles.

Related, and probably the same root: [[bug-nilpy-rtl-exception-surface-shadowed]] — pylib's Exception shadows sysutils', so Exception.CreateFmt is missing from NilPy. One of the two classes has to win properly, or they have to be one class (the "ONE Exception class" work in 873a693e did that for the NilPy side).

Where it bites

lib/pcl/mimic_reportlab_pdfgen.pas needs both (pylib for Python-shaped keyword arguments, sysutils for raise Exception.Create) and carries a comment pinning the order. Any library in the same position has to know this folklore.

Gate

make test + a regression test with both orders, and the pylib/sysutils Exception surfaces both reachable.

Log

REOPENED 2026-08-13 — the 2026-08-01 fix did not hold, and could not

Track T's watcher went RED at 1df75aad5458 on test-core#src:test/test_uses_order_pylib_exception_a.pas — the very regression test this ticket's Gate asked for. It did its job.

The original two-line repro at the top of this file fails again, verbatim, against a compiler rebuilt at HEAD:

$ ./compiler/pascal26 /tmp/uo_orig.pas /tmp/uo_orig26
pascal26:4824: error: "argsv": no such member on this record/class
  near: k    e  >>> argsv  TPyList

Same program, same uses order, same mechanism — a different field. In August it was msg; now it is argsv, at compiler/builtin/pylib.pas:4824 (e.argsv := TPyList.Create). Reverse the uses clause and it compiles; the _b.pas companion, which names pylib first, still passes.

So this was a microfix, and the root cause is untouched

382b75e54 made the then-existing fields of pylib's Exception resolve. It did not fix "pylib's own implementation binds Exception against sysutils' class when the CONSUMER names sysutils first". That defect sat dormant for twelve days purely because nobody added a field — and the moment 67910b097 feat(N): e.args, and the KeyError repr it unblocked added one, it returned unchanged.

Any new member on pylib's Exception re-breaks uses sysutils, pylib. That is the actual bug, and it is a booby trap for every future N/B change: the lane that adds the field is not the lane that owns the defect, and the failure surfaces far from the edit.

Track N is NOT at fault — do not route it there

The obvious reading is "the feat(N) commit turned it red, so N broke it". It did not. Adding a field to a class your own unit declares is unremarkable code. 67910b097 is the trigger, not the cause, and reverting it would only re-arm the trap for the next field. This ticket stays Track A, where the name-resolution defect lives.

Why the Gate passed in August and the bug survived

The Gate asked for "a regression test with both orders". It got one, and the test is good — it caught this within hours. But a test pinned to the fields that existed on the day cannot prove the class of bug is gone. Whatever fix lands now should be checked by adding a throwaway field to pylib's Exception and confirming uses sysutils, pylib still compiles — that is the property, and it is the one thing neither the old fix nor the old Gate established.

See devdocs/dev/root-cause-over-microfix.md; this is a worked example of exactly what it warns about, including the part where the microfix looked complete because the symptom was gone.

Log

2026-08-14 — CORRECTED, and superseded by a Track U decision

Two things in my 2026-08-13 reopen above need fixing, and the second matters.

Master is no longer red. feat(N) e.args was reverted the same night; plexus is GREEN across native, full, slow and opt at 618371ac365d with zero open regressions. The summary's "master is red" was true when written and is not now.

And "the 08-01 fix was a microfix, root cause untouched" is too simple. Track N measured what I did not: ClassNameIsDeliberatelyShared('exception') exempts the name from FindUClass's own-unit preference on purpose, and that exemption is what makes a bare except Exception: catch a raise from either runtime. Removing it was tried — pylib then sees its own class correctly and the unification breaks, so _b fails at run time. The exemption is load-bearing exactly as designed.

So this is not a fix someone neglected to finish. It is a deliberate tradeoff whose cost was invisible until the first member was added that sysutils lacks, and the honest statement of the defect is: pylib can never extend Exception beyond sysutils' surface. My reopen was right that the class of bug survived, and wrong about why.

Do not act on this ticket. The fork belongs to [[decide-pylib-exception-vs-sysutils-exception]] (Track U, p55) — who owns Exception, and how pylib extends it. Whatever is decided there determines whether this ticket becomes an A-lane resolution change (its option 4), a B-lane RTL change (option 1), or is closed as working-as-designed (option 3). Prio stays 70 so it does not drift out of sight while that is open.

RESOLVED 2026-08-14 — by construction, not by a fix here

uses sysutils, pylib compiles, and so does the reverse, because there is no longer a name to fight over: pylib's Python root is PyException ([[decide-pylib-exception-vs-sysutils-exception]] option 5, commit 6ed45773f). Exception means sysutils' class in a Pascal program, full stop, and pylib's own constructor bodies resolve msg against their own class in every uses order.

test_uses_order_pylib_exception_a and _b now print identical output — _b's recorded expectation was [%5d], which was the order-dependence this ticket describes, written down as if it were correct. Both are [ 3] now (sysutils' padded CreateFmt), and that equality is what the pair asserts.

ClassNameIsDeliberatelyShared, the mechanism this ticket is about, is deleted. The "design tradeoff with a measured cost" in the summary above is no longer a tradeoff anyone has to make.