From c99edecc6b5d733cacb3ad58f57f092c02ccaca2 Mon Sep 17 00:00:00 2001 From: Tim King Date: Thu, 10 Oct 2024 10:39:36 -0700 Subject: [PATCH] gopls/internal/golang/completion: add alias support for literals Derived from https://go.dev/cl/603935. Change-Id: I1a75562da194a8e5cc532f1b52fbc1f9d9b2907a Reviewed-on: https://go-review.googlesource.com/c/tools/+/619478 Reviewed-by: Alan Donovan Commit-Queue: Tim King LUCI-TryBot-Result: Go LUCI --- gopls/internal/golang/completion/literal.go | 46 ++++++++++++------- .../test/marker/testdata/completion/alias.txt | 9 ++++ 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/gopls/internal/golang/completion/literal.go b/gopls/internal/golang/completion/literal.go index 129045d99e2..50ddb1fc26e 100644 --- a/gopls/internal/golang/completion/literal.go +++ b/gopls/internal/golang/completion/literal.go @@ -517,28 +517,30 @@ func (c *completer) typeNameSnippet(literalType types.Type, qf types.Qualifier) var ( snip snippet.Builder typeName string - // TODO(adonovan): think more about aliases. - // They should probably be treated more like Named. - named, _ = types.Unalias(literalType).(*types.Named) + pnt, _ = literalType.(typesinternal.NamedOrAlias) // = *Named | *Alias ) - if named != nil && named.Obj() != nil && named.TypeParams().Len() > 0 && !c.fullyInstantiated(named) { + tparams := typesinternal.TypeParams(pnt) + if tparams.Len() > 0 && !c.fullyInstantiated(pnt) { + // tparams.Len() > 0 implies pnt != nil. + // Inv: pnt is not "error" or "unsafe.Pointer", so pnt.Obj() != nil and has a Pkg(). + // We are not "fully instantiated" meaning we have type params that must be specified. - if pkg := qf(named.Obj().Pkg()); pkg != "" { + if pkg := qf(pnt.Obj().Pkg()); pkg != "" { typeName = pkg + "." } // We do this to get "someType" instead of "someType[T]". - typeName += named.Obj().Name() + typeName += pnt.Obj().Name() snip.WriteText(typeName + "[") if c.opts.placeholders { - for i := 0; i < named.TypeParams().Len(); i++ { + for i := 0; i < tparams.Len(); i++ { if i > 0 { snip.WriteText(", ") } snip.WritePlaceholder(func(snip *snippet.Builder) { - snip.WriteText(types.TypeString(named.TypeParams().At(i), qf)) + snip.WriteText(types.TypeString(tparams.At(i), qf)) }) } } else { @@ -557,25 +559,35 @@ func (c *completer) typeNameSnippet(literalType types.Type, qf types.Qualifier) // fullyInstantiated reports whether all of t's type params have // specified type args. -func (c *completer) fullyInstantiated(t *types.Named) bool { - tps := t.TypeParams() - tas := t.TypeArgs() +func (c *completer) fullyInstantiated(t typesinternal.NamedOrAlias) bool { + targs := typesinternal.TypeArgs(t) + tparams := typesinternal.TypeParams(t) - if tps.Len() != tas.Len() { + if tparams.Len() != targs.Len() { return false } - for i := 0; i < tas.Len(); i++ { - // TODO(adonovan) think about generic aliases. - switch ta := types.Unalias(tas.At(i)).(type) { + for i := 0; i < targs.Len(); i++ { + targ := targs.At(i) + + // The expansion of an alias can have free type parameters, + // whether or not the alias itself has type parameters: + // + // func _[K comparable]() { + // type Set = map[K]bool // free(Set) = {K} + // type MapTo[V] = map[K]V // free(Map[foo]) = {V} + // } + // + // So, we must Unalias. + switch targ := types.Unalias(targ).(type) { case *types.TypeParam: // A *TypeParam only counts as specified if it is currently in // scope (i.e. we are in a generic definition). - if !c.typeParamInScope(ta) { + if !c.typeParamInScope(targ) { return false } case *types.Named: - if !c.fullyInstantiated(ta) { + if !c.fullyInstantiated(targ) { return false } } diff --git a/gopls/internal/test/marker/testdata/completion/alias.txt b/gopls/internal/test/marker/testdata/completion/alias.txt index e4c340e3f1f..6e5a92253d5 100644 --- a/gopls/internal/test/marker/testdata/completion/alias.txt +++ b/gopls/internal/test/marker/testdata/completion/alias.txt @@ -26,6 +26,15 @@ func takesGeneric[a int | string](s[a]) { takesGeneric() //@rank(")", tpInScopeLit),snippet(")", tpInScopeLit, "s[a]{\\}") } +func _() { + s[int]{} //@item(tpInstLit, "s[int]{}", "", "var") + takesGeneric[int]() //@rank(")", tpInstLit),snippet(")", tpInstLit, "s[int]{\\}") + + "s[...]{}" //@item(tpUninstLit, "s[...]{}", "", "var") + takesGeneric() //@rank(")", tpUninstLit),snippet(")", tpUninstLit, "s[${1:}]{\\}") +} + + type myType int //@item(flType, "myType", "int", "type") type myt[T int] myType //@item(aflType, "myt[T]", "int", "type")