From 277fca766e3a61d7c7e18d15367250bb852ae315 Mon Sep 17 00:00:00 2001 From: Nikita Shoshin Date: Sat, 2 Aug 2025 20:38:58 +0400 Subject: [PATCH] gopls/internal/golang/completion: don't make unnecessary conversions for generic functions Type conversions introduced by CL 618675 are necessary only when function results are generic (e.g. `func New[T any](v ...T) Set[T]`). This CL adds an additional check to prevent unnecessary conversions. It also fixes cases where accepting a completion item would result in a compile error, like this: ``` func sort(s []int) { slices.SortFunc(s, func(a, b int) int { // compile error: cannot use interface cmp.Ordered in conversion return cmp.Compare(cmp.Ordered(a)) }) } ``` --- .../internal/golang/completion/completion.go | 4 +- gopls/internal/golang/implementation.go | 3 + .../completion/type_params_reverse_infer.txt | 78 +++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 gopls/internal/test/marker/testdata/completion/type_params_reverse_infer.txt diff --git a/gopls/internal/golang/completion/completion.go b/gopls/internal/golang/completion/completion.go index 31d58ce7cf7..3d2995bb8ec 100644 --- a/gopls/internal/golang/completion/completion.go +++ b/gopls/internal/golang/completion/completion.go @@ -2419,7 +2419,9 @@ Nodes: return inf } - if sig.TypeParams().Len() > 0 { + // Inference is necessary only when function results are generic. + var free typeparams.Free + if free.Has(sig.Results()) { targs := c.getTypeArgs(node) res := inferExpectedResultTypes(c, i) substs := reverseInferTypeArgs(sig, targs, res) diff --git a/gopls/internal/golang/implementation.go b/gopls/internal/golang/implementation.go index a0ac931968f..01d14c71ea8 100644 --- a/gopls/internal/golang/implementation.go +++ b/gopls/internal/golang/implementation.go @@ -773,6 +773,9 @@ func unify(x, y types.Type, unifier map[*types.TypeParam]types.Type) bool { // typeParams yields all the free type parameters within t that are relevant for // unification. +// +// Note: this function is tailored for the specific needs of the unification algorithm. +// Don't try to use it for other purposes, see [typeparams.Free] instead. func typeParams(t types.Type) iter.Seq[*types.TypeParam] { return func(yield func(*types.TypeParam) bool) { diff --git a/gopls/internal/test/marker/testdata/completion/type_params_reverse_infer.txt b/gopls/internal/test/marker/testdata/completion/type_params_reverse_infer.txt new file mode 100644 index 00000000000..53116ba02a5 --- /dev/null +++ b/gopls/internal/test/marker/testdata/completion/type_params_reverse_infer.txt @@ -0,0 +1,78 @@ +-- flags -- +-ignore_extra_diags + +-- declarations.go -- +package x + +import ( + "cmp" + "io" + "os" +) + +var File *os.File + +func A[T cmp.Ordered](T) int { return 0 } + +func B[T comparable](T) int { return 0 } + +func C[T int | string](T) int { return 0 } + +func D[T io.Reader](T) int { return 0 } + +-- a.go -- +package x + +func _(i int) { + i = A(File.Nam) //@acceptcompletion(re"Nam()", "Name", A) +} + +-- @A/a.go -- +package x + +func _(i int) { + i = A(File.Name()) //@acceptcompletion(re"Nam()", "Name", A) +} + +-- b.go -- +package x + +func _(i int) { + i = B(File.Nam) //@acceptcompletion(re"Nam()", "Name", B) +} + +-- @B/b.go -- +package x + +func _(i int) { + i = B(File.Name()) //@acceptcompletion(re"Nam()", "Name", B) +} + +-- c.go -- +package x + +func _(i int) { + i = C(File.Nam) //@acceptcompletion(re"Nam()", "Name", C) +} + +-- @C/c.go -- +package x + +func _(i int) { + i = C(File.Name()) //@acceptcompletion(re"Nam()", "Name", C) +} + +-- d.go -- +package x + +func _(i int) { + i = D(Fil) //@acceptcompletion(re"Fil()", "File", D) +} + +-- @D/d.go -- +package x + +func _(i int) { + i = D(File) //@acceptcompletion(re"Fil()", "File", D) +} +