punk::lib 0.5.1: compat::lpop builtin-parity fixes, validated by differential matrix
Reviewed against the real 8.7/9 lpop via a 24-case differential (results,
final list state, error messages, errorcodes) - the compat proc was
correct on every success path; all divergences were error-path:
- out-of-range errors said 'tcl_lpop index ...' with no errorcode; now
the builtin's exact message and {TCL VALUE INDEX OUTOFRANGE}
- variable-access failures named the internal upvar link variable and
flattened detail (missing array element misreported as 'no such
variable'; unset var lost its TCL LOOKUP errorcode). Fixed by probing
with a plain read in the caller's frame BEFORE creating the upvar
link - through a link, or once a link merely exists, Tcl downgrades
the error class.
Finding: the assumed 8.6 divergence does not exist - 8.6's core index
parser already accepts TIP-176 M+N arithmetic, so compat::lpop now has
zero known divergence from the builtin (doctools note corrected).
compat.test extended (3 -> 10 tests): a differential parity matrix that
runs wherever the builtin exists (self-updating pin against future core
changes), static pins for values/errors/errorcodes that also run under
8.6 where no builtin exists, an unconditional M+N arithmetic pin, and
an 8.6-only pin that punk::lib installs lpop as the compat alias.
Green under Tcl 9.0.3 (9 pass/1 gated skip) and native 8.6.17
(9 pass/1 gated skip).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
#0.5.1 - compat::lpop builtin-parity fixes (validated by a 24-case differential matrix vs the 8.7/9 builtin, pinned in modules/punk/lib compat.test): out-of-range errors now use the builtin's message ('index "X" out of range' - was 'tcl_lpop index ...') and errorcode {TCL VALUE INDEX OUTOFRANGE}; variable-access failures (unset var / whole-array name / missing array element) reproduce the builtin's exact message and errorcode by probing with a plain read in the caller's frame BEFORE creating the upvar link (through a link, or once a link exists, Tcl flattens the detail - 'no such variable' loses its TCL LOOKUP errorcode and a missing array element misreports as 'no such variable'). No success-path changes. Doc note corrected: 8.6's core index parser already accepts TIP-176 M+N arithmetic, so compat::lpop has no known divergence from the builtin.
#0.5.0 - G-076: check::tclbug_console_deadspin_fixed_in set to 9.0.5 - the upstream f10d91c2d3 fix (ticket closed 2026-07-14, merged to core-9-0-branch whose head reports 9.0.5) passed the G-039 kill-procedure re-verification 2026-07-20 on a zig-built core-9-0-branch runtime with the watchdog disarmed (dead console delivered as a readable fileevent; repl exited via the eof path - needs punk::repl 0.5.2's repl_handler guard). Runtimes at or past 9.0.5 now neither warn via 'help tcl' nor arm the repl watchdog; earlier Tcl 9 windows runtimes keep both. Official release not required per the amended G-076 acceptance (self-built from upstream sources qualifies).
#0.5.0 - G-076: check::tclbug_console_deadspin_fixed_in set to 9.0.5 - the upstream f10d91c2d3 fix (ticket closed 2026-07-14, merged to core-9-0-branch whose head reports 9.0.5) passed the G-039 kill-procedure re-verification 2026-07-20 on a zig-built core-9-0-branch runtime with the watchdog disarmed (dead console delivered as a readable fileevent; repl exited via the eof path - needs punk::repl 0.5.2's repl_handler guard). Runtimes at or past 9.0.5 now neither warn via 'help tcl' nor arm the repl watchdog; earlier Tcl 9 windows runtimes keep both. Official release not required per the amended G-076 acceptance (self-built from upstream sources qualifies).
#0.5.0 - new check pair punk::lib::check::has_tclbug_nestedswitch_tracelines + tclbug_nestedswitch_tracelines_signature (pure facts-in/verdict-out classifier, G-076 console_deadspin style): live behavioural probe of upstream tcl tktview 5d5b1052280c976ea3d4 (execution-trace line mis-attribution for nested single-block switch arm bodies whose split-list index lands on a literal word of the switch command; punkshell-author-reported; punk-free repro scriptlib/developer/tcl_switch_traceline_repro.tcl, identical 8.6.17/8.7a6/9.0.3). The probe defines a dynamic-body proc, enterstep-traces inner arm 1 (affected index 1 -> reports 3) vs the default arm (index 5, beyond the 4-word command -> reports 2) and matches the characterized wrong/right signature - so detection flips automatically on a fixed Tcl (no version gate; followup steps for that day documented in the proc: re-verify with the repro script, flip the ns/cmdtrace.test GAP pins + canary, update the cmdtrace argdoc caveat, note fixed-in version). Surfaced via 'help tcl' warnings (level medium) alongside the other has_tclbug_* checks; probe is self-cleaning (temp proc/var removed in finally).
#0.5.0 - new check pair punk::lib::check::has_tclbug_nestedswitch_tracelines + tclbug_nestedswitch_tracelines_signature (pure facts-in/verdict-out classifier, G-076 console_deadspin style): live behavioural probe of upstream tcl tktview 5d5b1052280c976ea3d4 (execution-trace line mis-attribution for nested single-block switch arm bodies whose split-list index lands on a literal word of the switch command; punkshell-author-reported; punk-free repro scriptlib/developer/tcl_switch_traceline_repro.tcl, identical 8.6.17/8.7a6/9.0.3). The probe defines a dynamic-body proc, enterstep-traces inner arm 1 (affected index 1 -> reports 3) vs the default arm (index 5, beyond the 4-word command -> reports 2) and matches the characterized wrong/right signature - so detection flips automatically on a fixed Tcl (no version gate; followup steps for that day documented in the proc: re-verify with the repro script, flip the ns/cmdtrace.test GAP pins + canary, update the cmdtrace argdoc caveat, note fixed-in version). Surfaced via 'help tcl' warnings (level medium) alongside the other has_tclbug_* checks; probe is self-cleaning (temp proc/var removed in finally).
#0.4.3 - G-076: buginfo dicts gain an optional mitigated/mitigation axis (mitigated boolean + mitigation text) orthogonal to level - a check whose defect is covered by a shipped punkshell mitigation reports it so 'help tcl' can render the warning subdued while keeping the severity classification. has_tclbug_console_deadspin populates it: mitigated when punk::repl >= 0.5.0 (console liveness watchdog) is available to the runtime (udp-check-style version discovery without loading), with the watchdog scope described in the mitigation text (non-repl console reads remain exposed).
#0.4.3 - G-076: buginfo dicts gain an optional mitigated/mitigation axis (mitigated boolean + mitigation text) orthogonal to level - a check whose defect is covered by a shipped punkshell mitigation reports it so 'help tcl' can render the warning subdued while keeping the severity classification. has_tclbug_console_deadspin populates it: mitigated when punk::repl >= 0.5.0 (console liveness watchdog) is available to the runtime (udp-check-style version discovery without loading), with the watchdog scope described in the mitigation text (non-repl console reads remain exposed).
test compat_lpop_basic_values {compat lpop returns popped values and updates the list (static pins - run on 8.6 too)}\
-setup $common -body {
set l {a b c}
lappend result [punk::lib::compat::lpop l] $l
set l {a b c}
lappend result [punk::lib::compat::lpop l 0] $l
set l {{a b} {c d}}
lappend result [punk::lib::compat::lpop l 1 0] $l
set l {a}
lappend result [punk::lib::compat::lpop l] $l
set result
}\
-cleanup {
}\
-result [list\
c {a b}\
a {b c}\
c {{a b} d}\
a {}\
]
test compat_lpop_error_out_of_range {compat lpop out-of-range: builtin message and errorcode (static pins - run on 8.6 too)}\
-setup $common -body {
foreach {init idx} {
{a b c} 5
{a b c} end+1
{a b c} -1
{} end
} {
set l $init
set code [catch {punk::lib::compat::lpop l $idx} msg copts]
lappend result [list $code $msg [dict get $copts -errorcode] $l]
}
set result
}\
-cleanup {
}\
-result [list\
[list 1 {index "5" out of range} {TCL VALUE INDEX OUTOFRANGE} {a b c}]\
[list 1 {index "end+1" out of range} {TCL VALUE INDEX OUTOFRANGE} {a b c}]\
[list 1 {index "-1" out of range} {TCL VALUE INDEX OUTOFRANGE} {a b c}]\
[list 1 {index "end" out of range} {TCL VALUE INDEX OUTOFRANGE} {}]\
]
test compat_lpop_error_variable_access {compat lpop variable-access errors name the caller's variable with builtin errorcodes (static pins - run on 8.6 too)}\
-setup $common -body {
catch {unset novar}
set code [catch {punk::lib::compat::lpop novar} msg copts]
lappend result [list $code $msg [dict get $copts -errorcode]]
catch {unset anarr}
array set anarr {x 1}
set code [catch {punk::lib::compat::lpop anarr} msg copts]
lappend result [list $code $msg [dict get $copts -errorcode]]
set code [catch {punk::lib::compat::lpop anarr(nokey)} msg copts]
lappend result [list $code $msg [dict get $copts -errorcode]]
unset anarr
set result
}\
-cleanup {
}\
-result [list\
[list 1 {can't read "novar": no such variable} {TCL LOOKUP VARNAME novar}]\