diff --git a/gopls/internal/lsp/source/definition_gox.go b/gopls/internal/lsp/source/definition_gox.go index b14c77438ef..fb422233eeb 100644 --- a/gopls/internal/lsp/source/definition_gox.go +++ b/gopls/internal/lsp/source/definition_gox.go @@ -9,6 +9,7 @@ import ( "fmt" "go/types" "log" + "strings" "github.com/goplus/gop/ast" "github.com/goplus/gop/token" @@ -16,6 +17,8 @@ import ( "golang.org/x/tools/gopls/internal/goxls" "golang.org/x/tools/gopls/internal/goxls/parserutil" "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/gopls/internal/lsp/safetoken" + "golang.org/x/tools/gopls/internal/span" "golang.org/x/tools/internal/event" ) @@ -83,6 +86,14 @@ func GopDefinition(ctx context.Context, snapshot Snapshot, fh FileHandle, positi if obj == nil { return nil, nil } + + var anonyOvFunc *ast.FuncLit //goxls:overload anonymous member + if ovPkg, ovFuncLit, ovObj, ok := IsOverloadAnonymousMember(ctx, snapshot, pkg, obj); ok { + pkg = ovPkg + obj = ovObj + anonyOvFunc = ovFuncLit + } + if goxls.DbgDefinition { log.Println("gopReferencedObject ret:", obj, "pos:", obj.Pos()) } @@ -135,8 +146,12 @@ func GopDefinition(ctx context.Context, snapshot Snapshot, fh FileHandle, positi return []protocol.Location{loc}, nil } + typeEnd := adjustedObjEnd(obj) + if anonyOvFunc != nil { // goxls: anonymous overload function + typeEnd = anonyOvFunc.Type.End() + } // Finally, map the object position. - loc, err := mapPosition(ctx, pkg.FileSet(), snapshot, obj.Pos(), adjustedObjEnd(obj)) + loc, err := mapPosition(ctx, pkg.FileSet(), snapshot, obj.Pos(), typeEnd) if goxls.DbgDefinition { log.Println("gopReferencedObject mapPosition:", obj, "err:", err, "loc:", loc) } @@ -282,3 +297,50 @@ func gopImportDefinition(ctx context.Context, s Snapshot, pkg Package, pgf *Pars return locs, nil } + +// goxls:match in current package & variants +func IsOverloadAnonymousMember(ctx context.Context, snapshot Snapshot, pkg Package, obj types.Object) (Package, *ast.FuncLit, types.Object, bool) { + if _, ok := obj.(*types.Func); !ok { + return nil, nil, nil, false + } + + declPosn := safetoken.StartPosition(pkg.FileSet(), obj.Pos()) + declURI := span.URIFromPath(declPosn.Filename) + + inPkg := func(searchPkg Package) (*ast.FuncLit, types.Object, bool) { + fset := searchPkg.FileSet() + for ov, om := range searchPkg.GopTypesInfo().Implicits { + if anonyOvFunc, ok := ov.(*ast.FuncLit); ok { + funPos := safetoken.StartPosition(fset, anonyOvFunc.Pos()) + if declPosn.Offset == funPos.Offset { + return anonyOvFunc, om, true + } + } + } + return nil, nil, false + } + + // goxls:match in current package + if funcLit, ovObj, ok := inPkg(pkg); ok { + return pkg, funcLit, ovObj, true + } + + // goxls:match in variants package + if strings.HasSuffix(string(declURI), ".gop") { + variants, err := snapshot.MetadataForFile(ctx, declURI) + if err != nil { + return nil, nil, nil, false + } + for _, m := range variants { + varPkgs, err := snapshot.TypeCheck(ctx, m.ID) + if err != nil { + return nil, nil, nil, false + } + varPkg := varPkgs[0] + if funcLit, ovObj, ok := inPkg(varPkg); ok { + return varPkg, funcLit, ovObj, true + } + } + } + return nil, nil, nil, false +} diff --git a/gopls/internal/regtest/misc/definition_gox_test.go b/gopls/internal/regtest/misc/definition_gox_test.go new file mode 100644 index 00000000000..00f58323266 --- /dev/null +++ b/gopls/internal/regtest/misc/definition_gox_test.go @@ -0,0 +1,234 @@ +package misc + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/lsp/regtest" +) + +const overloadDefinition1 = ` +-- go.mod -- +module mod.com + +go 1.19 +-- def.gop -- +func add = ( + func(a, b int) int { + return a + b + } + func(a, b string) string { + return a + b + } +) +-- test.gop -- +println add(100, 7) +-- gop_autogen.go -- +package main + +import "fmt" + +const _ = true +func add__0(a int, b int) int { + return a + b +} +func add__1(a string, b string) string { + return a + b +} +func main() { + fmt.Println(add__0(100, 7)) +} +` + +func TestOverloadDefinition1(t *testing.T) { + Run(t, overloadDefinition1, func(t *testing.T, env *Env) { + env.OpenFile("test.gop") + loc := env.GoToDefinition(env.RegexpSearch("test.gop", "add")) + name := env.Sandbox.Workdir.URIToPath(loc.URI) + if want := "def.gop"; name != want { + t.Errorf("GoToDefinition: got file %q, want %q", name, want) + } + // goxls : match the 'func' position of the corresponding overloaded function + if want := env.RegexpSearch("def.gop", `func\(a, b int\) int`); loc != want { + t.Errorf("GoToDefinition: got location %v, want %v", loc, want) + } + }) +} + +const overloadDefinition2 = ` +-- go.mod -- +module mod.com + +go 1.19 +-- def.gop -- +func mulInt(a, b int) int { + return a * b +} + +func mulFloat(a, b float64) float64 { + return a * b +} + +func mul = ( + mulInt + func(a, b string) string { + return a + b + } + mulFloat +) +-- test.gop -- +println mul(100, 7) +-- gop_autogen.go -- +package main + +import "fmt" + +const _ = true +const Gopo_mul = "mulInt,,mulFloat" +func mulInt(a int, b int) int { + return a * b +} +func mul__1(a string, b string) string { + return a + b +} +func mulFloat(a float64, b float64) float64 { + return a * b +} +func main() { + fmt.Println(mulInt(100, 7)) +} +` + +func TestOverloadDefinition2(t *testing.T) { + Run(t, overloadDefinition2, func(t *testing.T, env *Env) { + env.OpenFile("test.gop") + loc := env.GoToDefinition(env.RegexpSearch("test.gop", `println (mul)\(100, 7\)`)) + name := env.Sandbox.Workdir.URIToPath(loc.URI) + if want := "def.gop"; name != want { + t.Errorf("GoToDefinition: got file %q, want %q", name, want) + } + // goxls: match mulInt + if want := env.RegexpSearch("def.gop", `func (mulInt)\(a, b int\) int`); loc != want { + t.Errorf("GoToDefinition: got location %v, want %v", loc, want) + } + }) +} + +const overloadDefinition3 = ` +-- go.mod -- +module mod.com + +go 1.19 +-- def.gop -- +type foo struct { +} + +func (a *foo) mulInt(b int) *foo { + return a +} + +func (a *foo) mulFoo(b *foo) *foo { + return a +} + +func (foo).mul = ( + (foo).mulInt + (foo).mulFoo +) +-- test.gop -- +var a *foo +var c = a.mul(100) +-- gop_autogen.go -- +package main + +const _ = true + +type foo struct { +} + +const Gopo_foo_mul = ".mulInt,.mulFoo" +func (a *foo) mulInt(b int) *foo { + return a +} +func (a *foo) mulFoo(b *foo) *foo { + return a +} + +var a *foo +var c = a.mulInt(100) + +func main() { +} +` + +func TestOverloadDefinition3(t *testing.T) { + Run(t, overloadDefinition3, func(t *testing.T, env *Env) { + env.OpenFile("test.gop") + loc := env.GoToDefinition(env.RegexpSearch("test.gop", "mul")) + name := env.Sandbox.Workdir.URIToPath(loc.URI) + if want := "def.gop"; name != want { + t.Errorf("GoToDefinition: got file %q, want %q", name, want) + } + // goxls: match mulInt + if want := env.RegexpSearch("def.gop", `func \(a \*foo\) (mulInt)\(b int\) \*foo`); loc != want { + t.Errorf("GoToDefinition: got location %v, want %v", loc, want) + } + }) +} + +const overloadDefinition4 = ` +-- go.mod -- +module mod.com + +go 1.19 +-- def.go -- +package main + +const GopPackage = true + +type N struct { +} + +func (m *N) OnKey__0(a string, fn func()) { + fn() +} + +func (m *N) OnKey__1(a string, fn func(key string)) { + fn(a) +} + +func (m *N) OnKey__2(a []string, fn func()) { + fn() +} +-- test.gop -- +n := &N{} + +n.onKey("hello", func() { + println("hello world") +}) +-- gop_autogen.go -- +package main + +import "fmt" + +const _ = true +func main() { + n := &N{} + n.OnKey__0("hello", func() { + fmt.Println("hello world") + }) +} +` + +func TestOverloadDefinition4(t *testing.T) { + Run(t, overloadDefinition4, func(t *testing.T, env *Env) { + env.OpenFile("test.gop") + loc := env.GoToDefinition(env.RegexpSearch("test.gop", "onKey")) + name := env.Sandbox.Workdir.URIToPath(loc.URI) + if want := "def.go"; name != want { + t.Errorf("GoToDefinition: got file %q, want %q", name, want) + } + if want := env.RegexpSearch("def.go", `OnKey__0`); loc != want { + t.Errorf("GoToDefinition: got location %v, want %v", loc, want) + } + }) +}