From f79f446726bc446d3b6408d5a003fd86bfe51855 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Tue, 3 Nov 2026 13:12:22 +0000 Subject: [PATCH] Restore comparability of completion metadata --- completion.go | 18 +++++++++++++++--- completion_test.go | 8 ++++++++ docs/roadmap/remediation.md | 4 +++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/completion.go b/completion.go index c7580d1..ecc56f0 100644 --- a/completion.go +++ b/completion.go @@ -61,7 +61,12 @@ type CompletionChoice struct { // value requests no value suggestions; hints do not constrain accepted input. type CompletionHint struct { kind completionHintKind - choices []CompletionChoice + choices *completionChoicesData +} + +// Keep the public metadata structs comparable without exposing mutable choices. +type completionChoicesData struct { + values []CompletionChoice } type completionHintKind uint8 @@ -78,7 +83,11 @@ const ( // values and values containing a newline, tab, or NUL cannot be presented as // suggestions, but remain valid input if the command's converter accepts them. func CompleteChoices(choices ...CompletionChoice) CompletionHint { - return CompletionHint{kind: completionChoices, choices: append([]CompletionChoice(nil), choices...)} + hint := CompletionHint{kind: completionChoices} + if len(choices) != 0 { + hint.choices = &completionChoicesData{values: append([]CompletionChoice(nil), choices...)} + } + return hint } // CompleteFiles requests shell filename completion. @@ -95,7 +104,10 @@ func bridgeCompletion(hint CompletionHint) commandmeta.CompletionHint { switch hint.kind { case completionChoices: result.Kind = commandmeta.CompletionChoices - for _, choice := range hint.choices { + if hint.choices == nil { + break + } + for _, choice := range hint.choices.values { result.Choices = append(result.Choices, commandmeta.CompletionChoice{Value: choice.Value, Description: choice.Description}) } case completionFiles: diff --git a/completion_test.go b/completion_test.go index 9433780..da57e83 100644 --- a/completion_test.go +++ b/completion_test.go @@ -8,6 +8,14 @@ import ( commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta" ) +// Existing consumer code can compare these metadata values or use them as keys. +var ( + _ map[FlagMeta]struct{} + _ map[ArgumentMeta]struct{} + _ = (FlagMeta{}) == (FlagMeta{}) + _ = (ArgumentMeta{}) == (ArgumentMeta{}) +) + func TestCompletionHintsFollowCapturedBindings(t *testing.T) { type inner struct{ values Supplied[[]string] } type outer struct{ flags FlagValues[inner] } diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index 8660688..b71a739 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -88,7 +88,9 @@ control record cannot appear as a candidate. ### 4. Preserve comparability of public metadata -Priority: P2. Status: open. +Priority: P2. Status: complete. + +- [x] Completed: Choice hints now point to privately owned copied data. Compile checks cover comparison and map keys for both metadata types; the root suite passes. [CompletionHint](../../completion.go#L62) embeds a slice by value. Adding it to [FlagMeta and ArgumentMeta](../../command.go#L93) makes both existing exported