6.0 KiB
G-083 punk::args argument relations: strict mutual exclusivity and parsekey-group integrity
Status: proposed 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) Goal: punk::args definitions can express hard mutual exclusivity - a per-argument -conflicts relation (keyed on parsekeys, checked only against received arguments so defaults never conflict) and an @opts-level strict mode making distinct-member co-occurrence within a shared-parsekey group an error - while legacy last-wins remains the default, so lsearch-class commands (grouped styles with pairwise incompatibilities like -sorted vs -glob/-regexp and -bisect vs -all/-not) parse and document faithfully without combinatorial @form explosion. Acceptance: parse raises a usage-style error with a structured errorcode (PUNKARGS VALIDATION optionconflict shape) naming both offending received arguments for -conflicts violations and strict-group co-occurrence, through both the ordinary and mash option paths with prefix abbreviations resolved before the check; define time rejects a -parsekey colliding with a distinct defined argument's name (parsekey_collides_with_defined_optname_GAP flips); legacy behaviour is untouched by default (full existing punk::args suite passes, including the pinned last-wins/prepend-defaults override idiom); the lsearch moduledoc models its documented incompatibilities with the new vocabulary and drops its "punk::args fixes required for grouped mutually exclusive options" caveat; usage/synopsis output for conflict rules is pinned by characterization tests.
Context
Review finding (2026-07-14): punk::args has no way to express "error if these arguments are combined". What exists today:
-parsekeyshared keys give SOFT exclusivity by overwrite: last received member wins under the common key. That semantics is deliberate and pinned (parsekey_repeat_ordering: overriding earlier non -multiple options must always be possible - it protects the prepend-defaults idiompunk::args::parse [list -flag default {*}$userargs]).-group/-grouphelpassociate options for usage display only; no parse semantics.- Multiple
@forms are the only strict mechanism - structural, combinatorially explosive, and they fork the documentation. - The
namespace whichmoduledoc entry works around the gap by modelling-command/-variableas one positional withliteralprefix(...)choices (exactly-one-of, but only for a fixed position, not free-order flags) and carries##todo - make mutually exclusive - (separate forms).
Evidence of need in the tclcore moduledoc alone:
- The lsearch definition states: "(documentation incomplete - punk::args fixes required for grouped mutually exclusive options and prefix calculation)".
- lsearch
-sortedis "mutually exclusive with -glob and -regexp" per the man page - yet it shares the -MATCHSTYLE parsekey group with them, whose members are otherwise last-wins. Both semantics coexist within ONE group, so a per-group boolean alone cannot model lsearch. - lsearch
-bisect"cannot be used with either -all or -not" - a cross-group conflict with no shared value key. clock clicks-milliseconds/-microsecondscarry a definition comment noting they are mutually exclusive and unmodelled.
A define-time integrity hole was found in the same review: nothing prevents a
-parsekey value colliding with a DIFFERENT defined option's name (e.g.
{-a -parsekey -b} {-b}); the two silently conflate into an implicit
shared-key group (pinned by parsekey_collides_with_defined_optname_GAP).
Approach
Design leans (settle and record here during the work):
- Per-argument
-conflicts {parsekey-or-flag ...}: violation checked against RECEIVED arguments only, after prefix/abbreviation resolution, never against defaults. Applies to any pair regardless of grouping. @opts-level strict mode for a shared-parsekey group (name TBD, e.g.-parsekeymode override|erroror-groupexclusive): distinct-member co-occurrence errors; same-member repetition stays G-053 territory (-multiple {0 1}), and legacy last-wins remains the default everywhere.- The mash (short-flag bundling) parse path implements its own parsekey logic and must share the conflict check - covered explicitly by acceptance.
- Errorcode vocabulary: extend PUNKARGS VALIDATION with an
optionconflictmember mirroring the existingoptionmissingshape, designed so a later requires-relation slots in alongside. - Usage/synopsis rendering: the group rendering machinery (OPT_GROUPS headers, parsekey hints) is most of the display substrate; conflict rules need a representation there.
Alternatives considered
- Per-group strict boolean only - insufficient: lsearch mixes last-wins and error semantics within one group (see Context).
- Modelling exclusivity via
@formexplosion - rejected: combinatorial and forks documentation; the goal exists to avoid this. - Folding into G-053 range-valued -multiple - rejected: G-053 is occurrence arity of ONE argument; this goal is relations BETWEEN arguments. They compose (shared error vocabulary, different axes).
Notes
-requires/-implies(dependency relations, e.g. lsearch-subindiceswith-index,-bisectimplying-sorted) deliberately deferred - the man page semantics there are mostly soft ("has no effect unless"); design the errorcode vocabulary so they can be added without reshaping it.- Test assets already in place (2026-07-14): characterization pins in src/tests/modules/punk/args/testsuites/args/parsekey.test (mash ordering, prefix abbreviation, collision conflation GAP) ready to flip/extend.
- Related: G-053 (occurrence arity), G-072 (compound clause types - orthogonal), G-084 (parsekey completeness fixes; its cross-member -multiple design decision should be made with this goal's vocabulary in mind).