diff --git a/gopls/internal/lsp/source/definition_gox.go b/gopls/internal/lsp/source/definition_gox.go index b14c77438ef..51e9a79239c 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,14 @@ func GopDefinition(ctx context.Context, snapshot Snapshot, fh FileHandle, positi return []protocol.Location{loc}, nil } + typeEnd := adjustedObjEnd(obj) + if anonyOvFunc != nil { + // goxls:if the object is an anonymous overload function + // use the end of the 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 +299,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 local package + if strings.HasSuffix(string(declURI), ".gop") { + metas, err := snapshot.MetadataForFile(ctx, declURI) + if err != nil { + return nil, nil, nil, false + } + for _, m := range metas { + pkgs, err := snapshot.TypeCheck(ctx, m.ID) + if err != nil { + return nil, nil, nil, false + } + localPkg := pkgs[0] + if funcLit, ovObj, ok := inPkg(localPkg); ok { + return localPkg, 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..08c746054e2 --- /dev/null +++ b/gopls/internal/regtest/misc/definition_gox_test.go @@ -0,0 +1,448 @@ +package misc + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/lsp/regtest" +) + +const anonyOverload = ` +-- 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) +println add("Hello", "World") +-- 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)) + fmt.Println(add__1("Hello", "World")) +} +` + +func TestAnonyOverload(t *testing.T) { + testCases := []defTest{ + {`test.gop`, `println (add)\(100, 7\)`, "def.gop", `func\(a, b int\) int`}, + {`test.gop`, `println (add)\("Hello", "World"\)`, "def.gop", `func\(a, b string\) string`}, + } + runGoToDefinitionTest(t, anonyOverload, testCases) +} + +const overloadMixAnonyAndNamed = ` +-- 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) +println mul("Hello", "World") +println mul(200.5, 2.3) +-- 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)) + fmt.Println(mul__1("Hello", "World")) + fmt.Println(mulFloat(200.5, 2.3)) +} +` + +func TestOverloadMixAnonyAndNamed(t *testing.T) { + testCases := []defTest{ + {`test.gop`, `println (mul)\(100, 7\)`, "def.gop", `func (mulInt)\(a, b int\) int`}, + {`test.gop`, `println (mul)\("Hello", "World"\)`, "def.gop", `func\(a, b string\) string`}, + {`test.gop`, `println (mul)\(200.5, 2.3\)`, "def.gop", `func (mulFloat)\(a, b float64\) float64`}, + } + runGoToDefinitionTest(t, overloadMixAnonyAndNamed, testCases) +} + +const overloadMethod = ` +-- 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) +var d = a.mul(&foo{}) +-- 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) +var d = a.mulFoo(&foo{}) + +func main() { +} +` + +func TestOverloadMethod(t *testing.T) { + mulIntLocReg := `func \(a \*foo\) (mulInt)\(b int\) \*foo` + mulFooLocReg := `func \(a \*foo\) (mulFoo)\(b \*foo\) \*foo` + testCases := []defTest{ + {`test.gop`, `var c = a.(mul)\(100\)`, "def.gop", mulIntLocReg}, + {`test.gop`, `var d = a.(mul)\(&foo\{\}\)`, "def.gop", mulFooLocReg}, + {`def.gop`, `\(foo\)\.(mulInt)`, "def.gop", mulIntLocReg}, + {`def.gop`, `\(foo\)\.(mulFoo)`, "def.gop", mulFooLocReg}, + } + runGoToDefinitionTest(t, overloadMethod, testCases) +} + +const overloadFromGo = ` +-- 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") +}) + +n.onKey("hello", func(key string) { + println("hello world", key) +}) + +n.onKey([]string{"hello", "world"}, 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") + }) + n.OnKey__1("hello", func(key string) { + fmt.Println("hello world", key) + }) + n.OnKey__2([]string{"hello", "world"}, func() { + fmt.Println("hello world") + }) +} +` + +func TestOverloadFromGo(t *testing.T) { + testCases := []defTest{ + {`test.gop`, `n.(onKey)\("hello", func\(\)`, "def.go", `OnKey__0`}, + {`test.gop`, `n.(onKey)\("hello", func\(key string\)`, "def.go", `OnKey__1`}, + {`test.gop`, `n.(onKey)\(\[\]string\{"hello", "world"\}, func\(\)`, "def.go", `OnKey__2`}, + } + runGoToDefinitionTest(t, overloadFromGo, testCases) +} + +const overloadCrossPkg = ` +-- go.mod -- +module mod.com + +go 1.19 +-- lib/lib.gop -- +package lib + +func Add = ( + func(a, b int) int { + return a + b + } + func(a, b string) string { + return a + b + } +) + +func MulInt(a, b int) int { + return a * b +} + +func MulFloat(a, b float64) float64 { + return a * b +} + +func Mul = ( + MulInt + func(x, y string) string { + return x + y + } + MulFloat +) + +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 +) +-- lib/gop_autogen.go -- +package lib + +const GopPackage = true +const _ = true +const Gopo_Mul = "MulInt,,MulFloat" + +type Foo struct { +} + +const Gopo_Foo_Mul = ".MulInt,.MulFoo" +func Add__0(a int, b int) int { + return a + b +} +func Add__1(a string, b string) string { + return a + b +} +func MulInt(a int, b int) int { + return a * b +} +func Mul__1(x string, y string) string { + return x + y +} +func MulFloat(a float64, b float64) float64 { + return a * b +} +func (a *Foo) MulInt(b int) *Foo { + return a +} +func (a *Foo) MulFoo(b *Foo) *Foo { + return a +} +-- lib2/lib2.go -- +package lib2 + +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() +} +-- main.gop -- +import ( + "mod.com/lib" + "mod.com/lib2" +) + +println lib.Add(100, 7) +println lib.Add("Hello", "World") + +println lib.Mul(100, 7) +println lib.Mul("Hello", "World") +println lib.Mul(200.5, 2.3) + +var a *lib.Foo +var c = a.Mul(100) +var d = a.Mul(&lib.Foo{}) + +_ = c +_ = d + +n := &lib2.N{} + +n.OnKey("hello", func() { + println("hello world") +}) + +n.OnKey("hello", func(key string) { + println("hello world", key) +}) + +n.OnKey([]string{"hello", "world"}, func() { + println("hello world") +}) +-- gop_autogen.go -- +package main + +import ( + "fmt" + "mod.com/lib" + "mod.com/lib2" +) + +const _ = true +func main() { + fmt.Println(lib.Add__0(100, 7)) + fmt.Println(lib.Add__1("Hello", "World")) + fmt.Println(lib.MulInt(100, 7)) + fmt.Println(lib.Mul__1("Hello", "World")) + fmt.Println(lib.MulFloat(200.5, 2.3)) + var a *lib.Foo + var c = a.MulInt(100) + var d = a.MulFoo(&lib.Foo{}) + _ = c + _ = d + n := &lib2.N{} + n.OnKey__0("hello", func() { + fmt.Println("hello world") + }) + n.OnKey__1("hello", func(key string) { + fmt.Println("hello world", key) + }) + n.OnKey__2([]string{"hello", "world"}, func() { + fmt.Println("hello world") + }) +} +` + +// Test cross package 's overload definition +func TestOverloadCrossPkg(t *testing.T) { + testCases := []defTest{ + // anony overload + {`main.gop`, `println lib.(Add)\(100, 7\)`, "lib/lib.gop", `func\(a, b int\) int`}, + {`main.gop`, `println lib.(Add)\("Hello", "World"\)`, "lib/lib.gop", `func\(a, b string\) string`}, + + // named & anony overload + {`main.gop`, `println lib.(Mul)\(100, 7\)`, "lib/lib.gop", `func (MulInt)\(a, b int\) int`}, + {`main.gop`, `println lib.(Mul)\("Hello", "World"\)`, "lib/lib.gop", `func\(x, y string\) string`}, + {`main.gop`, `println lib.(Mul)\(200.5, 2.3\)`, "lib/lib.gop", `func (MulFloat)\(a, b float64\) float64`}, + + // method + {`main.gop`, `var c = a.(Mul)\(100\)`, "lib/lib.gop", `func \(a \*Foo\) (MulInt)\(b int\) \*Foo`}, + {`main.gop`, `var d = a.(Mul)\(&lib\.Foo\{\}\)`, "lib/lib.gop", `func \(a \*Foo\) (MulFoo)\(b \*Foo\) \*Foo`}, + + // from go + {`main.gop`, `n.(OnKey)\("hello", func\(\)`, "lib2/lib2.go", `OnKey__0`}, + {`main.gop`, `n.(OnKey)\("hello", func\(key string\)`, "lib2/lib2.go", `OnKey__1`}, + {`main.gop`, `n.(OnKey)\(\[\]string\{"hello", "world"\}, func\(\)`, "lib2/lib2.go", `OnKey__2`}, + } + runGoToDefinitionTest(t, overloadCrossPkg, testCases) +} + +type defTest struct { + findLocFile string + findLocReg string + wantFile string + wantLocReg string +} + +func runGoToDefinitionTest(t *testing.T, files string, testCases []defTest) { + Run(t, files, func(t *testing.T, env *Env) { + for _, test := range testCases { + env.OpenFile(test.findLocFile) + loc := env.GoToDefinition(env.RegexpSearch(test.findLocFile, test.findLocReg)) + name := env.Sandbox.Workdir.URIToPath(loc.URI) + if name != test.wantFile { + t.Errorf("GoToDefinition: got file %q, want %q", name, test.wantFile) + } + if want := env.RegexpSearch(test.wantFile, test.wantLocReg); loc != want { + t.Errorf("GoToDefinition:%v got location %v, want %v", test.findLocReg, loc, want) + } + } + }) +}