# G-046 punk::args deferred -help resolution (parse-time performance + reentrancy) and rendering/value-shape fixes Status: proposed Scope: src/modules/punk/args-999999.0a1.0.tm (resolve/get_dict: display-field deferral, dynamic-cache subst path, prefix writeback, string renderer, cmdhelp-facing messages), src/modules/punk/ansi-999999.0a1.0.tm (mark_columns argdoc as the reentrancy/perf testbed), src/tests/modules/punk/args/testsuites/ (GAP tests flip; perf verification) Acceptance: parsing/argument resolution provably skips -help expansion (a definition whose -help contains a ${[...]} that would error or record its invocation shows the substitution did NOT run during a parse-only path, only for help display); first parse of punk::ansi::mark_columns drops from ~4s to well under a second with 'i punk::ansi::mark_columns' still rendering the embedded example, and a -help that parses its OWN definition id resolves or errors cleanly rather than looping; first-parse timing improves for at least one other heavily documented command (recorded in the detail file); rendering_atdynamic_multiline_help_insertion_GAP flips to all-aligned; choicegroups_imap_prefix_listwrap_GAP flips to shape-identical (prefix input yields the same plain string as exact input); the -return string renderer's cmd-help continuations align under the first line with relative indents preserved (rendering_string_renderer_characterization updated); the 'Bad number of leading values...' prefix shown by goodargs parsing in 'i string is'-style output is reworded or suppressed for the usage-display path; full punk::args and punk::ns suites pass with no non-GAP expectations weakened. ## Context Drafted 2026-07-09 from defects found (and pinned as GAP tests) during the tests-first characterization pass. User emphasis at approval: "Any performance gain we can gain from deferring -help processing out of argument resolution would be worthwhile" - deferral is the headline item, not merely a reentrancy patch. ### 1. Deferred -help resolution (performance + reentrancy) punk::args::resolve currently tstr-processes entire definition blocks, including -help sections. The in-source review note (resolve, near the block-processing loop) already observes: "-help section processing is sometimes expensive ... isn't required for parsing of arguments ... ideally we would delay expansion of -help sections until needed for display, and use a different cache key for the parsing vs display versions of the resolved definition." The reentrancy class this also fixes, reproduced 2026-07-09 with punk::ansi::mark_columns: its argdoc -help contains ${[punk::ansi::argdoc::num_columns_example]}, and that example proc itself calls mark_columns, whose punk::args::parse resolves the same still-resolving definition. First call measured at ~3.94 SECONDS (60us thereafter, once cached). The mark_columns source (punk/ansi ~6443-6470) documents the hazard - including the observed "too many nested evaluations (infinite loop?)" when [a red] etc. are used in that -help - and lists the candidate fixes: (a) avoid parsing functions in argdocs (limiting, not DRY), (b) reentrancy guard, (c) don't process -help during argument resolution. This goal takes (c) as primary (with the perf benefit), with a reentrancy guard as defence-in-depth for the display path (a -help that renders help for its OWN id must error cleanly or resolve, never loop). Design notes: - separate cache keys for parse-spec vs display-spec (per the in-source note); the @dynamic unresolved-cache (ptlist/paramlist subst mechanism) splits accordingly. - fields in display-only scope: -help on @cmd/@examples/args, -choicelabels content, @formdisplay bodies - anything not consulted by get_dict for matching/validation. (-choices/-choicealiases/-typesynopsis ARE parse-relevant or synopsis-relevant - classify explicitly in implementation.) ### 2. @dynamic multiline paramindent gap rendering_atdynamic_multiline_help_insertion_GAP: a ${$DYN_X} double substitution whose second-round result is multiline lands lines 2+ at insertion-column minus 4 (internal relative indents preserved), because the cached-path plain `subst` has no paramindent re-alignment - contrast single-level ${[cmd]} insertion which aligns fully (rendering_tstr_multiline_command_insertion). Fix: apply the same paramindent treatment to second-round substitution results. Note interaction with item 1: if display fields defer, the fix belongs in the display-time expansion path. ### 3. Prefix-normalization value shape (list-wrap) choicegroups_imap_prefix_listwrap_GAP: for choices containing backslashes (punk::imap4's {\Deleted} etc. - spaces would behave likewise), exact input stores the plain string but prefix-normalized input stores a one-element LIST ({\Deleted}) - the get_dict overwrite writeback lsets into the stored value treating a scalar as a list. Consumers get a different value shape depending on how the user typed the choice. Fix the writeback for the single-value case (the -multiple/clause paths already index deliberately). ### 4. -return string renderer cmd-help alignment rendering_string_renderer_characterization: the string renderer joins the -help first line onto the "Description: " label but drops continuations flush-left (relative indents preserved among themselves). The table renderer aligns properly. Align continuations under the first line. (The characterization test's pinned flush-left expectations get updated to the aligned form.) ### 5. cmdhelp goodargs message accuracy 'i string is' (and any command whose definition requires leaders) prefixes its usage output with "Bad number of leading values for punk::args::parse $args_remaining ..." - an internal-looking, inaccurate message produced by cmdhelp's goodargs-marking parse of the (empty) trailing args. Reword for the usage-display path or suppress (the parse is only advisory for highlighting supplied args). User: "the message is not accurate and could do with a fix up at some stage." ## Alternatives considered - Reentrancy guard alone (without deferral) for item 1 - rejected as primary: it fixes the loop but forfeits the first-parse performance win the user explicitly wants, and heavy -help processing would still run on every first parse. - Documenting "don't call parsing functions from argdocs" - rejected (the mark_columns source itself calls this limiting and non-DRY). ## Notes - GAP tests to flip: rendering_atdynamic_multiline_help_insertion_GAP, choicegroups_imap_prefix_listwrap_GAP; characterization tests to update: rendering_string_renderer_characterization. Safety net: the full 128-test punk/args suite (esp. rendering.test invariants and dev/dynamic-cache.test) and punk/ns cmdflow. - Perf evidence baseline (2026-07-09): mark_columns first parse ~3.94s / subsequent 60us (punk902z dev, this machine). tclcore moduledoc commands (e.g. ::lseq with helpers::example blocks) are candidates for the second timing datapoint. - Related: G-045 (authoring ergonomics - same resolve neighbourhood; free sequencing), G-041 (form matching will exercise resolve paths more heavily - deferral lightens its per-form attempts), G-044 (completion wants fast parse-spec resolution).