Repository: spf13/cobra
Pull request #2234 (2025-02-16 17:44:53)

fix CompletionFunc implementation
- **chore: add missing non-regression test for completions**
- **fix: completion helper retro-compatibility**

This PR is about this
- Fixes code implemented in https://github.com/spf13/cobra/pull/2220
- discussion that occurred here https://github.com/spf13/cobra/pull/2231#issuecomment-2661510587
- breaking changes reported here: https://github.com/docker/cli/pull/5827
- possible fix tried on docker-cli: https://github.com/docker/cli/issues/5828


ccoVeille: I provided a separate commit so anyone reviewing could fetch and check.

I covered the possible issue such as 

```go
type UserCompletionTypeHelper func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)

type UserCompletionTypeAliasHelper = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
```

Please review @marckhouzam @thaJeztah @tassa-yoniso-manasi-karoto

Thanks
marckhouzam: I'm reviewing this now.  Thanks for the quick fix @ccoVeille !

Patch:
diff --git a/completions.go b/completions.go
index cedb4d7..a1752f7 100644
--- a/completions.go
+++ b/completions.go
@@ -129,7 +129,7 @@ type CompletionOptions struct {
 type Completion = string
 
 // CompletionFunc is a function that provides completion results.
-type CompletionFunc func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective)
+type CompletionFunc = func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective)
 
 // CompletionWithDesc returns a [Completion] with a description by using the TAB delimited format.
 func CompletionWithDesc(choice string, description string) Completion {


Test patch:
diff --git a/completions_test.go b/completions_test.go
index 964e389..89da3d5 100644
--- a/completions_test.go
+++ b/completions_test.go
@@ -2885,13 +2885,99 @@ func TestCompleteWithRootAndLegacyArgs(t *testing.T) {
 		"arg1",
 		"arg2",
 		":4",
-		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
+		"Completion ended with directive: ShellCompDirectiveNoFileComp", "",
+	}, "\n")
 
 	if output != expected {
 		t.Errorf("expected: %q, got: %q", expected, output)
 	}
 }
 
+func TestCompletionFuncCompatibility(t *testing.T) {
+	t.Run("validate signature", func(t *testing.T) {
+		t.Run("format with []string", func(t *testing.T) {
+			var userComp func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
+
+			// check against new signature
+			var _ CompletionFunc = userComp
+
+			// check Command accepts
+			cmd := Command{
+				ValidArgsFunction: userComp,
+			}
+
+			_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
+		})
+
+		t.Run("format with []Completion", func(t *testing.T) {
+			var userComp func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective)
+
+			// check against new signature
+			var _ CompletionFunc = userComp
+
+			// check Command accepts
+			cmd := Command{
+				ValidArgsFunction: userComp,
+			}
+
+			_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
+		})
+
+		t.Run("format with CompletionFunc", func(t *testing.T) {
+			var userComp CompletionFunc
+
+			// check helper against old signature
+			var _ func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) = userComp
+			var _ func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) = userComp
+
+			// check Command accepts
+			cmd := Command{
+				ValidArgsFunction: userComp,
+			}
+
+			_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
+		})
+	})
+
+	t.Run("user defined completion helper", func(t *testing.T) {
+		t.Run("type helper", func(t *testing.T) {
+			// This is a type that may have been defined by the user of the library
+			// This replicates the issue https://github.com/docker/cli/issues/5827
+			// https://github.com/docker/cli/blob/b6e7eba4470ecdca460e4b63270fba8179674ad6/cli/command/completion/functions.go#L18
+			type UserCompletionTypeHelper func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
+
+			var userComp UserCompletionTypeHelper
+
+			// Here we are validating the existing type validates the CompletionFunc type
+			var _ CompletionFunc = userComp
+
+			cmd := Command{
+				ValidArgsFunction: userComp,
+			}
+
+			_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
+		})
+
+		t.Run("type alias helper", func(t *testing.T) {
+			// This is a type that may have been defined by the user of the library
+			// This replicates the possible fix that was tried here https://github.com/docker/cli/pull/5828
+			// https://github.com/docker/cli/blob/ae3d4db9f658259dace9dee515718be7c1b1f517/cli/command/completion/functions.go#L18
+			type UserCompletionTypeAliasHelper = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
+
+			var userComp UserCompletionTypeAliasHelper
+
+			// Here we are validating the existing type validates the CompletionFunc type
+			var _ CompletionFunc = userComp
+
+			cmd := Command{
+				ValidArgsFunction: userComp,
+			}
+
+			_ = cmd.RegisterFlagCompletionFunc("foo", userComp)
+		})
+	})
+}
+
 func TestFixedCompletions(t *testing.T) {
 	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
 	choices := []string{"apple", "banana", "orange"}