зеркало из https://github.com/golang/tools.git
gopls/internal/golang: Definition: jump to assembly
This CL adds support for jumping to the definition of a function implemented in assembly. The first Definition query jumps to the Go declaration, as usual; this func has no body. Executing a second Definition query jumps to the assembly implementation, if found. + Test, doc, relnote Fixes golang/go#60531 Change-Id: I943a05d4a2a5b6a398450131831f49cc7c0754e4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/612537 Reviewed-by: Robert Findley <rfindley@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Родитель
8fcd92f832
Коммит
5aac53c5ff
|
@ -22,6 +22,8 @@ A definition query also works in these unexpected places:
|
|||
(like [`hover`](passive.md#hover)) the location of the linked symbol.
|
||||
- On a file name in a **[`go:embed` directive](https://pkg.go.dev/embed)**,
|
||||
it returns the location of the embedded file.
|
||||
- On the declaration of a non-Go function (a `func` with no body),
|
||||
it returns the location of the assembly implementation, if any,
|
||||
|
||||
<!-- On a built-in symbol such as `append` or `unsafe.Pointer`, `definition` reports
|
||||
the location of the declaration in the builtin or unsafe pseudo-packages,
|
||||
|
|
|
@ -46,3 +46,11 @@ Editors may use this for syntax coloring.
|
|||
|
||||
Now, function signature help can be used on any identifier with a function
|
||||
signature, not just within the parentheses of a function being called.
|
||||
|
||||
## Jump to assembly definition
|
||||
|
||||
A Definition query on a reference to a function jumps to the
|
||||
function's Go `func` declaration. If the function is implemented in C
|
||||
or assembly, the function has no body. Executing a second Definition
|
||||
query (while already at the Go declaration) will navigate you to the
|
||||
assembly implementation.
|
||||
|
|
|
@ -412,18 +412,15 @@ func buildMetadata(updates map[PackageID]*metadata.Package, pkg *packages.Packag
|
|||
|
||||
updates[id] = mp
|
||||
|
||||
for _, filename := range pkg.CompiledGoFiles {
|
||||
uri := protocol.URIFromPath(filename)
|
||||
mp.CompiledGoFiles = append(mp.CompiledGoFiles, uri)
|
||||
}
|
||||
for _, filename := range pkg.GoFiles {
|
||||
uri := protocol.URIFromPath(filename)
|
||||
mp.GoFiles = append(mp.GoFiles, uri)
|
||||
}
|
||||
for _, filename := range pkg.IgnoredFiles {
|
||||
uri := protocol.URIFromPath(filename)
|
||||
mp.IgnoredFiles = append(mp.IgnoredFiles, uri)
|
||||
copyURIs := func(dst *[]protocol.DocumentURI, src []string) {
|
||||
for _, filename := range src {
|
||||
*dst = append(*dst, protocol.URIFromPath(filename))
|
||||
}
|
||||
}
|
||||
copyURIs(&mp.CompiledGoFiles, pkg.CompiledGoFiles)
|
||||
copyURIs(&mp.GoFiles, pkg.GoFiles)
|
||||
copyURIs(&mp.IgnoredFiles, pkg.IgnoredFiles)
|
||||
copyURIs(&mp.OtherFiles, pkg.OtherFiles)
|
||||
|
||||
depsByImpPath := make(map[ImportPath]PackageID)
|
||||
depsByPkgPath := make(map[PackagePath]PackageID)
|
||||
|
|
|
@ -45,10 +45,11 @@ type Package struct {
|
|||
PkgPath PackagePath
|
||||
Name PackageName
|
||||
|
||||
// these three fields are as defined by go/packages.Package
|
||||
// These fields are as defined by go/packages.Package
|
||||
GoFiles []protocol.DocumentURI
|
||||
CompiledGoFiles []protocol.DocumentURI
|
||||
IgnoredFiles []protocol.DocumentURI
|
||||
OtherFiles []protocol.DocumentURI
|
||||
|
||||
ForTest PackagePath // q in a "p [q.test]" package, else ""
|
||||
TypesSizes types.Sizes
|
||||
|
|
|
@ -12,12 +12,15 @@ import (
|
|||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/gopls/internal/cache"
|
||||
"golang.org/x/tools/gopls/internal/cache/metadata"
|
||||
"golang.org/x/tools/gopls/internal/cache/parsego"
|
||||
"golang.org/x/tools/gopls/internal/file"
|
||||
"golang.org/x/tools/gopls/internal/protocol"
|
||||
"golang.org/x/tools/gopls/internal/util/astutil"
|
||||
"golang.org/x/tools/gopls/internal/util/bug"
|
||||
"golang.org/x/tools/internal/event"
|
||||
)
|
||||
|
@ -92,6 +95,18 @@ func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
|
|||
return builtinDefinition(ctx, snapshot, obj)
|
||||
}
|
||||
|
||||
// Non-go (e.g. assembly) symbols
|
||||
//
|
||||
// When already at the definition of a Go function without
|
||||
// a body, we jump to its non-Go (C or assembly) definition.
|
||||
for _, decl := range pgf.File.Decls {
|
||||
if decl, ok := decl.(*ast.FuncDecl); ok &&
|
||||
decl.Body == nil &&
|
||||
astutil.NodeContains(decl.Name, pos) {
|
||||
return nonGoDefinition(ctx, snapshot, pkg, decl.Name.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, map the object position.
|
||||
loc, err := mapPosition(ctx, pkg.FileSet(), snapshot, obj.Pos(), adjustedObjEnd(obj))
|
||||
if err != nil {
|
||||
|
@ -353,3 +368,40 @@ func mapPosition(ctx context.Context, fset *token.FileSet, s file.Source, start,
|
|||
m := protocol.NewMapper(fh.URI(), content)
|
||||
return m.PosLocation(file, start, end)
|
||||
}
|
||||
|
||||
// nonGoDefinition returns the location of the definition of a non-Go symbol.
|
||||
// Only assembly is supported for now.
|
||||
func nonGoDefinition(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, symbol string) ([]protocol.Location, error) {
|
||||
// Examples:
|
||||
// TEXT runtime·foo(SB)
|
||||
// TEXT ·foo<ABIInternal>(SB)
|
||||
// TODO(adonovan): why does ^TEXT cause it not to match?
|
||||
pattern := regexp.MustCompile("TEXT\\b.*·(" + regexp.QuoteMeta(symbol) + ")[\\(<]")
|
||||
|
||||
for _, uri := range pkg.Metadata().OtherFiles {
|
||||
if strings.HasSuffix(uri.Path(), ".s") {
|
||||
fh, err := snapshot.ReadFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, err // context cancelled
|
||||
}
|
||||
content, err := fh.Content()
|
||||
if err != nil {
|
||||
continue // can't read file
|
||||
}
|
||||
if match := pattern.FindSubmatchIndex(content); match != nil {
|
||||
mapper := protocol.NewMapper(uri, content)
|
||||
loc, err := mapper.OffsetLocation(match[2], match[3])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []protocol.Location{loc}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(adonovan): try C files
|
||||
|
||||
// This may be reached for functions that aren't implemented
|
||||
// in assembly (e.g. compiler intrinsics like getg).
|
||||
return nil, fmt.Errorf("can't find non-Go definition of %s", symbol)
|
||||
}
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
package misc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -595,3 +597,48 @@ func _(err error) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAssemblyDefinition(t *testing.T) {
|
||||
// This test cannot be expressed as a marker test because
|
||||
// the expect package ignores markers (@loc) within a .s file.
|
||||
const src = `
|
||||
-- go.mod --
|
||||
module mod.com
|
||||
|
||||
-- foo_darwin_arm64.s --
|
||||
|
||||
// assembly implementation
|
||||
TEXT ·foo(SB),NOSPLIT,$0
|
||||
RET
|
||||
|
||||
-- a.go --
|
||||
//go:build darwin && arm64
|
||||
|
||||
package a
|
||||
|
||||
// Go declaration
|
||||
func foo(int) int
|
||||
|
||||
var _ = foo(123) // call
|
||||
`
|
||||
Run(t, src, func(t *testing.T, env *Env) {
|
||||
env.OpenFile("a.go")
|
||||
|
||||
locString := func(loc protocol.Location) string {
|
||||
return fmt.Sprintf("%s:%s", filepath.Base(loc.URI.Path()), loc.Range)
|
||||
}
|
||||
|
||||
// Definition at the call"foo(123)" takes us to the Go declaration.
|
||||
callLoc := env.RegexpSearch("a.go", regexp.QuoteMeta("foo(123)"))
|
||||
declLoc := env.GoToDefinition(callLoc)
|
||||
if got, want := locString(declLoc), "a.go:5:5-5:8"; got != want {
|
||||
t.Errorf("Definition(call): got %s, want %s", got, want)
|
||||
}
|
||||
|
||||
// Definition a second time takes us to the assembly implementation.
|
||||
implLoc := env.GoToDefinition(declLoc)
|
||||
if got, want := locString(implLoc), "foo_darwin_arm64.s:2:6-2:9"; got != want {
|
||||
t.Errorf("Definition(go decl): got %s, want %s", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче