Browse Source

G-082 achieved: punk::args 0.12.1 single-form error selection - specific rejection preferred over generic overflow

A word rejected by an OPTIONAL argument's allocation screen (G-071
restricted-choice screen, or the basic int/double/bool/number/dict type
screen) that subsequently overflows the argument list now reports the
specific rejection instead of "Received more values than can be assigned":

- choiceviolation naming the word and allowed choices, with -badarg/-badval
  and the final-validation message template (Leading/Trailing argument
  attribution), for choice-screen rejections
- typemismatch naming the declared (normalized) type, with -badarg/-badval,
  for basic-type screen rejections

Mechanism: private::get_dict_can_assign_value returns a 'rejection' record
(single-member clauses only; literal-category mismatches deliberately not
recorded); get_dict_form collects first-rejection-per-word from the three
allocation call sites (leader/value split trial, leaders loop, values loop)
into a per-form store; the two toomanyarguments raise sites prefer a
recorded rejection for the unassignable word. Genuinely-surplus words still
report toomanyarguments; words consumed by later slots parse unchanged;
multiform selection (rank_form_failures) untouched.

errorselection.test _GAP pins flipped to the pointed expectations (pre-fix
behaviour pinned in the previous commit). punk::args buildversion
0.12.0 -> 0.12.1.

Closeout: G-082 flipped achieved + archived (index -> GOALS-archive.md,
detail -> goals/archive/ with mechanism/verification/residue recorded);
G-073 note updated to point at the new rejection-record seam;
src/modules/AGENTS.md authoring-ergonomics bullet re-documented (-optional 0
workaround no longer needed on >= 0.12.1; make.tcl keeps it - it runs
against the bootsupport 0.12.0 snapshot). Project version 0.12.28 +
CHANGELOG.

Verified: full punk/args suite 228/224/0-failed (4 intended skips) and
punk/ns 57/57 under Tcl 9.0.3; errorselection.test 7/7 under Tcl 8.7;
make.tcl frobnicate still pointed; goals_lint clean.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 17 hours ago
parent
commit
d2e96dbe30
  1. 5
      CHANGELOG.md
  2. 4
      GOALS-archive.md
  3. 4
      GOALS.md
  4. 13
      goals/G-073-punkargs-unavailable-choices.md
  5. 48
      goals/archive/G-082-punkargs-error-selection.md
  6. 2
      punkproject.toml
  7. 2
      src/modules/AGENTS.md
  8. 105
      src/modules/punk/args-999999.0a1.0.tm
  9. 3
      src/modules/punk/args-buildversion.txt
  10. 33
      src/tests/modules/punk/args/testsuites/args/errorselection.test

5
CHANGELOG.md

@ -5,6 +5,11 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to
Entries are newest-first; one bullet per notable change. See the root `AGENTS.md` Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy. "Project Versioning" section for the bump policy.
## [0.12.28] - 2026-07-14
- G-082 (achieved): punk::args 0.12.1 single-form parse error selection - a word rejected by an optional argument's allocation screen (restricted choices, or the basic int/double/bool/number/dict type screen) that then overflows the argument list now reports the specific rejection (choiceviolation naming the word and allowed choices, or typemismatch naming the type, with -badarg/-badval) instead of the generic "Received more values than can be assigned" overflow - restoring the 0.5.0-era pointedness for optional choice-restricted dispatch shapes (shell-visible in usage errors from punk::args-parsed commands). Genuinely-surplus words still report toomanyarguments; multiform selection unchanged. New errorselection.test (pre-fix behaviour characterized first, pins flipped deliberately); the make.tcl `-optional 0` authoring workaround is no longer needed on fixed versions (src/modules/AGENTS.md guideline re-documented; make.tcl keeps it while running against the bootsupport 0.12.0 snapshot).
- Also: punk::args goals G-083 (argument relations: strict mutual exclusivity, parsekey-group integrity) and G-084 (-parsekey completeness) proposed, with parsekey characterization tests (mash/prefix/shared-default/collision pins + dev known-bug pins) landed alongside.
## [0.12.27] - 2026-07-14 ## [0.12.27] - 2026-07-14
- make.tcl shell under a built punk executable: fixed missing prompts and the raw-mode 'invalid command name struct::set' error. Both stemmed from the kit's script-mode boot pre-loading the punk stack: (1) app-punkscript correctly forces ::tcl_interactive 0 for script runs, and since punk::repl was already loaded its load-time interactivity probe never re-ran - the shell branch now recomputes ::tcl_interactive via punk::repl::is_interactive against the real input channel before repl::start; (2) the accelerator-reload block (package forget + command destroy of sha1/md5/struct::*) relied on later package-require chains to restore the packages, which never re-fire when consumers (punk::lib, punk::du, flagfilter etc) were pre-loaded at kit boot - the block now records what was actually loaded and re-requires it immediately (verified: repl-thread interp state now identical under tclsh and punk91). src/AGENTS.md gains a "pre-loaded interp, not a virgin one" note covering the class: interp surgery and load-time-state assumptions in make.tcl must restore deliberately and be tested under both tclsh and a punk exe (includes the kit-vs-bootsupport module provenance-mixing caveat). - make.tcl shell under a built punk executable: fixed missing prompts and the raw-mode 'invalid command name struct::set' error. Both stemmed from the kit's script-mode boot pre-loading the punk stack: (1) app-punkscript correctly forces ::tcl_interactive 0 for script runs, and since punk::repl was already loaded its load-time interactivity probe never re-ran - the shell branch now recomputes ::tcl_interactive via punk::repl::is_interactive against the real input channel before repl::start; (2) the accelerator-reload block (package forget + command destroy of sha1/md5/struct::*) relied on later package-require chains to restore the packages, which never re-fire when consumers (punk::lib, punk::du, flagfilter etc) were pre-loaded at kit boot - the block now records what was actually loaded and re-requires it immediately (verified: repl-thread interp state now identical under tclsh and punk91). src/AGENTS.md gains a "pre-loaded interp, not a virgin one" note covering the class: interp surgery and load-time-state assumptions in make.tcl must restore deliberately and be tested under both tclsh and a punk exe (includes the kit-vs-bootsupport module provenance-mixing caveat).

4
GOALS-archive.md

@ -92,3 +92,7 @@ Acceptance: `tclsh src/make.tcl` and `-help` render punk::args tabled usage list
### G-074 [achieved 2026-07-13] punk::args multiform ambiguity analysis: on-demand form-overlap detection with sanctioned-overlap annotation → detail: goals/archive/G-074-punkargs-multiform-ambiguity-lint.md ### G-074 [achieved 2026-07-13] punk::args multiform ambiguity analysis: on-demand form-overlap detection with sanctioned-overlap annotation → detail: goals/archive/G-074-punkargs-multiform-ambiguity-lint.md
Scope: src/modules/punk/args-999999.0a1.0.tm (analysis command, @form sanction key as decided in the work), src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (::after/::lseq as proving consumers, sanction adoption), src/tests/modules/punk/args/testsuites/args/ (new suite) Scope: src/modules/punk/args-999999.0a1.0.tm (analysis command, @form sanction key as decided in the work), src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (::after/::lseq as proving consumers, sanction adoption), src/tests/modules/punk/args/testsuites/args/ (new suite)
Acceptance: the analysis command reports the two known real cases from the G-041 closeout - ::lseq range/start_count overlap via the expr-typed end slot (unsanctioned finding: type-weakness class) and ::after cancelid/cancelscript (documented-overlap class) - and reports nothing for multiform definitions whose forms are fully discriminated (the parse withid/withdef pair, the forms.test afterish fixture); classifications distinguish at minimum type-weakness overlap (a discriminator aligned with a permissive type) from structural overlap (no discriminating slot exists); the sanction annotation silences (or downgrades to acknowledged) a listed form pair without affecting parse behaviour, is rejected at definition resolve when it names unknown forms, and is adopted for the after cancel pair in the tclcore moduledoc; the analysis is conservative in the documented direction (may miss deep ambiguities, must not false-alarm on discriminated forms - the miss/report boundary is documented with the slot model); running it performs no parse of user-supplied words and adds no work to define/resolve for definitions that never call it; a new testsuite covers the finding classes, the sanction, the unknown-form rejection and the no-finding cases; full punk::args suite passes. Acceptance: the analysis command reports the two known real cases from the G-041 closeout - ::lseq range/start_count overlap via the expr-typed end slot (unsanctioned finding: type-weakness class) and ::after cancelid/cancelscript (documented-overlap class) - and reports nothing for multiform definitions whose forms are fully discriminated (the parse withid/withdef pair, the forms.test afterish fixture); classifications distinguish at minimum type-weakness overlap (a discriminator aligned with a permissive type) from structural overlap (no discriminating slot exists); the sanction annotation silences (or downgrades to acknowledged) a listed form pair without affecting parse behaviour, is rejected at definition resolve when it names unknown forms, and is adopted for the after cancel pair in the tclcore moduledoc; the analysis is conservative in the documented direction (may miss deep ambiguities, must not false-alarm on discriminated forms - the miss/report boundary is documented with the slot model); running it performs no parse of user-supplied words and adds no work to define/resolve for definitions that never call it; a new testsuite covers the finding classes, the sanction, the unknown-form rejection and the no-finding cases; full punk::args suite passes.
### G-082 [achieved 2026-07-14] punk::args single-form parse error selection: specific validation failures preferred over generic overflow → detail: goals/archive/G-082-punkargs-error-selection.md
Scope: src/modules/punk/args-999999.0a1.0.tm (get_dict/parse error selection, validation-failure capture around the choice/type screens), src/tests/modules/punk/args/testsuites/args/ (characterization + regression suite)
Acceptance: a definition with an optional choice-restricted leader or value that receives a non-matching word reports a choiceviolation-class error naming the word and the allowed choices (message and -errorcode), not toomanyarguments - pinned for the make.tcl-style dispatch shape (optional subcommand leader with choices) and a values-position equivalent; a word rejected by a typed optional argument (e.g int) that then overflows reports the type failure similarly; genuinely-surplus cases (all optional args satisfied, extra word remains) still report toomanyarguments; multiform error selection (rank_form_failures) is unchanged or its pins deliberately updated; current 0.12.x behaviour is characterized first and the pins flipped deliberately; full punk::args suite passes with no expectations weakened; on completion the G-030 -optional 0 workaround sites are revisited (make.tcl dispatch definitions, the src/modules/AGENTS.md "punk::args definition authoring ergonomics" bullet) and relaxed or re-documented.

4
GOALS.md

@ -310,10 +310,6 @@ Detail: goals/G-080-ansi2html-cellgrid-mode.md
Scope: src/modules/punk/mix/commandset/doc-999999.0a1.0.tm (doc.* commandset - new generation subcommands), src/scriptapps/tools/punkargs_to_doctools.tcl + punkargs_punknative.tcl (prototypes to productize), src/scriptapps/tools/fontprep/ (shared doc-set asset in link mode), src/doc tree conventions, src/tests coverage Scope: src/modules/punk/mix/commandset/doc-999999.0a1.0.tm (doc.* commandset - new generation subcommands), src/scriptapps/tools/punkargs_to_doctools.tcl + punkargs_punknative.tcl (prototypes to productize), src/scriptapps/tools/fontprep/ (shared doc-set asset in link mode), src/doc tree conventions, src/tests coverage
Detail: goals/G-081-argdoc-build-pipeline.md Detail: goals/G-081-argdoc-build-pipeline.md
### G-082 [active] punk::args single-form parse error selection: specific validation failures preferred over generic overflow
Scope: src/modules/punk/args-999999.0a1.0.tm (get_dict/parse error selection, validation-failure capture around the choice/type screens), src/tests/modules/punk/args/testsuites/args/ (characterization + regression suite)
Detail: goals/G-082-punkargs-error-selection.md
### G-083 [proposed] punk::args argument relations: strict mutual exclusivity and parsekey-group integrity ### G-083 [proposed] punk::args argument relations: strict mutual exclusivity and parsekey-group integrity
Scope: src/modules/punk/args-999999.0a1.0.tm (resolve/spec compiler, parse paths incl mash, arg_error/synopsis/usage renderers, errorcode vocabulary), src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (lsearch, clock clicks dogfood), src/tests/modules/punk/args/testsuites/args/ + testsuites/dev/ (GAP flips + new characterization) Scope: src/modules/punk/args-999999.0a1.0.tm (resolve/spec compiler, parse paths incl mash, arg_error/synopsis/usage renderers, errorcode vocabulary), src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (lsearch, clock clicks dogfood), src/tests/modules/punk/args/testsuites/args/ + testsuites/dev/ (GAP flips + new characterization)
Detail: goals/G-083-punkargs-argument-relations.md Detail: goals/G-083-punkargs-argument-relations.md

13
goals/G-073-punkargs-unavailable-choices.md

@ -62,8 +62,11 @@ riding the existing rendering pipeline.
once proven here). once proven here).
- The reject-message wording should steer the user: name the note/annotation and - The reject-message wording should steer the user: name the note/annotation and
the version boundary, not just "invalid". the version boundary, not just "invalid".
- Related: G-082 (proposed 2026-07-14) - single-form error selection currently - Related: G-082 (achieved 2026-07-14 - goals/archive/G-082-punkargs-error-selection.md) -
discards an optional argument's specific choice/type rejection in favour of a single-form error selection now surfaces an optional argument's specific
generic overflow error; this goal's tailored unavailable-choice message wants choice/type rejection instead of the generic overflow error. The seam this
the same "surface the specific rejection" seam. Whichever lands first should goal wants exists: private::get_dict_can_assign_value returns a 'rejection'
leave the other a clean hook. record (kind choice|type + word/choices/type) and get_dict_form's
alloc_rejections store is consulted at the overflow raise sites - the
tailored unavailable-choice message can extend the record's kind vocabulary
for recognised-but-unavailable words rather than adding a second mechanism.

48
goals/G-082-punkargs-error-selection.md → goals/archive/G-082-punkargs-error-selection.md

@ -1,6 +1,6 @@
# G-082 punk::args single-form parse error selection: specific validation failures preferred over generic overflow # G-082 punk::args single-form parse error selection: specific validation failures preferred over generic overflow
Status: active Status: achieved 2026-07-14
Scope: src/modules/punk/args-999999.0a1.0.tm (get_dict/parse error selection, validation-failure capture around the choice/type screens), src/tests/modules/punk/args/testsuites/args/ (characterization + regression suite) Scope: src/modules/punk/args-999999.0a1.0.tm (get_dict/parse error selection, validation-failure capture around the choice/type screens), src/tests/modules/punk/args/testsuites/args/ (characterization + regression suite)
Goal: when a single-form parse fails because a word was rejected by an optional argument's validation (restricted choices, type) and consequently overflowed the argument list, the reported error names the specific rejection (the choiceviolation with the offending word and choice list, or the type failure) instead of the generic "Received more values than can be assigned" overflow - restoring the pointedness punk::args 0.5.0-era parsing had for optional choice-restricted leaders/values, and removing the need for the -optional 0 authoring workaround adopted by make.tcl's dispatch definitions. Goal: when a single-form parse fails because a word was rejected by an optional argument's validation (restricted choices, type) and consequently overflowed the argument list, the reported error names the specific rejection (the choiceviolation with the offending word and choice list, or the type failure) instead of the generic "Received more values than can be assigned" overflow - restoring the pointedness punk::args 0.5.0-era parsing had for optional choice-restricted leaders/values, and removing the need for the -optional 0 authoring workaround adopted by make.tcl's dispatch definitions.
Acceptance: a definition with an optional choice-restricted leader or value that receives a non-matching word reports a choiceviolation-class error naming the word and the allowed choices (message and -errorcode), not toomanyarguments - pinned for the make.tcl-style dispatch shape (optional subcommand leader with choices) and a values-position equivalent; a word rejected by a typed optional argument (e.g int) that then overflows reports the type failure similarly; genuinely-surplus cases (all optional args satisfied, extra word remains) still report toomanyarguments; multiform error selection (rank_form_failures) is unchanged or its pins deliberately updated; current 0.12.x behaviour is characterized first and the pins flipped deliberately; full punk::args suite passes with no expectations weakened; on completion the G-030 -optional 0 workaround sites are revisited (make.tcl dispatch definitions, the src/modules/AGENTS.md "punk::args definition authoring ergonomics" bullet) and relaxed or re-documented. Acceptance: a definition with an optional choice-restricted leader or value that receives a non-matching word reports a choiceviolation-class error naming the word and the allowed choices (message and -errorcode), not toomanyarguments - pinned for the make.tcl-style dispatch shape (optional subcommand leader with choices) and a values-position equivalent; a word rejected by a typed optional argument (e.g int) that then overflows reports the type failure similarly; genuinely-surplus cases (all optional args satisfied, extra word remains) still report toomanyarguments; multiform error selection (rank_form_failures) is unchanged or its pins deliberately updated; current 0.12.x behaviour is characterized first and the pins flipped deliberately; full punk::args suite passes with no expectations weakened; on completion the G-030 -optional 0 workaround sites are revisited (make.tcl dispatch definitions, the src/modules/AGENTS.md "punk::args definition authoring ergonomics" bullet) and relaxed or re-documented.
@ -30,10 +30,54 @@ recording candidate rejections during allocation and preferring the most
specific/nearest one at error-report time - the single-form analogue of the specific/nearest one at error-report time - the single-form analogue of the
multiform rank_form_failures selection. multiform rank_form_failures selection.
## Progress
- 2026-07-14 (achieved): implemented in punk::args 0.12.1 (source module).
Mechanism: private::get_dict_can_assign_value returns a 'rejection' record
(kind choice|type, argname, word, choices/nocase/prefix or type) when its
optionality-gated screens reject a single-member clause's candidate word;
get_dict_form collects first-rejection-per-word from the three allocation
call sites (leader/value split trial, leaders loop, values loop) into a
per-form alloc_rejections store; the two toomanyarguments raise sites
consult the store for the unassignable word and prefer the specific report -
choiceviolation with -badarg/-badval and the final-validation message
template, or typemismatch naming the declared (normalized) type.
Literal-category mismatches are deliberately not recorded (syntax-word
shapes like lseq's 'to' - overflow remains the clearer report). The
basic-type screen covers int/double/bool/number/dict; other types are not
screened at allocation and were never part of the regression shape.
- Verification (Tcl 9.0.3 via src/tests/runtests.tcl): errorselection.test
characterized pre-fix behaviour first (7/7 against the unfixed module,
commit "G-082 increment 1"), then its three _GAP pins were flipped to the
pointed choiceviolation/typemismatch expectations; full punk/args suite
228 total / 224 passed / 0 failed / 4 intended skips (allocation.test G-071
pins, parsestatus.test, forms.test all unchanged); punk/ns suite 57/57;
errorselection.test 7/7 under Tcl 8.7. Multiform selection unchanged
(rank_form_failures untouched; the multiform probe reports identical
noformmatch classes pre/post fix, pinned by errsel_multiform_selection).
make.tcl frobnicate still reports the pointed choiceviolation (bootsupport
punk::args 0.12.0 - the regressed snapshot - with the retained -optional 0
workaround; the workaround stays needed there until a bootsupport refresh
ships >= 0.12.1).
- Workaround-site revisit: make.tcl dispatch definitions keep -optional 0
(semantically sound - the dispatch only parses when a word is present, and
make.tcl runs against the bootsupport punk::args 0.12.0 snapshot, not the
fixed source module);
the src/modules/AGENTS.md authoring-ergonomics bullet was re-documented
(workaround no longer needed for pointed errors on punk::args >= 0.12.1).
- Residue (out of this goal's acceptance): a REQUIRED typed value whose word
fails the basic-type screen still reports missingrequiredvalue rather than
a typemismatch naming the word (pre-existing, pinned nowhere) - candidate
follow-on; the rejection-record seam would cover it.
## Notes ## Notes
- Related: G-073 (unavailable-choice tailored rejection messages want the same - Related: G-073 (unavailable-choice tailored rejection messages want the same
"surface the specific rejection" seam - whichever lands first should leave the "surface the specific rejection" seam - whichever lands first should leave the
other a clean hook), G-071 achieved 2026-07-12 (the optional-element allocation other a clean hook. G-082 landed first: the hook is the 'rejection' record
returned by private::get_dict_can_assign_value plus the per-form
alloc_rejections store consulted at the overflow raise sites - G-073 can
extend the record's kind vocabulary for recognised-but-unavailable words),
G-071 achieved 2026-07-12 (the optional-element allocation
machinery this selection logic sits beside - goals/archive/G-071-punkargs-optional-allocation.md). machinery this selection logic sits beside - goals/archive/G-071-punkargs-optional-allocation.md).
- Archived-goal references in this file: G-030 achieved 2026-07-14 (goals/archive/G-030-maketcl-punkargs.md); G-074 achieved 2026-07-13 (goals/archive/G-074-punkargs-multiform-ambiguity-lint.md). - Archived-goal references in this file: G-030 achieved 2026-07-14 (goals/archive/G-030-maketcl-punkargs.md); G-074 achieved 2026-07-13 (goals/archive/G-074-punkargs-multiform-ambiguity-lint.md).

2
punkproject.toml

@ -1,4 +1,4 @@
[project] [project]
name = "punkshell" name = "punkshell"
version = "0.12.27" version = "0.12.28"
license = "BSD-2-Clause" license = "BSD-2-Clause"

2
src/modules/AGENTS.md

@ -344,7 +344,7 @@ Before writing or generating punk::args definitions, know the mechanisms punk::a
- **`-&` record continuation** (unquoted trailing token) continues a record across lines *inside constructed strings*, where backslash-newline is consumed by the building code's own quoting; brace a literal `{-&}` to escape. Byte-identical assembly to the backslash equivalent. - **`-&` record continuation** (unquoted trailing token) continues a record across lines *inside constructed strings*, where backslash-newline is consumed by the building code's own quoting; brace a literal `{-&}` to escape. Byte-identical assembly to the backslash equivalent.
- **Container quoting rules** (braced values fully literal; double-quoted values get Tcl backslash semantics with `$`/`[]` still literal; `\$\{...\}` for a literal placeholder) are documented in the `punk::args::define` -help — consult it rather than experimenting. - **Container quoting rules** (braced values fully literal; double-quoted values get Tcl backslash semantics with `$`/`[]` still literal; `\$\{...\}` for a literal placeholder) are documented in the `punk::args::define` -help — consult it rather than experimenting.
- **Script-level/constructed ids want an explicit `@form -synopsis`**: the auto-generated synopsis currently renders the definition id (e.g `(script)::punkboot::modules`), not `@cmd -name` — supply the real invocation string (G-030 lesson). - **Script-level/constructed ids want an explicit `@form -synopsis`**: the auto-generated synopsis currently renders the definition id (e.g `(script)::punkboot::modules`), not `@cmd -name` — supply the real invocation string (G-030 lesson).
- For choice-restricted leaders/values in dispatch-style definitions, prefer `-optional 0` when the calling code only parses when a word is present: as of punk::args 0.12.0 a non-matching word for an *optional* choice arg falls through to a generic overflow error instead of a pointed choiceviolation (regression vs 0.5.0, recorded in goals/archive/G-030-maketcl-punkargs.md; fix proposed as goal G-082 — revisit this guideline when it ships). - For choice-restricted leaders/values in dispatch-style definitions, `-optional 0` is no longer needed for pointed errors: the 0.12.0 regression (a non-matching word for an *optional* choice arg fell through to a generic overflow error instead of a pointed choiceviolation - recorded in goals/archive/G-030-maketcl-punkargs.md) is fixed in punk::args 0.12.1 (G-082, achieved 2026-07-14 - goals/archive/G-082-punkargs-error-selection.md): the specific choiceviolation/typemismatch is reported when an optional argument's rejected word overflows. `-optional 0` remains semantically sound where the calling code only parses when a word is present (make.tcl's dispatch definitions keep it - they also run against bootsupport punk::args, not the fixed source module); only definitions that must parse pointedly under a snapshot of the regressed 0.12.0 still need the workaround.
## Verification ## Verification

105
src/modules/punk/args-999999.0a1.0.tm

@ -7993,6 +7993,10 @@ tcl::namespace::eval punk::args {
lassign [Dict_getdef $ARG_INFO $thisname -choicemultiple {1 1}] cw_cmmin cw_cmmax lassign [Dict_getdef $ARG_INFO $thisname -choicemultiple {1 1}] cw_cmmin cw_cmmax
} }
} }
#G-082: when a screen (choice/basic-type) rejects the candidate word for a
#single-member clause, the reason is recorded here and returned so the caller
#can prefer the specific rejection over a generic overflow at raise time.
set rejection {}
foreach tp $thistype { foreach tp $thistype {
#usual case is a single tp (basic length-1 clause) - but tp may commonly have alternates eg int|literal(xxx) #usual case is a single tp (basic length-1 clause) - but tp may commonly have alternates eg int|literal(xxx)
set v [lindex $all_remaining $alloc_count] set v [lindex $all_remaining $alloc_count]
@ -8188,6 +8192,17 @@ tcl::namespace::eval punk::args {
} }
} else { } else {
set alloc_ok 0 set alloc_ok 0
#G-082: single-member clause only - the shapes where the rejected
#word and a subsequently-overflowing word are the same word.
#Literal-category mismatches are deliberately not recorded (syntax
#words like lseq's 'to' - overflow remains the clearer report).
if {[llength $thistype] == 1} {
if {$member_choicechecked} {
set rejection [tcl::dict::create kind choice argname $thisname word $v choices $cw_allchoices nocase $cw_nocase prefix $cw_prefix]
} elseif {[llength $ctg_other]} {
set rejection [tcl::dict::create kind type argname $thisname word $v type $tp]
}
}
} }
} }
@ -8203,10 +8218,10 @@ tcl::namespace::eval punk::args {
#such elements shouldn't be subject to validation #such elements shouldn't be subject to validation
if {$alloc_ok} { if {$alloc_ok} {
#puts stderr ">>>private::get_dict_can_assign_value idx:$idx v:[lindex $values $idx] consumed:$alloc_count thistype:$thistype" #puts stderr ">>>private::get_dict_can_assign_value idx:$idx v:[lindex $values $idx] consumed:$alloc_count thistype:$thistype"
set d [dict create consumed $alloc_count resultlist $resultlist typelist $newtypelist] set d [dict create consumed $alloc_count resultlist $resultlist typelist $newtypelist rejection {}]
} else { } else {
#puts stderr ">>>private::get_dict_can_assign_value NOT alloc_ok: idx:$idx v:[lindex $values $idx] consumed:$alloc_count thistype:$thistype" #puts stderr ">>>private::get_dict_can_assign_value NOT alloc_ok: idx:$idx v:[lindex $values $idx] consumed:$alloc_count thistype:$thistype"
set d [dict create consumed 0 resultlist {} typelist $thistype] set d [dict create consumed 0 resultlist {} typelist $thistype rejection $rejection]
} }
#puts ">>>> private::get_dict_can_assign_value $d" #puts ">>>> private::get_dict_can_assign_value $d"
return $d return $d
@ -10512,6 +10527,13 @@ tcl::namespace::eval punk::args {
set FORMDISPLAY [dict get $formdict FORMDISPLAY] set FORMDISPLAY [dict get $formdict FORMDISPLAY]
#G-082 single-form error selection: words the allocation screens rejected for an
#OPTIONAL argument (restricted choices, basic type), keyed by the word itself.
#First rejection per word wins (nearest/earliest defined argument - the one the
#caller most plausibly intended). Consulted only if the word later overflows -
#a word consumed by a later slot leaves its entry unused.
set alloc_rejections [dict create]
if {$VAL_MIN eq ""} { if {$VAL_MIN eq ""} {
set valmin 0 set valmin 0
#set VAL_MIN 0 #set VAL_MIN 0
@ -10732,7 +10754,11 @@ tcl::namespace::eval punk::args {
incr ridx $consumed incr ridx $consumed
incr ridx -1 ;#leave ridx at index of last r that we set incr ridx -1 ;#leave ridx at index of last r that we set
} else { } else {
#G-082: keep the screen's rejection (if any) for error selection at overflow time
set rj [dict get $assign_d rejection]
if {$rj ne "" && ![dict exists $alloc_rejections [dict get $rj word]]} {
dict set alloc_rejections [dict get $rj word] $rj
}
} }
if {!$is_multiple} { if {!$is_multiple} {
incr nameidx incr nameidx
@ -11660,6 +11686,11 @@ tcl::namespace::eval punk::args {
set resultlist [dict get $assign_d resultlist] set resultlist [dict get $assign_d resultlist]
set newtypelist [dict get $assign_d typelist] set newtypelist [dict get $assign_d typelist]
if {$consumed == 0} { if {$consumed == 0} {
#G-082: keep the screen's rejection (if any) for error selection at overflow time
set rj [dict get $assign_d rejection]
if {$rj ne "" && ![dict exists $alloc_rejections [dict get $rj word]]} {
dict set alloc_rejections [dict get $rj word] $rj
}
if {[tcl::dict::get $argstate $leadername -optional]} { if {[tcl::dict::get $argstate $leadername -optional]} {
#puts stderr "get_dict cannot assign val:$ldr to leadername:$leadername leaders:$leaders (111)" #puts stderr "get_dict cannot assign val:$ldr to leadername:$leadername leaders:$leaders (111)"
#return -options [list -code error -errorcode [list PUNKARGS UNCONSUMED -argspecs $argspecs]] "private::get_dict_can_assign_value consumed 0 unexpected 1?" #return -options [list -code error -errorcode [list PUNKARGS UNCONSUMED -argspecs $argspecs]] "private::get_dict_can_assign_value consumed 0 unexpected 1?"
@ -11759,6 +11790,37 @@ tcl::namespace::eval punk::args {
tcl::dict::set arg_checks $positionalidx $LEADER_CHECKS_DEFAULTS tcl::dict::set arg_checks $positionalidx $LEADER_CHECKS_DEFAULTS
lappend leadernames_received $positionalidx lappend leadernames_received $positionalidx
} else { } else {
#G-082 single-form error selection: if this unassignable word was
#rejected by an optional argument's allocation screen, report that
#specific rejection instead of the generic overflow.
if {[dict exists $alloc_rejections $ldr]} {
set rj [dict get $alloc_rejections $ldr]
set rj_argname [dict get $rj argname]
if {$rj_argname in $LEADER_NAMES} {
set rj_argclass "Leading argument"
} else {
set rj_argclass "Trailing argument"
}
if {[dict get $rj kind] eq "choice"} {
if {[dict get $rj nocase]} {
set rj_casemsg " (case insensitive)"
} else {
set rj_casemsg " (case sensitive)"
}
if {[dict get $rj prefix]} {
set rj_prefixmsg " (or a unique prefix of a value)"
} else {
set rj_prefixmsg ""
}
set rj_choices [dict get $rj choices]
set msg "$rj_argclass '$rj_argname' for %caller% must be one of the listed values:\n [join $rj_choices "\n "]\n$rj_casemsg$rj_prefixmsg. Received: '$ldr'"
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choiceviolation $ldr choices $rj_choices] -badarg $rj_argname -badval $ldr -argspecs $argspecs]] $msg
} else {
set rj_type [dict get $rj type]
set msg "$rj_argclass '$rj_argname' for %caller% requires type '$rj_type'. Received: '$ldr'"
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list typemismatch $rj_type] -badarg $rj_argname -badval $ldr -argspecs $argspecs]] $msg
}
}
set msg "Bad number of leaders for %caller%. Received more leaders than can be assigned to argument names. (set '@leaders -unnamed true' to allow unnamed leaders)" set msg "Bad number of leaders for %caller%. Received more leaders than can be assigned to argument names. (set '@leaders -unnamed true' to allow unnamed leaders)"
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list toomanyarguments [llength $values] index $positionalidx] -argspecs $argspecs]] $msg return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list toomanyarguments [llength $values] index $positionalidx] -argspecs $argspecs]] $msg
} }
@ -11871,6 +11933,11 @@ tcl::namespace::eval punk::args {
set resultlist [dict get $assign_d resultlist] set resultlist [dict get $assign_d resultlist]
set newtypelist [dict get $assign_d typelist] set newtypelist [dict get $assign_d typelist]
if {$consumed == 0} { if {$consumed == 0} {
#G-082: keep the screen's rejection (if any) for error selection at overflow time
set rj [dict get $assign_d rejection]
if {$rj ne "" && ![dict exists $alloc_rejections [dict get $rj word]]} {
dict set alloc_rejections [dict get $rj word] $rj
}
if {[tcl::dict::get $argstate $valname -optional]} { if {[tcl::dict::get $argstate $valname -optional]} {
#error 333 #error 333
#puts stderr "get_dict cannot assign val:$val to valname:$valname (333)" #puts stderr "get_dict cannot assign val:$val to valname:$valname (333)"
@ -11967,6 +12034,38 @@ tcl::namespace::eval punk::args {
tcl::dict::set arg_checks $positionalidx $VAL_CHECKS_DEFAULTS tcl::dict::set arg_checks $positionalidx $VAL_CHECKS_DEFAULTS
lappend valnames_received $positionalidx lappend valnames_received $positionalidx
} else { } else {
#G-082 single-form error selection: if this unassignable word was
#rejected by an optional argument's allocation screen, report that
#specific rejection instead of the generic overflow.
#(MAINTENANCE - same selection logic as leaders loop above)
if {[dict exists $alloc_rejections $val]} {
set rj [dict get $alloc_rejections $val]
set rj_argname [dict get $rj argname]
if {$rj_argname in $LEADER_NAMES} {
set rj_argclass "Leading argument"
} else {
set rj_argclass "Trailing argument"
}
if {[dict get $rj kind] eq "choice"} {
if {[dict get $rj nocase]} {
set rj_casemsg " (case insensitive)"
} else {
set rj_casemsg " (case sensitive)"
}
if {[dict get $rj prefix]} {
set rj_prefixmsg " (or a unique prefix of a value)"
} else {
set rj_prefixmsg ""
}
set rj_choices [dict get $rj choices]
set msg "$rj_argclass '$rj_argname' for %caller% must be one of the listed values:\n [join $rj_choices "\n "]\n$rj_casemsg$rj_prefixmsg. Received: '$val'"
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choiceviolation $val choices $rj_choices] -badarg $rj_argname -badval $val -argspecs $argspecs]] $msg
} else {
set rj_type [dict get $rj type]
set msg "$rj_argclass '$rj_argname' for %caller% requires type '$rj_type'. Received: '$val'"
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list typemismatch $rj_type] -badarg $rj_argname -badval $val -argspecs $argspecs]] $msg
}
}
set msg "Bad number of values for %caller%. Received more values than can be assigned to argument names. (set '@values -unnamed true' to allow unnamed values)" set msg "Bad number of values for %caller%. Received more values than can be assigned to argument names. (set '@values -unnamed true' to allow unnamed values)"
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list toomanyarguments [llength $values] index $positionalidx] -argspecs $argspecs]] $msg return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list toomanyarguments [llength $values] index $positionalidx] -argspecs $argspecs]] $msg
} }

3
src/modules/punk/args-buildversion.txt

@ -1,6 +1,7 @@
0.12.0 0.12.1
#First line must be a semantic version number #First line must be a semantic version number
#all other lines are ignored. #all other lines are ignored.
#0.12.1 - G-082 single-form parse error selection: a word rejected by an OPTIONAL argument's allocation screen (G-071 restricted-choice screen, or the basic-type screen: int/double/bool/number/dict) that subsequently overflows the argument list now reports the specific rejection - choiceviolation naming the word and the allowed choices (with -badarg/-badval and the same message template as final validation's report for required args), or typemismatch naming the declared type - instead of the generic 'Received more values than can be assigned' toomanyarguments (regression vs 0.5.0 found during G-030 make.tcl dogfooding; the -optional 0 authoring workaround is no longer required). Mechanism: get_dict_can_assign_value returns a 'rejection' record (single-member clauses only; literal-category mismatches deliberately not recorded - overflow stays the clearer report for syntax-word shapes like lseq 'to'), the three allocation call sites (leader/value split trial, leaders loop, values loop) collect first-rejection-per-word into a per-form store, and the two overflow raise sites prefer a recorded rejection for the unassignable word. Genuinely-surplus words (all optionals satisfied) still report toomanyarguments; words consumed by a later slot parse unchanged; multiform selection (rank_form_failures) unchanged - per-form failures just carry the more specific class. New testsuite errorselection.test (characterized pre-fix, pins flipped deliberately).
#0.12.0 - G-074: new punk::args::formcheck - on-demand multiform ambiguity analysis. Static pairwise pass over a definition's resolved FORMS: enumerates the positional word-slot chains each form can present (leaders then values, branching on -optional arguments and ?-wrapped optional clause members, -multiple capped; options excluded - order-free), derives candidate witness arglists for aligned equal-length chains (discriminator words + type-derived witnesses, screened via choiceword_match/type tests so screening can't diverge from parse acceptance), and only reports a form pair after CONFIRMING a witness with a real single-form parse against BOTH forms (parse_status; no user-supplied words, nothing added to define/resolve cost) - so fully discriminated pairs cannot false-alarm and every reported witness is a genuine multipleformmatches arglist; the documented miss direction is witness derivation (exotic types, forms requiring options, enumeration caps). Findings classify as type_weakness (a discriminator slot - literal()/literalprefix()/restricted choices - aligned with a permissive non-validating type: any/none/string/ansistring/globstring/expr/script; the 'lseq 1 count 5' class) vs structural (forms genuinely share an argument shape; the 'after cancel id|script' class). New @form key -overlapallowed <formname-list> sanctions a KNOWN overlap on either pair member: the finding reports with sanctioned 1 and leaves the unsanctioned list (the actionable/gate subset) - parse behaviour is never affected (multipleformmatches still raises); unknown form names rejected at definition resolve. Returns machine-parsable dict (id/form_names/pairs/findings with witness+per-slot relations/unsanctioned/summary) or -return summary for the report text. New testsuite formcheck.test; @form directive doc updated. Proving consumers: tclcore ::lseq (reports range/start_count AND range/count - both real, root cause the expr-typed end slot) and ::after (exactly the sanctioned cancelid/cancelscript documented overlap). #0.12.0 - G-074: new punk::args::formcheck - on-demand multiform ambiguity analysis. Static pairwise pass over a definition's resolved FORMS: enumerates the positional word-slot chains each form can present (leaders then values, branching on -optional arguments and ?-wrapped optional clause members, -multiple capped; options excluded - order-free), derives candidate witness arglists for aligned equal-length chains (discriminator words + type-derived witnesses, screened via choiceword_match/type tests so screening can't diverge from parse acceptance), and only reports a form pair after CONFIRMING a witness with a real single-form parse against BOTH forms (parse_status; no user-supplied words, nothing added to define/resolve cost) - so fully discriminated pairs cannot false-alarm and every reported witness is a genuine multipleformmatches arglist; the documented miss direction is witness derivation (exotic types, forms requiring options, enumeration caps). Findings classify as type_weakness (a discriminator slot - literal()/literalprefix()/restricted choices - aligned with a permissive non-validating type: any/none/string/ansistring/globstring/expr/script; the 'lseq 1 count 5' class) vs structural (forms genuinely share an argument shape; the 'after cancel id|script' class). New @form key -overlapallowed <formname-list> sanctions a KNOWN overlap on either pair member: the finding reports with sanctioned 1 and leaves the unsanctioned list (the actionable/gate subset) - parse behaviour is never affected (multipleformmatches still raises); unknown form names rejected at definition resolve. Returns machine-parsable dict (id/form_names/pairs/findings with witness+per-slot relations/unsanctioned/summary) or -return summary for the report text. New testsuite formcheck.test; @form directive doc updated. Proving consumers: tclcore ::lseq (reports range/start_count AND range/count - both real, root cause the expr-typed end slot) and ::after (exactly the sanctioned cancelid/cancelscript documented overlap).
#0.11.2 - bad-@dynamic warn-once + round-1 caching (user-reported: 'i join' emitted the "bad @dynamic tag" warning 4x, 'i join test' 6x): a @dynamic definition whose round-1 tstr output contains no round-2 parameters previously bypassed argdefcache_unresolved entirely, so EVERY resolve redid display masking plus the full round-1 tstr and re-warned - each 'i' invocation resolves several times (doc walk, advisory parse, get_spec for the render, synopsis), all cache hits for static definitions but full re-work for bad-dynamic ones. Such definitions are now cached as a zero-param unresolved entry: subsequent resolves take the cheap cached branch (consistent with the round-1 freezing legitimately dynamic definitions already get) and the warning emits once per definition per interp. Warning message corrected while there: @dynamic is NOT a complete no-op for a parse-inert definition - deferred display fields still re-expand per render instead of caching (expand_display_fields spec_dynamic gate), so the message now says removal is appropriate only if that display behaviour is unintended. Legitimately dynamic definitions unaffected (round-2 re-substitution pinned live). Also commented the dev diagnostics dump (puts of the full records list + ::testrecord global) on resolve's malformed-record error path - the raised message already carries the offending record; found when the malformed ::tcl_startOfNextWord tclcore definition triggered it (fixed in tclcore 0.3.2). New testsuite args/dynamic.test (warn-once + cached-parse pin, legit-dynamic round-2 freshness pin, stderr captured via channel transform). #0.11.2 - bad-@dynamic warn-once + round-1 caching (user-reported: 'i join' emitted the "bad @dynamic tag" warning 4x, 'i join test' 6x): a @dynamic definition whose round-1 tstr output contains no round-2 parameters previously bypassed argdefcache_unresolved entirely, so EVERY resolve redid display masking plus the full round-1 tstr and re-warned - each 'i' invocation resolves several times (doc walk, advisory parse, get_spec for the render, synopsis), all cache hits for static definitions but full re-work for bad-dynamic ones. Such definitions are now cached as a zero-param unresolved entry: subsequent resolves take the cheap cached branch (consistent with the round-1 freezing legitimately dynamic definitions already get) and the warning emits once per definition per interp. Warning message corrected while there: @dynamic is NOT a complete no-op for a parse-inert definition - deferred display fields still re-expand per render instead of caching (expand_display_fields spec_dynamic gate), so the message now says removal is appropriate only if that display behaviour is unintended. Legitimately dynamic definitions unaffected (round-2 re-substitution pinned live). Also commented the dev diagnostics dump (puts of the full records list + ::testrecord global) on resolve's malformed-record error path - the raised message already carries the offending record; found when the malformed ::tcl_startOfNextWord tclcore definition triggered it (fixed in tclcore 0.3.2). New testsuite args/dynamic.test (warn-once + cached-parse pin, legit-dynamic round-2 freshness pin, stderr captured via channel transform).
#0.11.1 - documentation-only: define -help gains a 'Registration styles' section (direct define vs deferred lappend-PUNKARGS + register::NAMESPACES registration - lazy @id scan and on-demand definition via update_definitions, the module-template/moduledoc style, PUNKARGS_aliases, punk::args::status timings; prefer deferred for anything large) and an 'Interpolation (tstr placeholders) and the defspace' section (display-field deferral vs parse-field expansion at first resolve vs @dynamic re-expansion; defspace rule: direct define = calling namespace, registered PUNKARGS = the argdoc child namespace when it exists even if the PUNKARGS variable is in the parent; unresolvable placeholders left silently literal - check the defspace when a literal ${...} appears; robust patterns for load-time-computed values: explicit tstr pre-expansion, build-time string-map token, or setting the variable in argdoc). Motivated by the ::after id-shape harvest misattribution (2026-07-13, corrected record in goals/G-055). #0.11.1 - documentation-only: define -help gains a 'Registration styles' section (direct define vs deferred lappend-PUNKARGS + register::NAMESPACES registration - lazy @id scan and on-demand definition via update_definitions, the module-template/moduledoc style, PUNKARGS_aliases, punk::args::status timings; prefer deferred for anything large) and an 'Interpolation (tstr placeholders) and the defspace' section (display-field deferral vs parse-field expansion at first resolve vs @dynamic re-expansion; defspace rule: direct define = calling namespace, registered PUNKARGS = the argdoc child namespace when it exists even if the PUNKARGS variable is in the parent; unresolvable placeholders left silently literal - check the defspace when a literal ${...} appears; robust patterns for load-time-computed values: explicit tstr pre-expansion, build-time string-map token, or setting the variable in argdoc). Motivated by the ::after id-shape harvest misattribution (2026-07-13, corrected record in goals/G-055).

33
src/tests/modules/punk/args/testsuites/args/errorselection.test

@ -4,10 +4,12 @@ package require tcltest
#added 2026-07-14 (agent, G-082) - single-form parse error selection. #added 2026-07-14 (agent, G-082) - single-form parse error selection.
#Characterizes the error reported when a word rejected by an OPTIONAL argument's #Characterizes the error reported when a word rejected by an OPTIONAL argument's
#allocation screen (restricted choices, basic type) subsequently overflows the #allocation screen (restricted choices, basic type) subsequently overflows the
#argument list. Current 0.12.x behaviour: the specific rejection is discarded and a #argument list. Pre-fix 0.12.x discarded the specific rejection in favour of a
#generic toomanyarguments overflow is reported (regression vs punk::args 0.5.0 - #generic toomanyarguments overflow (regression vs punk::args 0.5.0 - see
#see goals/G-082-punkargs-error-selection.md). The _GAP pins here encode that #goals/archive/G-082-punkargs-error-selection.md). History: this file's
#current behaviour and are to be flipped deliberately by the G-082 fix. #original _GAP pins encoded that pre-fix behaviour; they were flipped 2026-07-14
#(agent, G-082) to the pointed choiceviolation/typemismatch expectations when the
#allocation-rejection capture + overflow-time error selection shipped.
namespace eval ::testspace { namespace eval ::testspace {
namespace import ::tcltest::* namespace import ::tcltest::*
variable common { variable common {
@ -26,7 +28,9 @@ namespace eval ::testspace {
} }
test errsel_leader_optional_choice_reject_GAP {GAP: optional choice-restricted leader rejecting a word reports generic overflow (make.tcl dispatch shape)}\ #flipped 2026-07-14 (agent, G-082) - was errsel_leader_optional_choice_reject_GAP
#pinning {toomanyarguments 1 index 0} with no -badarg/-badval attribution
test errsel_leader_optional_choice_reject {Optional choice-restricted leader rejecting a word reports pointed choiceviolation (make.tcl dispatch shape)}\
-setup $common -body { -setup $common -body {
set docid ::testspace::errsel_leader_optional_choice set docid ::testspace::errsel_leader_optional_choice
set docids [list $docid] set docids [list $docid]
@ -34,6 +38,7 @@ namespace eval ::testspace {
set err [catch {punk::args::parse {frobnicate} withid $docid} msg opts] set err [catch {punk::args::parse {frobnicate} withid $docid} msg opts]
lappend result $err {*}[ecinfo $opts] lappend result $err {*}[ecinfo $opts]
lappend result [string match "Leading argument 'subcommand' for*must be one of the listed values:*Received: 'frobnicate'*" $msg]
}\ }\
-cleanup { -cleanup {
foreach id $docids { foreach id $docids {
@ -41,11 +46,13 @@ namespace eval ::testspace {
} }
}\ }\
-result [list\ -result [list\
1 {PUNKARGS VALIDATION {toomanyarguments 1 index 0}} {} 1 {PUNKARGS VALIDATION {choiceviolation frobnicate choices {build test help}}} {-badarg subcommand -badval frobnicate} 1
] ]
test errsel_value_optional_choice_reject_GAP {GAP: optional choice-restricted value rejecting a word reports generic overflow}\ #flipped 2026-07-14 (agent, G-082) - was errsel_value_optional_choice_reject_GAP
#pinning {toomanyarguments 1 index 0} with no -badarg/-badval attribution
test errsel_value_optional_choice_reject {Optional choice-restricted value rejecting a word reports pointed choiceviolation}\
-setup $common -body { -setup $common -body {
set docid ::testspace::errsel_value_optional_choice set docid ::testspace::errsel_value_optional_choice
set docids [list $docid] set docids [list $docid]
@ -53,6 +60,7 @@ namespace eval ::testspace {
set err [catch {punk::args::parse {frobnicate} withid $docid} msg opts] set err [catch {punk::args::parse {frobnicate} withid $docid} msg opts]
lappend result $err {*}[ecinfo $opts] lappend result $err {*}[ecinfo $opts]
lappend result [string match "Trailing argument 'subject' for*must be one of the listed values:*Received: 'frobnicate'*" $msg]
}\ }\
-cleanup { -cleanup {
foreach id $docids { foreach id $docids {
@ -60,11 +68,15 @@ namespace eval ::testspace {
} }
}\ }\
-result [list\ -result [list\
1 {PUNKARGS VALIDATION {toomanyarguments 1 index 0}} {} 1 {PUNKARGS VALIDATION {choiceviolation frobnicate choices {build test help}}} {-badarg subject -badval frobnicate} 1
] ]
test errsel_value_optional_type_reject_GAP {GAP: optional typed (int) value rejecting a word reports generic overflow}\ #flipped 2026-07-14 (agent, G-082) - was errsel_value_optional_type_reject_GAP
#pinning {toomanyarguments 1 index 0} with no -badarg/-badval attribution
#(-type integer normalizes to int in the reported typemismatch, matching the
#existing option-position typemismatch errorcode)
test errsel_value_optional_type_reject {Optional typed (int) value rejecting a word reports pointed typemismatch}\
-setup $common -body { -setup $common -body {
set docid ::testspace::errsel_value_optional_type set docid ::testspace::errsel_value_optional_type
set docids [list $docid] set docids [list $docid]
@ -72,6 +84,7 @@ namespace eval ::testspace {
set err [catch {punk::args::parse {notanumber} withid $docid} msg opts] set err [catch {punk::args::parse {notanumber} withid $docid} msg opts]
lappend result $err {*}[ecinfo $opts] lappend result $err {*}[ecinfo $opts]
lappend result [string match "Trailing argument 'num' for*requires type 'int'. Received: 'notanumber'*" $msg]
}\ }\
-cleanup { -cleanup {
foreach id $docids { foreach id $docids {
@ -79,7 +92,7 @@ namespace eval ::testspace {
} }
}\ }\
-result [list\ -result [list\
1 {PUNKARGS VALIDATION {toomanyarguments 1 index 0}} {} 1 {PUNKARGS VALIDATION {typemismatch int}} {-badarg num -badval notanumber} 1
] ]

Loading…
Cancel
Save