2026-08-30 — REOPENED: the pin this was closed against has been WITHDRAWN
This ticket was closed against v394
e2ea9034a65e, which was blessed at ~06:40 and reverted at ~07:40 (b8fd07377) because it broke Track B's gate: a parameter typed as an alias ofPointerrejected a class instance that plainPointeraccepts, so vendored Synapse stopped compiling.pinnedis v3931d69760deabe2865again.The close was correct and the eleven-row verification stands as written — frankB re-ran every row and both controls on the v394 binary, not on HEAD. What has been withdrawn is the ground, not the result:
51b0753e7is still on master and the pin that carries it no longer exists.Returned to
unfinished/rather than left indone/, because the retirement condition is "the pin carries51b0753e7" and it does not. Re-close on the next pin by re-running the same rows; do not re-derive the answer.The withdrawal is the coordinator's, not this lane's — see face 188. A closure that cites a specific binary is a claim with a date on it, and this one outlived its subject by an hour.
tkinter facade: grid(padx=(8, 6)) — the two-tuple pad
- Type: feature (library surface, tkinter facade) — Track B.
- Filed: 2026-08-29 by the wasm lane against pin v392 (
60b060bb54a8).
CORRECTION 2026-08-29 — the diagnosis below is WRONG, the measurement is not
Written by the wasm lane, which filed this ticket and got its cause wrong. frankB investigated and changed nothing in
lib/pcl, which is the right outcome and the one this ticket as written would have prevented.The facade was already complete.
lib/pcl/tkinter.pas:104declaresconst padx: Variant = 0, and line 106 carries the comment "padx/pady are VARIANT: tkinter takes either a number or a (left, right)" — the exact semantics the "Fix" section below proposes adding. frankB confirmed the emitter too, by asking Tk itself rather than our own formatter:grid infoon a live widget reports-padx {8 6}.The real cause is [[bug-n-a-methods-keyword-call-drops-a-tuple-argument-when-an-earlier-default-is-skipped]]:
c = v6.TC(a=0, v=(8, 6)) # OK — constructor c.meth(a=0, v=(8, 6)) # FAILS — methodA method call, an earlier default left unbound, and an object-valued Variant are all three required. So this was never about tuples, and it explains the table below exactly:
padx=8works because scalars are unaffected;padx=(8, 6)fails becausesticky,columnspanandrowspanwere skipped ahead of it. Re-verified here — the app's spelling fails,grid(row=0, column=0, sticky="e", columnspan=1, rowspan=1, padx=(8, 6), pady=2)compiles, andgrid(padx=(8, 6))alone also fails, which the tuple theory cannot explain and the skipped-default one predicts.How I got it wrong, since I had written the rule three times the same evening.
no overload of grid matches these argumentsplus "scalar works, tuple fails" reads as the tuple form is unimplemented — and that reading is available without opening a single file. I never openedtkinter.pas. This is the ticket I closed hours earlier saying a diagnostic that names a cause may be naming the discriminator, and the cost of checking was one grep. I skipped it because the story was coherent, and a coherent story is precisely the input that makes verification feel unnecessary.Operational form, because "verify your claims" is too weak to act on: a ticket that proposes a fix must QUOTE the code it proposes to change — not cite it, quote it. I could not have written "padx must become Variant" underneath a line reading
const padx: Variant = 0.The one-line workaround is deliberately NOT taken. Naming every option is legitimate and is not a compiler-appeasement reshape — nothing is renamed or rerouted, the app just spells out options it already sets. It is still an app-source edit, and this track's principal goal is that songformatter's source stays unmodified CPython, to the point that the single app-side change that does exist (the fallback import) is scheduled for removal once dotted imports land. Spending the principal goal to save one N ticket is the wrong trade from here.
settings.pystays as CPython wrote it and waits on the N fix. If the owner wants the app moving tonight at that price, the spelling is in the paragraph above and it works.Everything below is the original filing, left intact. The measurement table is sound; the "Fix" section is the part that was invented.
Repro
import tkinter as tk
root = tk.Tk()
lbl = tk.Label(root, text="x")
lbl.grid(row=0, column=0, padx=(8, 6), pady=2)
pascal26:4: error: no overload of grid matches these arguments
| call | result |
|---|---|
grid(row=0, column=0) |
ok |
grid(..., padx=8, pady=2) |
ok |
grid(..., sticky="e", padx=8, pady=2) |
ok |
grid(..., padx=(8, 6), pady=2) |
fails |
So the whole option surface is fine and it is specifically the tuple form.
What tkinter means by it
padx/pady take either a scalar (that much padding on both sides) or a
2-sequence (before, after) — left/right for padx, top/bottom for pady. Tcl
receives it as a two-element list, so the facade needs to emit -padx {8 6}
rather than a single number. pack takes the same forms and should get the same
treatment in the same pass; place does not have these options.
Note the brace-quoting half is already understood here: a multi-word option
value reaching Tk unbraced was the -scrollregion 0 0 500 1026 bug fixed during
pass seven of [[feature-demo-songformatter-pxx-target]], where three of the four
numbers arrived as stray arguments. Same shape, so the same escaping applies.
Why it is worth doing now
settings.py:183 is the only remaining wall in that module —
label.grid(row=row, column=0, sticky="e", padx=(8, 6), pady=2) — and
settings.py otherwise compiles and runs, building all 60 of its widgets. This is
the last thing between it and a clean build.
Gate
Track B's: make lib-test / make demos green, built with $(PXX_STABLE)
(never rebuild the compiler), plus the repro above compiling and the padding
actually applied asymmetrically on screen — a scalar fallback that silently
ignores the second element would pass a compile check and is the wrong fix.
Triage (frank-b, 2026-08-29) — NOT a Track B defect; blocked on Track N
The facade is already complete. padx/pady on both grid and pack are
already Variant, and TkiOptPad already renders the (before, after) pair as
Tk's braced list. Nothing in lib/pcl/tkinter.pas needed changing, and I
changed nothing.
The gate's own requirement — "the padding actually applied asymmetrically ...
a scalar fallback that silently ignores the second element would pass a compile
check and is the wrong fix" — is met today. Verified by asking Tk, not our
own formatter (grid info on the live widgets under xvfb):
pair : ... -padx {8 6} -pady 2 -sticky e
scalar: ... -padx 8 -pady 2 -sticky e
pady : ... -padx 1 -pady {3 9} -sticky e
What actually rejects the call is a NilPy argument-binding bug, filed as [[bug-n-a-methods-keyword-call-drops-a-tuple-argument-when-an-earlier-default-is-skipped]]. A keyword call to a method that leaves an earlier defaulted parameter unbound rejects an object-valued Variant argument. The same signature and the same call bind correctly through the instantiation path and through a unit-level procedure — same class, only the path differs:
c = v6.TC(a=0, v=(8, 6)) # OK
c.meth(a=0, v=(8, 6)) # no overload of meth matches these arguments
Which is why the ticket's own table reads the way it does: grid(..., padx=8)
works because a scalar is not affected, and grid(..., padx=(8, 6)) fails
because three options before it were left unbound. Supply them all and the
tuple form compiles and works today:
lbl.grid(row=0, column=0, sticky="e", columnspan=1, rowspan=1, padx=(8, 6), pady=2)
That is the workaround available now for settings.py:183 if the wasm lane
does not want to wait for the N fix — it is one line and it is not a
compiler-appeasement reshape of library code, it is an application spelling
every option it is already setting.
Why this ticket stays open rather than being resolved: its ask is that the
short spelling work, and it does not. But there is nothing left for Track B to
do, so it is parked on the N bug rather than held in working/.
Re-verified 2026-08-30 (frankB) — the blocker is closed and this still fails, for a reason that is not a defect
[[bug-n-a-methods-keyword-call-drops-a-tuple-argument-when-an-earlier-default-is-skipped]]
resolved earlier today. tools/progress.sh check then flagged this ticket as a
STALE-EDGE: sitting in blocked/ with every blocker closed, which makes it
invisible to ready/next forever. Re-measured before moving it, and the
answer is neither "fixed" nor "still broken":
The fix is not in the pin, and Track B builds only with the pin. Pin is
v393 (1d69760deabe, blessed 2026-08-29 20:28 at 1fb9774b7417); the N fix
landed on master on 2026-08-30, after it. compiler/pascal26 in this tree is
byte-identical to pinned, so there is no binary here that carries the fix and
this lane does not build one.
Measured through v393 — the boundary is unchanged and exactly as the correction above describes:
grid(row=0, column=0, sticky="", columnspan=-1, rowspan=-1, padx=(8, 6), pady=2) COMPILES
grid(row=0, column=0, padx=8) COMPILES
grid(row=0, column=0, padx=(8, 6)) FAILS
grid(padx=(8, 6)) FAILS
A NilPy-defined class with the same shape (c.meth(a=0, v=(8, 6)) with an
earlier default skipped) passes through v393. That is not evidence the fix
arrived — it is the control that confirms the resolution's own diagnosis: the
refusal comes from FindUMethOverloadAhead, the speculative overload probe,
which a Pascal-declared façade method goes through and a NilPy class method
does not. Two paths, one shape; only the façade arm is affected, and only the
façade arm is what tkinter is.
So the gate is now pin advancement, not a ticket. blocked-by is emptied
because it named a closed ticket, and the ticket moves to unfinished/ rather
than backlog/ — putting it in the ranker would dispatch work that cannot be
done from this lane until the next make pin picks up the N fix.
To retire it: after the next pin, re-run the four spellings above. If rows 3
and 4 compile, close it — lib/pcl needs no change, which was this ticket's
finding all along. If they still fail, that is a new N/A finding against a pin
that contains the fix, and it should be filed as one rather than reopened here.
Retirement test WIDENED 2026-08-30 — grid is one of four, not one
Re-checked at HEAD: the pin is still v393 (1d69760deabe), unchanged, so
the four spellings above still behave exactly as recorded and the ticket is
still gated on pin advancement. Nothing to build. But the retirement test as
written checks one method, and that is too narrow — for the same reason two
other tickets went wrong tonight: an acceptance aimed at the reported symptom
leaves the siblings unverified.
The N fix ([[bug-n-a-methods-keyword-call-drops-a-tuple-argument-when-an-earlier-default-is-skipped]],
51b0753e7) is general — OverloadArgParamIdx resolves an argument to its
declared parameter in FindUMethOverloadAhead, and its commit message states
that settings.py compiles at lines 178 and 183. So grid will pass. The
question the four spellings cannot answer is whether the rest of the façade
passes with it.
The at-risk shape, enumerated over lib/pcl/*.pas
A façade method is exposed when a Variant parameter can be reached with an
earlier defaulted parameter left unbound. Eight declarations match; two are
unit-level functions, which the parent ticket's own boundary table says take
a different path (no probe), and they are confirmed unaffected below. That
leaves six methods, four of which have a call a real application would write:
| method | the exposed parameter | today, at v393 |
|---|---|---|
Widget.grid |
padx / pady |
FAILS |
Widget.pack |
padx / pady |
FAILS |
Canvas.configure / .config |
scrollregion, yscrollcommand |
FAILS |
Canvas.create_text |
font |
FAILS |
Text.tag_configure |
underline |
shape present; a tuple underline is not a call anyone writes, so untested |
askopenfilename / asksaveasfilename |
filetypes |
compiles — unit-level function, not a method |
Measured now, so the post-pin check has a baseline rather than a guess:
pack(padx=(8, 6)) FAILS: no overload of pack
pack(side="left", fill="x", expand=0, padx=(8, 6)) COMPILES
configure(scrollregion=(0, 0, 100, 100)) FAILS: no overload of configure
configure(state="normal", scrollregion=(0,0,1,1)) COMPILES
create_text(1.0, 2.0, "hi", font=("Arial", 12)) FAILS: no overload of create_text
create_text(..., anchor="w", fill="red", font=...) COMPILES
askopenfilename(filetypes=[("a", "*.b")]) COMPILES <- the control
Each failing row has its own passing row directly underneath, differing only in
whether the earlier defaults are named. Same diagnostic, same signature, same
mechanism. And askopenfilename is the control that keeps the claim honest: it
has the identical parameter shape and an object-valued argument, and it compiles
today — because it is not a method, exactly as the parent ticket predicted.
To retire this ticket
After the next pin, re-run all seven rows above plus the original four
grid spellings. If every FAILS row flips to COMPILES and the two COMPILES
controls stay green, close it — lib/pcl needs no change, which was this
ticket's finding all along. If any FAILS row survives, that is a residue of
51b0753e7 in a sibling it did not reach, and it is a new N ticket with
this table as its boundary — not a reopen of this one.
Still in unfinished/ and still owned by this lane: it cannot be completed from
Track B until the pin carries the fix, and claiming it into working/ would be
a lock over work that cannot proceed.
Pre-answered at HEAD, 2026-08-30 — every row flips, but the pin has NOT moved
The retirement run above was executed early, against a compiler built from
source at HEAD rather than against $(PXX_STABLE). This does not retire the
ticket — Track B ships against the pin, the pin is still v393
(1d69760deabe2865), and nothing in lib/pcl changes either way. It answers
the one question the wait was hiding: will the post-pin check close this
cleanly, or fragment into an N ticket? Cleanly.
Provenance of the binary under test, because a verification build that silently no-ops is the failure mode here:
worktree at HEAD d9b663137, seed = stable_linux_amd64/default/stable_pinned
converged after 2 round(s)
self-host fixedpoint: verified — 2 round(s), 837193ea839c
Built sha 837193ea839c differs from the seed 1d69760deabe, and converged
was printed — so the compiler under test is genuinely HEAD's, not the pin's
wearing a new mtime.
All eleven rows — the seven above plus the original four grid spellings:
grid(row=0, column=0, padx=(8, 6), pady=2) COMPILES
grid(padx=(8, 6)) COMPILES <- was FAILS
pack(padx=(8, 6)) COMPILES <- was FAILS
pack(side="left", fill="x", expand=0, padx=(8, 6)) COMPILES
configure(scrollregion=(0, 0, 100, 100)) COMPILES <- was FAILS
configure(state="normal", scrollregion=(0,0,1,1)) COMPILES
create_text(1.0, 2.0, "hi", font=("Arial", 12)) COMPILES <- was FAILS
create_text(..., anchor="w", fill="red", font=...) COMPILES
askopenfilename(filetypes=[("a", "*.b")]) COMPILES
Nine of nine green is exactly the shape that should be distrusted, because a compiler that accepted everything would produce the same table. Controls, on the same binary:
grid(nosuchkeyword=(8, 6)) REJECTED: Widget.grid has no parameter named 'nosuchkeyword'
lbl.no_such_method(...) REJECTED: Label has no method no_such_method
grid(row=0, column=0, padx=(8, 6), pady=2) binary built, runs, prints "grid-pad ok"
Rejection is still real and still specific to the right thing, and the accepted
row does not merely typecheck — it links and runs. So 51b0753e7 reached all
four façade methods, not just grid; there is no sibling residue and no new N
ticket to file.
What is left is purely the pin. When the pin moves past 51b0753e7, re-run
the eleven rows against $(PXX_STABLE) and close this — the answer is already
known, but the ticket's gate is the stable compiler, and a pre-answer at HEAD
is not that. Until then it stays in unfinished/.
2026-08-30 — CLOSED against the pin. v394 carries the fix.
The pin moved to v394 e2ea9034a65ea8b6 (cc5e02d6c), which contains
51b0753e7. That is the gate this ticket was written against — Track B ships
against $(PXX_STABLE), and the HEAD pre-answer above was explicitly not that.
Re-run against the pinned binary, all eleven rows:
grid(row=0, column=0, sticky="", columnspan=1, rowspan=1, padx=(8,6), pady=2) COMPILES
grid(padx=(8, 6)) COMPILES
grid(row=0, column=0, padx=8) COMPILES
grid(row=0, column=0, padx=(8, 6)) COMPILES
pack(padx=(8, 6)) COMPILES
pack(side="left", fill="x", expand=0, padx=(8, 6)) COMPILES
configure(scrollregion=(0, 0, 100, 100)) COMPILES
configure(state="normal", scrollregion=(0,0,1,1)) COMPILES
create_text(1.0, 2.0, "hi", font=("Arial", 12)) COMPILES
create_text(..., anchor="w", fill="red", font=("Arial", 12)) COMPILES
askopenfilename(filetypes=[("a", "*.b")]) COMPILES
Controls re-run on the same binary, because eleven-of-eleven green is the shape that should be distrusted and the pre-answer's controls were a different compiler's:
grid(nosuchkeyword=(8, 6)) REJECTED: Widget.grid has no parameter named 'nosuchkeyword'
lbl.no_such_method(...) REJECTED: Label has no method no_such_method
all four facades in one program -> builds, links, RUNS, prints "all four facades ok"
Closing per this ticket's own retirement text: every FAILS row flipped, both
COMPILES controls stayed green, so it closes and lib/pcl needs no change —
which was this ticket's finding all along. No sibling residue, so there is no new
N ticket: 51b0753e7 reached all four façade methods, not only grid.
The HEAD pre-answer of earlier today predicted exactly this and was right; it is left above as the record of what was known before the pin, not restated as if it were the close. The close is this section, and its evidence is the pinned binary.
Log
- 2026-08-30 — resolved, commit 39c9a2e54.
2026-08-30, same day — REOPENED. The pin was reverted; the close is no longer true.
v394 was reverted to v393 (b8fd07377) because it broke Track B's gate — an
unrelated defect, [[bug-p-a-pointer-type-alias-rejects-a-class-instance-that-plain-pointer-accepts]].
So $(PXX_STABLE) no longer carries 51b0753e7, and the close above rests on a
binary that is no longer pinned.
Verified rather than assumed — re-run on the reverted pin:
grid(padx=(8, 6)) FAILS: no overload of grid
pack(padx=(8, 6)) FAILS: no overload of pack
configure(scrollregion=(0, 0, 100, 100)) FAILS: no overload of configure
create_text(1.0, 2.0, "hi", font=(...)) FAILS: no overload of create_text
grid(row=0, column=0, padx=8) COMPILES <- control, still green
Back to unfinished/. The eleven-row close above stands exactly as written and
should not be re-derived — it is a correct measurement of v394, which is a real
binary that really carried the fix. Only the sentence "that is the gate this
ticket was written against" has stopped being true, because the pin moved back
underneath it. When the next pin lands with 51b0753e7, re-run the eleven rows
and the controls against that binary and close again, naming its sha.
This is the third state this ticket has been in today and the distinction between them is the whole point: a HEAD pre-answer (known, not closable), a pinned close (closable, closed), and a reverted pin (closed prematurely, reopened). Each was correct about the binary it named.
2026-08-30 — CLOSED AGAIN, against pin 53800fbeb0b66e11. This is the third pin and the last.
The alias regression that withdrew v394 is fixed and re-pinned. $(PXX_STABLE)
is now 53800fbeb0b66e11, measured with sha256sum stable_linux_amd64/default/pinned
at the top of this run rather than recalled from the dispatch that announced it.
The eleven rows were re-RUN, not re-derived. They were correct against v394; what had been withdrawn was the ground, not the analysis. Re-deriving would have spent a second full pass to reproduce a known result, with the added risk that the two passes differ in some detail and nobody can say which was right.
grid(row=0, column=0, sticky="", columnspan=1, rowspan=1, padx=(8,6), pady=2) COMPILES
grid(padx=(8, 6)) COMPILES
grid(row=0, column=0, padx=8) COMPILES
grid(row=0, column=0, padx=(8, 6)) COMPILES
pack(padx=(8, 6)) COMPILES
pack(side="left", fill="x", expand=0, padx=(8, 6)) COMPILES
configure(scrollregion=(0, 0, 100, 100)) COMPILES
configure(state="normal", scrollregion=(0, 0, 1, 1)) COMPILES
create_text(1.0, 2.0, "hi", font=("Arial", 12)) COMPILES
create_text(1.0, 2.0, "hi", anchor="w", fill="red", font=("Arial", 12)) COMPILES
askopenfilename(filetypes=[("a", "*.b")]) COMPILES
Eleven of eleven, identical to the v394 table row for row. Controls on the same binary, because eleven-of-eleven green is the shape a compiler that accepted everything would also produce:
grid(nosuchkeyword=(8, 6)) REJECTED rc=1: Widget.grid has no parameter named 'nosuchkeyword'
lbl.no_such_method(1, 2) REJECTED rc=1: Label has no method no_such_method
all four facades in one program -> builds, links, RUNS under xvfb, rc=0, prints
"all four facades ok"
Rejection is still real and still specific to the right thing, and the accepted program does not merely typecheck — it links and runs.
Each compile's exit status was read from the compiler's own invocation
(if $P ... > log 2>&1; then), not from a tail or an echo after it. For an
eleven-row table that distinction is the difference between a verification and
eleven confident greens: cmd > log 2>&1; tail log reports tail's status and
{ cmd; echo "rc=$?"; } reports the echo's, and both read as 0 for a red run.
lib/pcl needs no change — that was this ticket's finding from the first
triage onward, and three pins have not altered it. The facade was already
complete; what was broken was NilPy argument binding, fixed by 51b0753e7, and
this ticket was only ever waiting for a pin that carried it.
Four states in two days, and the distinction between them is the point: a HEAD pre-answer (known, not closable), a pinned close (closable, closed), a reverted pin (closed prematurely, reopened), and now a close against a pin that exists. Each was correct about the binary it named — which is why every one of them named it.
- 2026-08-30 — resolved, commit 39c9a2e54.