diff --git a/gopls/internal/goxls/testdata/bar/bar.go.in b/gopls/internal/goxls/testdata/bar/bar.go.in new file mode 100644 index 00000000000..502bdf74060 --- /dev/null +++ b/gopls/internal/goxls/testdata/bar/bar.go.in @@ -0,0 +1,47 @@ +// +build go1.11 + +package bar + +import ( + "golang.org/lsptests/foo" //@item(foo, "foo", "\"golang.org/lsptests/foo\"", "package") +) + +func helper(i foo.IntFoo) {} //@item(helper, "helper", "func(i foo.IntFoo)", "func") + +func _() { + help //@complete("l", helper) + _ = foo.StructFoo{} //@complete("S", IntFoo, StructFoo) +} + +// Bar is a function. +func Bar() { //@item(Bar, "Bar", "func()", "func", "Bar is a function.") + foo.Foo() //@complete("F", Foo, IntFoo, StructFoo) + var _ foo.IntFoo //@complete("I", IntFoo, StructFoo) + foo.() //@complete("(", Foo, IntFoo, StructFoo) +} + +func _() { + var Valentine int //@item(Valentine, "Valentine", "int", "var") + + _ = foo.StructFoo{ + Valu //@complete(" //", Value) + } + _ = foo.StructFoo{ + Va //@complete("a", Value, Valentine) + } + _ = foo.StructFoo{ + Value: 5, //@complete("a", Value) + } + _ = foo.StructFoo{ + //@complete("", Value, Valentine, foo, helper, Bar) + } + _ = foo.StructFoo{ + Value: Valen //@complete("le", Valentine) + } + _ = foo.StructFoo{ + Value: //@complete(" //", Valentine, foo, helper, Bar) + } + _ = foo.StructFoo{ + Value: //@complete(" ", Valentine, foo, helper, Bar) + } +} diff --git a/gopls/internal/goxls/testdata/baz/baz.go.in b/gopls/internal/goxls/testdata/baz/baz.go.in new file mode 100644 index 00000000000..94952e1267b --- /dev/null +++ b/gopls/internal/goxls/testdata/baz/baz.go.in @@ -0,0 +1,33 @@ +// +build go1.11 + +package baz + +import ( + "golang.org/lsptests/bar" + + f "golang.org/lsptests/foo" +) + +var FooStruct f.StructFoo + +func Baz() { + defer bar.Bar() //@complete("B", Bar) + // TODO(rstambler): Test completion here. + defer bar.B + var x f.IntFoo //@complete("n", IntFoo),typdef("x", IntFoo) + bar.Bar() //@complete("B", Bar) +} + +func _() { + bob := f.StructFoo{Value: 5} + if x := bob. //@complete(" //", Value) + switch true == false { + case true: + if x := bob. //@complete(" //", Value) + case false: + } + if x := bob.Va //@complete("a", Value) + switch true == true { + default: + } +} diff --git a/gopls/internal/goxls/testdata/foo/foo.gop b/gopls/internal/goxls/testdata/foo/foo.go similarity index 93% rename from gopls/internal/goxls/testdata/foo/foo.gop rename to gopls/internal/goxls/testdata/foo/foo.go index 3e4c12a6c0b..490ff2e657c 100644 --- a/gopls/internal/goxls/testdata/foo/foo.gop +++ b/gopls/internal/goxls/testdata/foo/foo.go @@ -23,9 +23,8 @@ func _() { shadowed := 123 { shadowed := "hi" //@item(shadowed, "shadowed", "string", "var") - _ = shadowed //@complete("a", shadowed) + sha //@complete("a", shadowed) } - _ = shadowed } type IntFoo int //@item(IntFoo, "IntFoo", "int", "type") diff --git a/gopls/internal/goxls/testdata/overload/c/add.gop b/gopls/internal/goxls/testdata/overload/c/add.gop new file mode 100644 index 00000000000..1ccda63f069 --- /dev/null +++ b/gopls/internal/goxls/testdata/overload/c/add.gop @@ -0,0 +1,10 @@ +package c + +func Add = ( + func(a, b int) int { + return a + b + } + func(a, b string) string { + return a + b + } +) diff --git a/gopls/internal/goxls/testdata/foo/gop_autogen.go b/gopls/internal/goxls/testdata/overload/c/gop_autogen.go similarity index 79% rename from gopls/internal/goxls/testdata/foo/gop_autogen.go rename to gopls/internal/goxls/testdata/overload/c/gop_autogen.go index 3d4911c1e9a..111e6a11eab 100644 --- a/gopls/internal/goxls/testdata/foo/gop_autogen.go +++ b/gopls/internal/goxls/testdata/overload/c/gop_autogen.go @@ -1,3 +1,3 @@ // Code generated by gop (Go+); DO NOT EDIT. -package foo +package c diff --git a/gopls/internal/goxls/testdata/overload/c/method.gop b/gopls/internal/goxls/testdata/overload/c/method.gop new file mode 100644 index 00000000000..e53f489ed90 --- /dev/null +++ b/gopls/internal/goxls/testdata/overload/c/method.gop @@ -0,0 +1,19 @@ +package c + +type Foo struct { +} + +func (a *Foo) MulInt(b int) *Foo { //@mark(c_foo_mulInt,"MulInt") + println "mulInt" + return a +} + +func (a *Foo) MulFoo(b *Foo) *Foo { //@mark(c_foo_mulFoo,"MulFoo") + println "mulFoo" + return a +} + +func (Foo).Mul = ( + (Foo).MulInt + (Foo).MulFoo +) diff --git a/gopls/internal/goxls/testdata/overload/c/mul.gop b/gopls/internal/goxls/testdata/overload/c/mul.gop new file mode 100644 index 00000000000..8ca7de64ea6 --- /dev/null +++ b/gopls/internal/goxls/testdata/overload/c/mul.gop @@ -0,0 +1,14 @@ +package c + +func MulInt(a, b int) int { //@mark(c_mulInt,"MulInt") + return a * b +} + +func MulFloat(a, b float64) float64 { //@mark(c_mulFloat,"MulFloat") + return a * b +} + +func Mul = ( + MulInt + MulFloat +) diff --git a/gopls/internal/goxls/testdata/overload/d/utils.go b/gopls/internal/goxls/testdata/overload/d/utils.go new file mode 100644 index 00000000000..e466ad0b3f9 --- /dev/null +++ b/gopls/internal/goxls/testdata/overload/d/utils.go @@ -0,0 +1,33 @@ +package d + +const GopPackage = true + +type Foo struct { +} + +const Gopo_Foo_Mul = ".MulInt,.MulFoo" +const Gopo_Mul = "MulInt,MulFloat" + +func Add__0(a int, b int) int { //@mark(d_add__0,"Add__0") + return a + b +} + +func Add__1(a string, b string) string { //@mark(d_add__1,"Add__1") + return a + b +} + +func (a *Foo) MulInt(b int) *Foo { //@mark(d_foo_mulInt,"MulInt") + return a +} + +func (a *Foo) MulFoo(b *Foo) *Foo { //@mark(d_foo_mulFoo,"MulFoo") + return a +} + +func MulInt(a int, b int) int { //@mark(d_mulInt,"MulInt") + return a * b +} + +func MulFloat(a float64, b float64) float64 { //@mark(d_mulFloat,"MulFloat") + return a * b +} diff --git a/gopls/internal/goxls/testdata/overload/gop_autogen.go b/gopls/internal/goxls/testdata/overload/gop_autogen.go index 26751cdc203..79cb0375e98 100644 --- a/gopls/internal/goxls/testdata/overload/gop_autogen.go +++ b/gopls/internal/goxls/testdata/overload/gop_autogen.go @@ -5,4 +5,6 @@ package main import ( "golang.org/lsptests/overload/a" "golang.org/lsptests/overload/b" + "golang.org/lsptests/overload/c" + "golang.org/lsptests/overload/d" ) diff --git a/gopls/internal/goxls/testdata/overload/overload.gop b/gopls/internal/goxls/testdata/overload/overload.gop index 32a305086a3..ccc0faec882 100644 --- a/gopls/internal/goxls/testdata/overload/overload.gop +++ b/gopls/internal/goxls/testdata/overload/overload.gop @@ -1,6 +1,8 @@ import ( "golang.org/lsptests/overload/a" "golang.org/lsptests/overload/b" + "golang.org/lsptests/overload/c" + "golang.org/lsptests/overload/d" ) Demo //@godef("Demo",Demo__0) @@ -30,3 +32,23 @@ b.demo "hello" //@godef("demo",b_demo__3) var n2 b.N n2.add 100 //@godef("add",b_add__0) n2.add "hello" //@godef("add",b_add__1) + +c.mul 100, 200 //@godef("mul",c_mulInt) +c.mul 100.1, 200 //@godef("mul",c_mulFloat) + +c.add 100, 200 +c.add "hello", "world" + +var foo c.Foo +foo.mul 100 //@godef("mul",c_foo_mulInt) +foo.mul new(c.Foo) //@godef("mul",c_foo_mulFoo) + +d.mul 100, 200 //@godef("mul",d_mulInt) +d.mul 100.1, 200 //@godef("mul",d_mulFloat) + +d.add 100, 200 //@godef("add",d_add__0) +d.add "hello", "world" //@godef("add",d_add__1) + +var foo2 d.Foo +foo2.mul 100 //@godef("mul",d_foo_mulInt) +foo2.mul new(d.Foo) //@godef("mul",d_foo_mulFoo) diff --git a/gopls/internal/goxls/testdata/overload/overload.gop.golden b/gopls/internal/goxls/testdata/overload/overload.gop.golden index c190441b1a6..6f4d4cf0639 100644 --- a/gopls/internal/goxls/testdata/overload/overload.gop.golden +++ b/gopls/internal/goxls/testdata/overload/overload.gop.golden @@ -108,6 +108,102 @@ func b.GopDemo() ``` [`b.GopDemo` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/b#GopDemo) +-- c_add-hoverdef -- +```go +func c.Add__1(a string, b string) string +``` + +[`c.Add__1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/c#Add__1) +-- c_add__0-hoverdef -- +```go +func c.Add__0(a int, b int) int +``` + +[`c.Add__0` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/c#Add__0) +-- c_foo_mulFoo-hoverdef -- +```go +func (*c.Foo).MulFoo(b *c.Foo) *c.Foo +``` + +[`(c.Foo).MulFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/c#Foo.MulFoo) +-- c_foo_mulInt-hoverdef -- +```go +func (*c.Foo).MulInt(b int) *c.Foo +``` + +[`(c.Foo).MulInt` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/c#Foo.MulInt) +-- c_foo_mul__0-hoverdef -- +```go +func (*c.Foo).MulInt(b int) *c.Foo +``` + +[`(c.Foo).MulInt` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/c#Foo.MulInt) +-- c_foo_mul__1-hoverdef -- +```go +func (*c.Foo).MulFoo(b *c.Foo) *c.Foo +``` + +[`(c.Foo).MulFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/c#Foo.MulFoo) +-- c_mulFloat-hoverdef -- +```go +func c.MulFloat(a float64, b float64) float64 +``` + +[`c.MulFloat` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/c#MulFloat) +-- c_mulInt-hoverdef -- +```go +func c.MulInt(a int, b int) int +``` + +[`c.MulInt` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/c#MulInt) +-- c_mul__0-hoverdef -- +```go +func c.MulInt(a int, b int) int +``` + +[`c.MulInt` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/c#MulInt) +-- c_mul__1-hoverdef -- +```go +func c.MulFloat(a float64, b float64) float64 +``` + +[`c.MulFloat` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/c#MulFloat) +-- d_add__0-hoverdef -- +```go +func d.Add__0(a int, b int) int +``` + +[`d.Add__0` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/d#Add__0) +-- d_add__1-hoverdef -- +```go +func d.Add__1(a string, b string) string +``` + +[`d.Add__1` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/d#Add__1) +-- d_foo_mulFoo-hoverdef -- +```go +func (*d.Foo).MulFoo(b *d.Foo) *d.Foo +``` + +[`(d.Foo).MulFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/d#Foo.MulFoo) +-- d_foo_mulInt-hoverdef -- +```go +func (*d.Foo).MulInt(b int) *d.Foo +``` + +[`(d.Foo).MulInt` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/d#Foo.MulInt) +-- d_mulFloat-hoverdef -- +```go +func d.MulFloat(a float64, b float64) float64 +``` + +[`d.MulFloat` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/d#MulFloat) +-- d_mulInt-hoverdef -- +```go +func d.MulInt(a int, b int) int +``` + +[`d.MulInt` on pkg.go.dev](https://pkg.go.dev/golang.org/lsptests/overload/d#MulInt) -- pkg_add__0-hoverdef -- ```go func (*pkg.N).Add__0(a int) diff --git a/gopls/internal/goxls/testdata/overload/overload_rank.gop.in b/gopls/internal/goxls/testdata/overload/overload_rank.gop.in index ebe907bb451..6ff7610593f 100644 --- a/gopls/internal/goxls/testdata/overload/overload_rank.gop.in +++ b/gopls/internal/goxls/testdata/overload/overload_rank.gop.in @@ -3,15 +3,15 @@ import ( ) func _() { - demo //@item(apkgDemo1, "demo", "Go+ overload funcs\n\n- func()\n- func(n int) int\n- func(n1 int, n2 int)\n- func(s ...string)", "func") - Demo //@item(apkgDemo2, "Demo", "Go+ overload funcs\n\n- func()\n- func(n int) int\n- func(n1 int, n2 int)\n- func(s ...string)", "func") + demo //@item(apkgDemo1, "demo", "Go+ overload func\n\n- func()\n- func(n int) int\n- func(n1 int, n2 int)\n- func(s ...string)", "func") + Demo //@item(apkgDemo2, "Demo", "Go+ overload func\n\n- func()\n- func(n int) int\n- func(n1 int, n2 int)\n- func(s ...string)", "func") a.demo //@rank(" //", apkgDemo1, apkgDemo2) } func _() { - add //@item(apkgAdd1, "add", "Go+ overload funcs\n\n- func(a int)\n- func(a ...string)", "method") - Add //@item(apkgAdd2, "Add", "Go+ overload funcs\n\n- func(a int)\n- func(a ...string)", "method") + add //@item(apkgAdd1, "add", "Go+ overload func\n\n- func(a int)\n- func(a ...string)", "method") + Add //@item(apkgAdd2, "Add", "Go+ overload func\n\n- func(a int)\n- func(a ...string)", "method") var n N n.add //@rank(" //", apkgAdd1, apkgAdd2) diff --git a/gopls/internal/goxls/testdata/overload/overload_unimport.gop.in b/gopls/internal/goxls/testdata/overload/overload_unimport.gop.in new file mode 100644 index 00000000000..502f6aeb9a0 --- /dev/null +++ b/gopls/internal/goxls/testdata/overload/overload_unimport.gop.in @@ -0,0 +1,24 @@ +func _() { + c.add //@unimported("add", c_add_unimported) + c.mul //@unimported("mul", c_mul_unimported) + c.mulInt //@unimported("mulInt", c_mulInt_unimported) + c.MulInt //@unimported("MulInt", c_MulInt_unimported) +} + +func _() { + d.add //@unimported("add", d_add_unimported) + d.mul //@unimported("mul", d_mul_unimported) + d.mulInt //@unimported("mulInt", d_mulInt_unimported) + d.MulInt //@unimported("MulInt", d_MulInt_unimported) +} + + +/* c.add */ //@item(c_add_unimported, "add", "Go+ overload func\n\nfunc (from \"golang.org/lsptests/overload/c\")", "func") +/* c.mul */ //@item(c_mul_unimported, "mul", "Go+ overload func\n\nfunc (from \"golang.org/lsptests/overload/c\")", "func") +/* c.mulInt */ //@item(c_mulInt_unimported, "mulInt", "Go+ alias func\n\nfunc (from \"golang.org/lsptests/overload/c\")", "func") +/* c.MulInt */ //@item(c_MulInt_unimported, "MulInt", "func (from \"golang.org/lsptests/overload/c\")", "func") + +/* d.add */ //@item(d_add_unimported, "add", "Go+ overload func\n\nfunc (from \"golang.org/lsptests/overload/d\")", "func") +/* d.mul */ //@item(d_mul_unimported, "mul", "Go+ overload func\n\nfunc (from \"golang.org/lsptests/overload/d\")", "func") +/* d.mulInt */ //@item(d_mulInt_unimported, "mulInt", "Go+ alias func\n\nfunc (from \"golang.org/lsptests/overload/d\")", "func") +/* d.MulInt */ //@item(d_MulInt_unimported, "MulInt", "func (from \"golang.org/lsptests/overload/d\")", "func") diff --git a/gopls/internal/goxls/testdata/summary.txt.golden b/gopls/internal/goxls/testdata/summary.txt.golden index 70d47218cfc..7ee2de39051 100644 --- a/gopls/internal/goxls/testdata/summary.txt.golden +++ b/gopls/internal/goxls/testdata/summary.txt.golden @@ -1,9 +1,9 @@ -- summary -- CallHierarchyCount = 0 CodeLensCount = 4 -CompletionsCount = 18 +CompletionsCount = 36 CompletionSnippetCount = 12 -UnimportedCompletionsCount = 0 +UnimportedCompletionsCount = 8 DeepCompletionsCount = 0 FuzzyCompletionsCount = 1 RankedCompletionsCount = 15 @@ -13,8 +13,8 @@ FoldingRangesCount = 0 SemanticTokenCount = 0 SuggestedFixCount = 0 MethodExtractionCount = 0 -DefinitionsCount = 23 -TypeDefinitionsCount = 1 +DefinitionsCount = 33 +TypeDefinitionsCount = 2 HighlightsCount = 0 InlayHintsCount = 0 RenamesCount = 0 diff --git a/gopls/internal/goxls/testdata/summary_go1.18.txt.golden b/gopls/internal/goxls/testdata/summary_go1.18.txt.golden index 70d47218cfc..7ee2de39051 100644 --- a/gopls/internal/goxls/testdata/summary_go1.18.txt.golden +++ b/gopls/internal/goxls/testdata/summary_go1.18.txt.golden @@ -1,9 +1,9 @@ -- summary -- CallHierarchyCount = 0 CodeLensCount = 4 -CompletionsCount = 18 +CompletionsCount = 36 CompletionSnippetCount = 12 -UnimportedCompletionsCount = 0 +UnimportedCompletionsCount = 8 DeepCompletionsCount = 0 FuzzyCompletionsCount = 1 RankedCompletionsCount = 15 @@ -13,8 +13,8 @@ FoldingRangesCount = 0 SemanticTokenCount = 0 SuggestedFixCount = 0 MethodExtractionCount = 0 -DefinitionsCount = 23 -TypeDefinitionsCount = 1 +DefinitionsCount = 33 +TypeDefinitionsCount = 2 HighlightsCount = 0 InlayHintsCount = 0 RenamesCount = 0 diff --git a/gopls/internal/goxls/testdata/summary_go1.21.txt.golden b/gopls/internal/goxls/testdata/summary_go1.21.txt.golden index 70d47218cfc..7ee2de39051 100644 --- a/gopls/internal/goxls/testdata/summary_go1.21.txt.golden +++ b/gopls/internal/goxls/testdata/summary_go1.21.txt.golden @@ -1,9 +1,9 @@ -- summary -- CallHierarchyCount = 0 CodeLensCount = 4 -CompletionsCount = 18 +CompletionsCount = 36 CompletionSnippetCount = 12 -UnimportedCompletionsCount = 0 +UnimportedCompletionsCount = 8 DeepCompletionsCount = 0 FuzzyCompletionsCount = 1 RankedCompletionsCount = 15 @@ -13,8 +13,8 @@ FoldingRangesCount = 0 SemanticTokenCount = 0 SuggestedFixCount = 0 MethodExtractionCount = 0 -DefinitionsCount = 23 -TypeDefinitionsCount = 1 +DefinitionsCount = 33 +TypeDefinitionsCount = 2 HighlightsCount = 0 InlayHintsCount = 0 RenamesCount = 0 diff --git a/gopls/internal/lsp/source/completion/completion.go b/gopls/internal/lsp/source/completion/completion.go index 541f5a6c381..a3bb9bfdea3 100644 --- a/gopls/internal/lsp/source/completion/completion.go +++ b/gopls/internal/lsp/source/completion/completion.go @@ -3308,141 +3308,3 @@ func forEachPackageMember(content []byte, f func(tok token.Token, id *ast.Ident, } } } - -// goxls: quickParse -func (c *gopCompleter) quickParse(ctx context.Context, cMu *sync.Mutex, enough *int32, selName string, relevances map[string]float64, needImport bool) func(uri span.URI, m *source.Metadata) error { - return func(uri span.URI, m *source.Metadata) error { - if atomic.LoadInt32(enough) != 0 { - return nil - } - - fh, err := c.snapshot.ReadFile(ctx, uri) - if err != nil { - return err - } - content, err := fh.Content() - if err != nil { - return err - } - path := string(m.PkgPath) - forEachPackageMember(content, func(tok token.Token, id *ast.Ident, fn *ast.FuncDecl) { - if atomic.LoadInt32(enough) != 0 { - return - } - - if !id.IsExported() { - return - } - - cMu.Lock() - score := c.matcher.Score(id.Name) - cMu.Unlock() - - if selName != "_" && score == 0 { - return // not a match; avoid constructing the completion item below - } - - // The only detail is the kind and package: `var (from "example.com/foo")` - // TODO(adonovan): pretty-print FuncDecl.FuncType or TypeSpec.Type? - // TODO(adonovan): should this score consider the actual c.matcher.Score - // of the item? How does this compare with the deepState.enqueue path? - item := CompletionItem{ - Label: id.Name, - Detail: fmt.Sprintf("%s (from %q)", strings.ToLower(tok.String()), m.PkgPath), - InsertText: id.Name, - Score: unimportedScore(relevances[path]), - } - switch tok { - case token.FUNC: - item.Kind = protocol.FunctionCompletion - case token.VAR: - item.Kind = protocol.VariableCompletion - case token.CONST: - item.Kind = protocol.ConstantCompletion - case token.TYPE: - // Without types, we can't distinguish Class from Interface. - item.Kind = protocol.ClassCompletion - } - - if needImport { - imp := &importInfo{importPath: path} - if imports.ImportPathToAssumedName(path) != string(m.Name) { - imp.name = string(m.Name) - } - item.AdditionalTextEdits, _ = c.importEdits(imp) - } - - // For functions, add a parameter snippet. - if fn != nil { - var sn snippet.Builder - sn.WriteText(id.Name) - - paramList := func(open, close string, list *ast.FieldList) { - if list != nil { - var cfg printer.Config // slight overkill - var nparams int - param := func(name string, typ ast.Expr) { - if nparams > 0 { - sn.WriteText(", ") - } - nparams++ - if c.opts.placeholders { - sn.WritePlaceholder(func(b *snippet.Builder) { - var buf strings.Builder - buf.WriteString(name) - buf.WriteByte(' ') - cfg.Fprint(&buf, token.NewFileSet(), typ) - b.WriteText(buf.String()) - }) - } else { - sn.WriteText(name) - } - } - - sn.WriteText(open) - for _, field := range list.List { - if field.Names != nil { - for _, name := range field.Names { - param(name.Name, field.Type) - } - } else { - param("_", field.Type) - } - } - sn.WriteText(close) - } - } - - paramList("[", "]", typeparams.ForFuncType(fn.Type)) - paramList("(", ")", fn.Type.Params) - - item.snippet = &sn - } - - cMu.Lock() - c.items = append(c.items, item) - // goxls func alias - if tok == token.FUNC { - if alias, ok := hasAliasName(id.Name); ok { - var noSnip bool - switch len(fn.Type.Params.List) { - case 0: - noSnip = true - case 1: - if fn.Recv != nil { - if _, ok := fn.Type.Params.List[0].Type.(*ast.Ellipsis); ok { - noSnip = true - } - } - } - c.items = append(c.items, cloneAliasItem(item, id.Name, alias, 0.0001, noSnip)) - } - } - if len(c.items) >= unimportedMemberTarget { - atomic.StoreInt32(enough, 1) - } - cMu.Unlock() - }) - return nil - } -} diff --git a/gopls/internal/lsp/source/completion/completion_gox.go b/gopls/internal/lsp/source/completion/completion_gox.go index f137be3478c..08489c96e38 100644 --- a/gopls/internal/lsp/source/completion/completion_gox.go +++ b/gopls/internal/lsp/source/completion/completion_gox.go @@ -1189,7 +1189,7 @@ func (c *gopCompleter) selector(ctx context.Context, sel *ast.SelectorExpr) erro return err } path := string(m.PkgPath) - gopForEachPackageMember(content, func(tok token.Token, id *ast.Ident, fn *ast.FuncDecl) { + gopForEachPackageMember(content, func(tok token.Token, id *ast.Ident, fnType *ast.FuncType, isOverload bool) { if atomic.LoadInt32(&enough) != 0 { return } @@ -1237,7 +1237,7 @@ func (c *gopCompleter) selector(ctx context.Context, sel *ast.SelectorExpr) erro } // For functions, add a parameter snippet. - if fn != nil { + if fnType != nil { var sn snippet.Builder sn.WriteText(id.Name) @@ -1277,28 +1277,23 @@ func (c *gopCompleter) selector(ctx context.Context, sel *ast.SelectorExpr) erro } } - paramList("[", "]", typeparams.ForFuncType(fn.Type)) - paramList("(", ")", fn.Type.Params) + paramList("[", "]", typeparams.ForFuncType(fnType)) + paramList("(", ")", fnType.Params) item.snippet = &sn } + if isOverload { + // ast.OverloadFuncDecl + item.isOverload = true + item.Detail = "Go+ overload func\n\n" + item.Detail + } cMu.Lock() c.items = append(c.items, item) // goxls func alias if tok == token.FUNC { if alias, ok := hasAliasName(id.Name); ok { - var noSnip bool - switch len(fn.Type.Params.List) { - case 0: - noSnip = true - case 1: - if fn.Recv != nil { - if _, ok := fn.Type.Params.List[0].Type.(*ast.Ellipsis); ok { - noSnip = true - } - } - } + noSnip := !isOverload && len(fnType.Params.List) == 0 c.items = append(c.items, cloneAliasItem(item, id.Name, alias, 0.0001, noSnip)) } } @@ -1310,12 +1305,20 @@ func (c *gopCompleter) selector(ctx context.Context, sel *ast.SelectorExpr) erro return nil } + // recheck is gop index overload check + recheck := newRecheckOverload() + for _, path := range paths { + m := known[source.PackagePath(path)] + if len(m.CompiledGopFiles) > 0 { + recheck.pkgs[source.PackagePath(path)] = true + } + } // Extract the package-level candidates using a quick parse. - quickParseGo := c.quickParse(ctx, &cMu, &enough, sel.Sel.Name, relevances, needImport) + quickParseGo := c.quickParse(ctx, &cMu, &enough, sel.Sel.Name, relevances, needImport, recheck) var g errgroup.Group for _, path := range paths { m := known[source.PackagePath(path)] - for _, uri := range m.CompiledGopFiles { // goxls: TODO - how to handle Go files? + for _, uri := range m.CompiledGopFiles { uri := uri g.Go(func() error { return quickParse(uri, m) @@ -1331,7 +1334,7 @@ func (c *gopCompleter) selector(ctx context.Context, sel *ast.SelectorExpr) erro if err := g.Wait(); err != nil { return err } - + recheck.checkOverload(c) // In addition, we search in the module cache using goimports. ctx, cancel := context.WithCancel(ctx) var mu sync.Mutex @@ -2427,7 +2430,7 @@ Nodes: // TYPE/VAR/CONST/FUNC declaration in the Go source file, based on a // quick partial parse. fn is non-nil only for function declarations. // The AST position information is garbage. -func gopForEachPackageMember(content []byte, f func(tok token.Token, id *ast.Ident, fn *ast.FuncDecl)) { +func gopForEachPackageMember(content []byte, f func(tok token.Token, id *ast.Ident, fnType *ast.FuncType, isOverload bool)) { purged := goxlsastutil.PurgeFuncBodies(content) file, _ := parserutil.ParseFile(token.NewFileSet(), "", purged, 0) for _, decl := range file.Decls { @@ -2437,15 +2440,38 @@ func gopForEachPackageMember(content []byte, f func(tok token.Token, id *ast.Ide switch spec := spec.(type) { case *ast.ValueSpec: // var/const for _, id := range spec.Names { - f(decl.Tok, id, nil) + f(decl.Tok, id, nil, false) } case *ast.TypeSpec: - f(decl.Tok, spec.Name, nil) + f(decl.Tok, spec.Name, nil, false) } } case *ast.FuncDecl: if decl.Recv == nil { - f(token.FUNC, decl.Name, decl) + f(token.FUNC, decl.Name, decl.Type, false) + } + case *ast.OverloadFuncDecl: + if decl.Recv == nil && ast.IsExported(decl.Name.Name) { + var typ *ast.FuncType + FindType: + for _, expr := range decl.Funcs { + switch expr := expr.(type) { + case *ast.Ident: + if !ast.IsExported(expr.Name) { + continue + } + if obj := file.Scope.Lookup(expr.Name); obj != nil { + if d, ok := obj.Decl.(*ast.FuncDecl); ok { + typ = d.Type + break FindType + } + } + case *ast.FuncLit: + typ = expr.Type + break FindType + } + } + f(token.FUNC, decl.Name, typ, true) } } } diff --git a/gopls/internal/lsp/source/completion/format_gox.go b/gopls/internal/lsp/source/completion/format_gox.go index 9078fc8b000..6a142df4938 100644 --- a/gopls/internal/lsp/source/completion/format_gox.go +++ b/gopls/internal/lsp/source/completion/format_gox.go @@ -142,7 +142,7 @@ Suffixes: if _, objs := gogen.CheckSigFuncExObjects(sig); len(objs) > 0 { isOverload = true var buf bytes.Buffer - buf.WriteString("Go+ overload funcs\n") + buf.WriteString("Go+ overload func\n") for _, o := range objs { if isIndexOverload(o.Name(), obj.Name()) { c.seen[o] = true @@ -295,6 +295,17 @@ func isIndexOverload(fn string, name string) bool { return false } +func maybleIndexOverload(fn string) bool { + n := len(fn) + if (n > 3) && (fn[n-3] == '_') && (fn[n-2] == '_') { + c := fn[n-1] + if (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') { + return true + } + } + return false +} + func (c *gopCompleter) formatBuiltin(ctx context.Context, cand candidate) (CompletionItem, error) { obj := cand.obj item := CompletionItem{ diff --git a/gopls/internal/lsp/source/completion/unimport_gox.go b/gopls/internal/lsp/source/completion/unimport_gox.go new file mode 100644 index 00000000000..493896fdb18 --- /dev/null +++ b/gopls/internal/lsp/source/completion/unimport_gox.go @@ -0,0 +1,300 @@ +package completion + +import ( + "context" + "fmt" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "sort" + "strconv" + "strings" + "sync" + "sync/atomic" + + goplsastutil "golang.org/x/tools/gopls/internal/astutil" + "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/gopls/internal/lsp/snippet" + "golang.org/x/tools/gopls/internal/lsp/source" + "golang.org/x/tools/gopls/internal/span" + "golang.org/x/tools/internal/imports" + "golang.org/x/tools/internal/typeparams" +) + +type recheckItem struct { + CompletionItem + noSnip bool +} + +type recheckOverload struct { + pkgs map[source.PackagePath]bool // gop package + items map[source.PackagePath][]recheckItem // index overload funcs + gopo map[source.PackagePath][]recheckItem // gopo overload funcs +} + +func newRecheckOverload() *recheckOverload { + return &recheckOverload{ + make(map[source.PackagePath]bool), + make(map[source.PackagePath][]recheckItem), + make(map[source.PackagePath][]recheckItem), + } +} + +func (recheck *recheckOverload) checkOverload(c *gopCompleter) { + // check gop package index overload + for pkg, items := range recheck.items { + if recheck.pkgs[pkg] { + names := make(map[string]bool) + sort.Slice(items, func(i, j int) bool { + return items[i].Label < items[j].Label + }) + for _, item := range items { + id := item.Label[:len(item.Label)-3] + if !names[id] { + names[id] = true + item.isOverload = true + item.Detail = "Go+ overload func\n\n" + item.Detail + c.items = append(c.items, cloneAliasItem(item.CompletionItem, item.Label, id, 0, false)) + if alias, ok := hasAliasName(id); ok { + c.items = append(c.items, cloneAliasItem(item.CompletionItem, item.Label, alias, 0.0001, item.noSnip)) + } + } + } + } else { + for _, item := range items { + c.items = append(c.items, item.CompletionItem) + if alias, ok := hasAliasName(item.Label); ok { + c.items = append(c.items, cloneAliasItem(item.CompletionItem, item.Label, alias, 0.0001, item.noSnip)) + } + } + } + } + // check gop package gopo overload + for pkg, items := range recheck.gopo { + if recheck.pkgs[pkg] { + for _, item := range items { + item.isOverload = true + item.Detail = "Go+ overload func\n\n" + item.Detail + c.items = append(c.items, item.CompletionItem) + if alias, ok := hasAliasName(item.Label); ok { + c.items = append(c.items, cloneAliasItem(item.CompletionItem, item.Label, alias, 0.0001, item.noSnip)) + } + } + } + } +} + +// goxls: quickParse +func (c *gopCompleter) quickParse(ctx context.Context, cMu *sync.Mutex, enough *int32, selName string, relevances map[string]float64, needImport bool, recheck *recheckOverload) func(uri span.URI, m *source.Metadata) error { + return func(uri span.URI, m *source.Metadata) error { + if atomic.LoadInt32(enough) != 0 { + return nil + } + + fh, err := c.snapshot.ReadFile(ctx, uri) + if err != nil { + return err + } + content, err := fh.Content() + if err != nil { + return err + } + path := string(m.PkgPath) + goForEachPackageMember(content, func(tok token.Token, id *ast.Ident, fn *ast.FuncDecl, gopo bool) { + if atomic.LoadInt32(enough) != 0 { + return + } + + if !id.IsExported() { + return + } + + cMu.Lock() + score := c.matcher.Score(id.Name) + cMu.Unlock() + + if selName != "_" && score == 0 { + return // not a match; avoid constructing the completion item below + } + + // The only detail is the kind and package: `var (from "example.com/foo")` + // TODO(adonovan): pretty-print FuncDecl.FuncType or TypeSpec.Type? + // TODO(adonovan): should this score consider the actual c.matcher.Score + // of the item? How does this compare with the deepState.enqueue path? + item := CompletionItem{ + Label: id.Name, + Detail: fmt.Sprintf("%s (from %q)", strings.ToLower(tok.String()), m.PkgPath), + InsertText: id.Name, + Score: unimportedScore(relevances[path]), + } + switch tok { + case token.FUNC: + item.Kind = protocol.FunctionCompletion + case token.VAR: + item.Kind = protocol.VariableCompletion + case token.CONST: + item.Kind = protocol.ConstantCompletion + case token.TYPE: + // Without types, we can't distinguish Class from Interface. + item.Kind = protocol.ClassCompletion + } + + if needImport { + imp := &importInfo{importPath: path} + if imports.ImportPathToAssumedName(path) != string(m.Name) { + imp.name = string(m.Name) + } + item.AdditionalTextEdits, _ = c.importEdits(imp) + } + + // For functions, add a parameter snippet. + if fn != nil { + var sn snippet.Builder + sn.WriteText(id.Name) + + paramList := func(open, close string, list *ast.FieldList) { + if list != nil { + var cfg printer.Config // slight overkill + var nparams int + param := func(name string, typ ast.Expr) { + if nparams > 0 { + sn.WriteText(", ") + } + nparams++ + if c.opts.placeholders { + sn.WritePlaceholder(func(b *snippet.Builder) { + var buf strings.Builder + buf.WriteString(name) + buf.WriteByte(' ') + cfg.Fprint(&buf, token.NewFileSet(), typ) + b.WriteText(buf.String()) + }) + } else { + sn.WriteText(name) + } + } + + sn.WriteText(open) + for _, field := range list.List { + if field.Names != nil { + for _, name := range field.Names { + param(name.Name, field.Type) + } + } else { + param("_", field.Type) + } + } + sn.WriteText(close) + } + } + + paramList("[", "]", typeparams.ForFuncType(fn.Type)) + paramList("(", ")", fn.Type.Params) + + item.snippet = &sn + } + + cMu.Lock() + if tok == token.CONST { + if id.Name == "GopPackage" { + recheck.pkgs[m.PkgPath] = true + } + } + if tok == token.FUNC { + noSnip := len(fn.Type.Params.List) == 0 + if maybleIndexOverload(id.Name) { + recheck.items[m.PkgPath] = append(recheck.items[m.PkgPath], recheckItem{item, noSnip}) + } else if gopo { + recheck.gopo[m.PkgPath] = append(recheck.gopo[m.PkgPath], recheckItem{item, noSnip}) + } else { + c.items = append(c.items, item) + // goxls func alias + if alias, ok := hasAliasName(id.Name); ok { + c.items = append(c.items, cloneAliasItem(item, id.Name, alias, 0.0001, noSnip)) + } + } + } else { + c.items = append(c.items, item) + } + if len(c.items) >= unimportedMemberTarget { + atomic.StoreInt32(enough, 1) + } + cMu.Unlock() + }) + return nil + } +} + +// goForEachPackageMember calls f(tok, id, fnType, gopo) for each package-level +// TYPE/VAR/CONST/FUNC declaration in the Go source file, based on a +// quick partial parse. fn is non-nil only for function declarations. +// The AST position information is garbage. +func goForEachPackageMember(content []byte, f func(tok token.Token, id *ast.Ident, fn *ast.FuncDecl, gopo bool)) { + purged := goplsastutil.PurgeFuncBodies(content) + file, _ := parser.ParseFile(token.NewFileSet(), "", purged, 0) + for _, decl := range file.Decls { + switch decl := decl.(type) { + case *ast.GenDecl: + for _, spec := range decl.Specs { + switch spec := spec.(type) { + case *ast.ValueSpec: // var/const + for _, id := range spec.Names { + if decl.Tok == token.CONST && strings.HasPrefix(id.Name, "Gopo_") { + if md := checkTypeMethod(id.Name[5:]); md.typ == "" && ast.IsExported(md.name) { + if fn := foundGopoExportOverload(file, spec); fn != nil { + f(token.FUNC, ast.NewIdent(md.name), fn, true) + } + } + } + f(decl.Tok, id, nil, false) + } + case *ast.TypeSpec: + f(decl.Tok, spec.Name, nil, false) + } + } + case *ast.FuncDecl: + if decl.Recv == nil { + f(token.FUNC, decl.Name, decl, false) + } + } + } +} + +func foundGopoExportOverload(f *ast.File, spec *ast.ValueSpec) (fn *ast.FuncDecl) { + if lit, ok := spec.Values[0].(*ast.BasicLit); ok { + if s, err := strconv.Unquote(lit.Value); err == nil { + for _, v := range strings.Split(s, ",") { + if obj := f.Scope.Lookup(v); obj != nil && ast.IsExported(obj.Name) { + if fn, ok := obj.Decl.(*ast.FuncDecl); ok { + return fn + } + } + } + } + } + return nil +} + +type mthd struct { + typ string + name string +} + +// Func (no _ func name) +// _Func (with _ func name) +// TypeName_Method (no _ method name) +// _TypeName__Method (with _ method name) +func checkTypeMethod(name string) mthd { + if pos := strings.IndexByte(name, '_'); pos >= 0 { + if pos == 0 { + t := name[1:] + if pos = strings.Index(t, "__"); pos <= 0 { + return mthd{"", t} // _Func + } + return mthd{t[:pos], t[pos+2:]} // _TypeName__Method + } + return mthd{name[:pos], name[pos+1:]} // TypeName_Method + } + return mthd{"", name} // Func +}