tkinter is missing from the Python-serving unit list
- Type: bug (Track A —
compiler/parser.inc, the NilPy import resolver). Falls out ofregression-cascade-4e27dc2be114; filed rather than fixed because it edits the sharedparser.incand frank3 holds the A/P slot. - Found: 2026-08-19, triaging the cascade.
What is wrong
e1109d7bc feat(A,N): a bare NilPy import resolves to Python, not a Pascal unit
closes the .pas chain to a bare NilPy import, except for the units named in
PyRtlUnitServesPython (compiler/parser.inc) — the curated list of units that
ARE the Python module of that name. tkinter is not in it.
It should be. lib/pcl/tkinter.pas's own header states the property as a design
decision, and names two units that ARE on the list in the same sentence:
Named
tkintersoimport tkinterresolves through the unit resolver, like lib/rtl/re.pas and configparser.pas.
So this is not a unit that "happens to share a Python name" — the exclusion the
list's design note is written around (classes, types, strings). It is the
inclusion case, missed because it lives in lib/pcl while the list was built by
sweeping lib/rtl. The note anticipated exactly this failure:
ADDING A UNIT TO THIS LIST IS PART OF WRITING ONE. [...] the failure lands at the import of a unit that plainly exists, far from the omission that caused it.
Repro (any of the six)
./compiler/pascal26 examples/tk/tkinter_facade.npy /tmp/out
pascal26:5: error: import: tkinter is the Pascal unit .../lib/pcl/tkinter.pas,
not a Python module — a bare NilPy import resolves to Python (.py/.npy) only.
To reach the Pascal unit, name it with its extension: import 'tkinter.pas' as tkinter
Why the tests must NOT be rewritten here
The other half of the cascade was test fixtures importing Pascal units under
test/nilpy_units/, and those were correctly rewritten to the quoted spelling.
These six are the opposite case. import tkinter as tk is what every real
tkinter program on earth contains; rewriting the examples to import 'tkinter.pas' as tk would make them show a spelling no Python source has, and
would put NilPy's flagship GUI surface out of reach of unmodified CPython code —
against the upward-compatibility rule (if it works on CPython it must work on
NilPy).
Verified fix
One name added to PyRtlUnitServesPython:
(lo = 're') or (lo = 'subprocess') or (lo = 'tempfile') or
(lo = 'tkinter');
Measured 2026-08-19 against a self-host fixedpoint built on 5b1539fc7 with
that one line applied ("converged after 1 round(s)"): all six of
examples/tk/{callbacks,facade_and_paths,field_class_identity,import_in_body,shadow_format_except,tkinter_facade}.npy
compile OK, with no edit to any example or test. Reverted afterwards — this
checkout does not hold the A/P slot.
Also worth deciding (not this ticket)
There is no spelling for a from-import of a Pascal unit: from 'basehook.pas' import ConfigBase is refused with expected a module name after from. That cost
test/test_nilpy_subclass_unit_base.npy one of its three assertions (recorded in
that file's header). Whether the quoted form should be accepted after from is a
Track U call, filed as decide-should-from-accept-a-quoted-foreign-file.
Log
- 2026-08-19 — resolved, commit b47ab6f3f.
RESOLVED 2026-08-19 by frank3 (A/P slot holder)
One name added to PyRtlUnitServesPython (compiler/parser.inc), plus an amendment
to the list's own doc comment.
Why the comment needed changing too. It read "When a new lib/rtl unit is
written to serve a Python module…" — describing the sweep that caused this omission
rather than the question the list answers. tkinter is lib/pcl's. Where a unit
lives is not the criterion; what it is FOR is. The comment now says so, so the next
lib/pcl or lib/* unit written to serve a Python module is not missed the same way.
Verified (gate.sh quick cannot see test-core, so the Makefile recipe lines
were run directly):
- all seven
examples/tkcompile jobs —tkinter_facade,uses_tkinter_and_configparser,field_class_identity,callbacks,import_in_body,shadow_format_except,facade_and_paths(frank2 reported six; there are seven) - the three Xvfb run-and-diff jobs — output identical to
.expected - both
test/jobs naming tkinter, includingtest_nilpy_renamed_class_is_not_a_module, whose point is a refusal and which therefore had to be checked for the right failure rather than for success - self-host fixedpoint converged;
gate.sh quickGREEN
Still refused, and correctly — NOT part of this bug
examples/tk/hello.npy and examples/tk/widgets.npy do import tk, which is
lib/pcl/tk.pas — the thin Tcl/Tk binding that tkinter.pas is built ON TOP OF, not
Python's module. These two are the genuine rewrite case (import 'tk.pas' as tk) and
were left for the cascade cleanup.
CORRECTED, same day. I first justified this with "CPython has no tk module, so
no unmodified Python source can name it" — i.e. stdlib membership. That rule is
wrong and must not be reused. A PyPI package named tk exists and installs, so a
Python program can import tk; and tkinter itself needs installing on most distros
(python3-tk), so "needs installing" separates nothing either. Anything can be
published to PyPI, so "could a Python program have written this import?" answers yes
for essentially every identifier and classifies nothing. Measured and challenged by
the owner via frankonpiler-bf.
The criterion is the one this ticket's own comment fix already states: is the unit
written to BE that Python module? Purpose, declared by the unit, not inventory.
Both headers answer it outright — tkinter.pas says it is named tkinter precisely
so import tkinter resolves through the unit resolver; tk.pas describes a Tcl/Tk
embed and points at building a tkinter-shaped surface on top of it. Note the failure
mode this is guarding against: I applied the strong criterion to the list's comment
and then reached for the weak one when arguing a case, in the same session.
The conclusion survives and gets stronger under the correct rule. Precisely
because a PyPI tk exists, putting tk on the Python-serving list would mean a
program that had installed it finds its bare import tk silently bound to our Tcl/Tk
binding — the wrong module, quietly, which is the exact class of bug this list exists
to prevent.
The pair still states where the line falls: same directory, same feature, opposite answers.