From a3d3088b03d0e5c1057c59e266b99c1cc9e098fd Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Mon, 13 Jul 2026 03:04:14 +1000 Subject: [PATCH] tclcore/G-055: correct the parse-field tstr finding - expansion happens in the argdoc defspace, not never The 0.3.0 record claimed tstr placeholders in non-display fields are not expanded for plain PUNKARGS definitions. Controlled probes show all authoring styles (direct define, registered PUNKARGS, argdoc-subns variable) DO expand parse-field placeholders - the ::after attempt failed because update_definitions resolves registered definitions in the argdoc subnamespace whenever one exists, while the harvest variable had landed in the parent namespace, and an unresolvable param is left silently literal. Module comment, buildversion changelog line and the G-055 workflow note corrected; the silent-literal fallback noted in G-055 as a diagnostic gap (a literal placeholder reaching a parse field is always an authoring error and could warn). Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- goals/G-055-tclcore-regen-workflow.md | 17 ++++++++++++----- .../punk/args/moduledoc/tclcore-999999.0a1.0.tm | 12 ++++++++---- .../args/moduledoc/tclcore-buildversion.txt | 2 +- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/goals/G-055-tclcore-regen-workflow.md b/goals/G-055-tclcore-regen-workflow.md index 3b275b3b..5c088c37 100644 --- a/goals/G-055-tclcore-regen-workflow.md +++ b/goals/G-055-tclcore-regen-workflow.md @@ -149,11 +149,18 @@ other commands including the multi-form ::after. G-074 sanction then covers exactly that id-shaped witness rather than the whole form pair. APPLIED 2026-07-13 (tclcore moduledoc 0.3.0, user-directed): both ids typed stringstartswith() via a build-time - %AFTERIDPREFIX% string map (note for the workflow: tstr ${...} in - non-display fields is NOT expanded for plain PUNKARGS definitions - G-046 - defers display fields only; parse-field interpolation needs the explicit - tstr wrap, as ::tcl::string::is does, or a string-map token); parity pins - in tclcoreparity.test including the accepted dead-id divergence. + %AFTERIDPREFIX% string map. CORRECTED note for the workflow (the first + record of this wrinkle misattributed it): parse-field tstr ${...} IS + expanded for plain registered PUNKARGS definitions - but in the DEFSPACE, + which is the argdoc subnamespace whenever one exists, even when the + PUNKARGS list lives in the parent namespace (update_definitions evalns + rule); a variable set in the parent is unresolvable there and the param is + left SILENTLY literal (diagnostic gap - a literal ${...} reaching a parse + field is always an authoring error and could warn). Load-time-computed + values are safest via build-time substitution (explicit tstr wrap as + ::tcl::string::is, or a string-map token as ::after) or by setting the + variable in the argdoc namespace. Parity pins in tclcoreparity.test + including the accepted dead-id divergence. - TIP 746 (user-flagged 2026-07-12, https://core.tcl-lang.org/tips/doc/trunk/tip/746.md) removes the expr behaviour from lseq operands in Tcl 9.1: the lseq model's number|expr operand diff --git a/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm b/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm index 6c37291e..b1ace07b 100644 --- a/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm +++ b/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm @@ -4542,10 +4542,14 @@ tcl::namespace::eval punk::args::moduledoc::tclcore { set after_id_prefix $_aip_prefix } } - #the %AFTERIDPREFIX% token below is substituted at build time (string map) because - #tstr ${...} in non-display fields is not expanded for plain PUNKARGS definitions - #(G-046 defers display fields only; the %TYPECHOICES% pattern in punk::args' own - #argdocs is the precedent) + #the %AFTERIDPREFIX% token below is substituted at build time (string map; the + #%TYPECHOICES% pattern in punk::args' own argdocs is the precedent). A tstr + #${$after_id_prefix} in the -type field would also work, BUT only if the variable + #lives in the DEFSPACE the definition resolves in - for registered PUNKARGS + #definitions that is the argdoc subnamespace whenever one exists (even when the + #PUNKARGS list itself is in the parent, as here), and an unresolvable param is + #left silently literal. Build-time substitution avoids the defspace subtlety + #entirely for load-time-computed values like this harvest. # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- lappend PUNKARGS [list [string map [list %AFTERIDPREFIX% $after_id_prefix] { #test of @form diff --git a/src/modules/punk/args/moduledoc/tclcore-buildversion.txt b/src/modules/punk/args/moduledoc/tclcore-buildversion.txt index c85c2e1e..afdec034 100644 --- a/src/modules/punk/args/moduledoc/tclcore-buildversion.txt +++ b/src/modules/punk/args/moduledoc/tclcore-buildversion.txt @@ -1,5 +1,5 @@ 0.3.0 #First line must be a semantic version number #all other lines are ignored. -#0.3.0 - ::after cancel-id discrimination (user-directed 2026-07-13): the cancelid and info forms' id argument is typed stringstartswith() with the prefix harvested from the RUNNING interpreter at define time (safe create+cancel probe 'after 999999 {}' / 'after cancel $id' - G-054 technique; prefix is after# on 8.6.11 and 9.0.3, hardcoded after#%d in tclTimer.c), substituted into the definition via a build-time %AFTERIDPREFIX% string map (tstr ${...} in non-display fields is not expanded for plain PUNKARGS definitions - G-046 defers display fields only). -typesynopsis id keeps the synopsis rendering as the man page's 'id'. Effect under G-041 form candidacy: 'after cancel ' resolves cleanly to the cancelscript form matching real semantics (real 'after cancel' with a non-id is a silent script-match no-op), and 'after info ' is model-rejected where real errors at runtime (parity-true); an id-SHAPED word after cancel remains truthfully ambiguous (cancelid+cancelscript) - real Tcl resolves that junction by id liveness at runtime, which no static type expresses; dead-id over-acceptance on 'after info' recorded as the accepted runtime-liveness boundary. Both ids gain man-page-derived -help text. Parity pins added in tclcoreparity.test (id-shape harvest agreement, cancel discrimination incl the liveness ambiguity, info error-vs-ok parity + accepted dead-id divergence). +#0.3.0 - ::after cancel-id discrimination (user-directed 2026-07-13): the cancelid and info forms' id argument is typed stringstartswith() with the prefix harvested from the RUNNING interpreter at define time (safe create+cancel probe 'after 999999 {}' / 'after cancel $id' - G-054 technique; prefix is after# on 8.6.11 and 9.0.3, hardcoded after#%d in tclTimer.c), substituted into the definition via a build-time %AFTERIDPREFIX% string map (CORRECTED finding: parse-field tstr IS expanded for registered PUNKARGS definitions, but in the argdoc subnamespace when one exists - a variable set in the parent namespace is unresolvable there and the param is left silently literal; build-time substitution sidesteps the defspace subtlety). -typesynopsis id keeps the synopsis rendering as the man page's 'id'. Effect under G-041 form candidacy: 'after cancel ' resolves cleanly to the cancelscript form matching real semantics (real 'after cancel' with a non-id is a silent script-match no-op), and 'after info ' is model-rejected where real errors at runtime (parity-true); an id-SHAPED word after cancel remains truthfully ambiguous (cancelid+cancelscript) - real Tcl resolves that junction by id liveness at runtime, which no static type expresses; dead-id over-acceptance on 'after info' recorded as the accepted runtime-liveness boundary. Both ids gain man-page-derived -help text. Parity pins added in tclcoreparity.test (id-shape harvest agreement, cancel discrimination incl the liveness ambiguity, info error-vs-ok parity + accepted dead-id divergence). #0.2.0 - G-054: 'string is' class choices (and the generated per-class virtual docids) are harvested from the RUNNING interpreter at define time instead of a hand-maintained list - a deliberately invalid probe of the builtin yields the authoritative class set from its error message (8.6: 21 classes, no dict; unreleased 8.7: +dict +unicode; 9.0: +dict, unicode removed), fixing accept/reject drift such as the doc wrongly accepting 'string is dict' under 8.6. Hand-written man-page descriptions apply only to classes the runtime accepts (generic label for unrecognized future classes); static version notes on dict (not in 8.6) and unicode (unreleased 8.7 only). Parity pinned by tclcoreparity.test with expectations derived from the live interpreter (green on 8.6.13, 8.7a6, 9.0.3)