From 30286179281983e8b8f000ed10eb14c8d25a217f Mon Sep 17 00:00:00 2001 From: xushiwei Date: Sun, 22 Oct 2023 04:18:24 +0800 Subject: [PATCH 1/2] lsp/definition --- gopls/internal/lsp/definition.go | 2 + gopls/internal/lsp/definition_gox.go | 5 + .../lsp/source/completion/definition_gox.go | 2 - gopls/internal/lsp/source/definition_gox.go | 197 ++++++++++++++++++ gopls/internal/lsp/source/view_gox.go | 10 + 5 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 gopls/internal/lsp/definition_gox.go diff --git a/gopls/internal/lsp/definition.go b/gopls/internal/lsp/definition.go index 89cf86efc05..29b832bedcd 100644 --- a/gopls/internal/lsp/definition.go +++ b/gopls/internal/lsp/definition.go @@ -29,6 +29,8 @@ func (s *Server) definition(ctx context.Context, params *protocol.DefinitionPara switch kind := snapshot.View().FileKind(fh); kind { case source.Tmpl: return template.Definition(snapshot, fh, params.Position) + case source.Gop: // goxls: Go+ + return source.GopDefinition(ctx, snapshot, fh, params.Position) case source.Go: // Partial support for jumping from linkname directive (position at 2nd argument). locations, err := source.LinknameDefinition(ctx, snapshot, fh, params.Position) diff --git a/gopls/internal/lsp/definition_gox.go b/gopls/internal/lsp/definition_gox.go new file mode 100644 index 00000000000..e598d3b34fc --- /dev/null +++ b/gopls/internal/lsp/definition_gox.go @@ -0,0 +1,5 @@ +// Copyright 2023 The GoPlus Authors (goplus.org). All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lsp diff --git a/gopls/internal/lsp/source/completion/definition_gox.go b/gopls/internal/lsp/source/completion/definition_gox.go index 5e4249d65f9..efbd4e875fa 100644 --- a/gopls/internal/lsp/source/completion/definition_gox.go +++ b/gopls/internal/lsp/source/completion/definition_gox.go @@ -4,7 +4,6 @@ package completion -/* import ( "go/types" "strings" @@ -98,4 +97,3 @@ func gopDefinition(path []ast.Node, obj types.Object, pgf *source.ParsedGopFile) } return ans, sel } -*/ diff --git a/gopls/internal/lsp/source/definition_gox.go b/gopls/internal/lsp/source/definition_gox.go index 51b800f2b10..3859ea57699 100644 --- a/gopls/internal/lsp/source/definition_gox.go +++ b/gopls/internal/lsp/source/definition_gox.go @@ -5,13 +5,133 @@ package source import ( + "context" + "fmt" "go/types" "log" "github.com/goplus/gop/ast" "github.com/goplus/gop/token" + "golang.org/x/tools/gopls/internal/bug" + "golang.org/x/tools/gopls/internal/goxls/parserutil" + "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/internal/event" ) +// GopDefinition handles the textDocument/definition request for Go files. +func GopDefinition(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) ([]protocol.Location, error) { + ctx, done := event.Start(ctx, "source.GopDefinition") + defer done() + + pkg, pgf, err := NarrowestPackageForGopFile(ctx, snapshot, fh.URI()) + if err != nil { + return nil, err + } + pos, err := pgf.PositionPos(position) + if err != nil { + return nil, err + } + + // Handle the case where the cursor is in an import. + importLocations, err := gopImportDefinition(ctx, snapshot, pkg, pgf, pos) + if err != nil { + return nil, err + } + if len(importLocations) > 0 { + return importLocations, nil + } + + // Handle the case where the cursor is in the package name. + // We use "<= End" to accept a query immediately after the package name. + // goxls: a Go+ file maybe no `package xxx` + // if pgf.File != nil && pgf.File.Name.Pos() <= pos && pos <= pgf.File.Name.End() { + if pgf.File != nil && pgf.HasPkgDecl() && pgf.File.Name.Pos() <= pos && pos <= pgf.File.Name.End() { + for _, pgf := range pkg.CompiledGoFiles() { + if pgf.File.Name != nil && pgf.File.Doc != nil { + loc, err := pgf.NodeLocation(pgf.File.Name) + if err != nil { + return nil, err + } + return []protocol.Location{loc}, nil + } + } + // If there's no package documentation, just use current file. + declFile := pgf + for _, pgf := range pkg.CompiledGopFiles() { + if pgf.HasPkgDecl() && pgf.File.Doc != nil { + declFile = pgf + break + } + } + loc, err := declFile.NodeLocation(declFile.File.Name) + if err != nil { + return nil, err + } + return []protocol.Location{loc}, nil + } + + // The general case: the cursor is on an identifier. + _, obj, _ := gopReferencedObject(pkg, pgf, pos) + if obj == nil { + return nil, nil + } + + // Handle objects with no position: builtin, unsafe. + if !obj.Pos().IsValid() { + var pgf *ParsedGoFile + if obj.Parent() == types.Universe { + // pseudo-package "builtin" + builtinPGF, err := snapshot.BuiltinFile(ctx) + if err != nil { + return nil, err + } + pgf = builtinPGF + + } else if obj.Pkg() == types.Unsafe { + // package "unsafe" + unsafe := snapshot.Metadata("unsafe") + if unsafe == nil { + return nil, fmt.Errorf("no metadata for package 'unsafe'") + } + uri := unsafe.GoFiles[0] + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + pgf, err = snapshot.ParseGo(ctx, fh, ParseFull&^SkipObjectResolution) + if err != nil { + return nil, err + } + } else { + return nil, bug.Errorf("internal error: no position for %v", obj.Name()) + } + // Inv: pgf ∈ {builtin,unsafe}.go + + // Use legacy (go/ast) object resolution. + astObj := pgf.File.Scope.Lookup(obj.Name()) + if astObj == nil { + // Every built-in should have documentation syntax. + return nil, bug.Errorf("internal error: no object for %s", obj.Name()) + } + decl, ok := astObj.Decl.(ast.Node) + if !ok { + return nil, bug.Errorf("internal error: no declaration for %s", obj.Name()) + } + loc, err := pgf.PosLocation(decl.Pos(), decl.Pos()+token.Pos(len(obj.Name()))) + if err != nil { + return nil, err + } + return []protocol.Location{loc}, nil + } + + // Finally, map the object position. + loc, err := mapPosition(ctx, pkg.FileSet(), snapshot, obj.Pos(), adjustedObjEnd(obj)) + if err != nil { + return nil, err + } + return []protocol.Location{loc}, nil +} + // gopReferencedObject returns the identifier and object referenced at the // specified position, which must be within the file pgf, for the purposes of // definition/hover/call hierarchy operations. It returns a nil object if no @@ -68,3 +188,80 @@ func gopReferencedObject(pkg Package, pgf *ParsedGopFile, pos token.Pos) (*ast.I } return nil, nil, nil } + +// gopImportDefinition returns locations defining a package referenced by the +// import spec containing pos. +// +// If pos is not inside an import spec, it returns nil, nil. +func gopImportDefinition(ctx context.Context, s Snapshot, pkg Package, pgf *ParsedGopFile, pos token.Pos) ([]protocol.Location, error) { + var imp *ast.ImportSpec + for _, spec := range pgf.File.Imports { + // We use "<= End" to accept a query immediately after an ImportSpec. + if spec.Path.Pos() <= pos && pos <= spec.Path.End() { + imp = spec + } + } + if imp == nil { + return nil, nil + } + + importPath := GopUnquoteImportPath(imp) + impID := pkg.Metadata().DepsByImpPath[importPath] + if impID == "" { + return nil, fmt.Errorf("failed to resolve import %q", importPath) + } + impMetadata := s.Metadata(impID) + if impMetadata == nil { + return nil, fmt.Errorf("missing information for package %q", impID) + } + + var locs []protocol.Location + for _, f := range impMetadata.CompiledGoFiles { + fh, err := s.ReadFile(ctx, f) + if err != nil { + if ctx.Err() != nil { + return nil, ctx.Err() + } + continue + } + pgf, err := s.ParseGo(ctx, fh, ParseHeader) + if err != nil { + if ctx.Err() != nil { + return nil, ctx.Err() + } + continue + } + loc, err := pgf.NodeLocation(pgf.File) + if err != nil { + return nil, err + } + locs = append(locs, loc) + } + for _, f := range impMetadata.CompiledGopFiles { + fh, err := s.ReadFile(ctx, f) + if err != nil { + if ctx.Err() != nil { + return nil, ctx.Err() + } + continue + } + pgf, err := s.ParseGop(ctx, fh, parserutil.ParseHeader) + if err != nil { + if ctx.Err() != nil { + return nil, ctx.Err() + } + continue + } + loc, err := pgf.NodeLocation(pgf.File) + if err != nil { + return nil, err + } + locs = append(locs, loc) + } + + if len(locs) == 0 { + return nil, fmt.Errorf("package %q has no readable files", impID) // incl. unsafe + } + + return locs, nil +} diff --git a/gopls/internal/lsp/source/view_gox.go b/gopls/internal/lsp/source/view_gox.go index 3f17565c683..8d89bdb1d10 100644 --- a/gopls/internal/lsp/source/view_gox.go +++ b/gopls/internal/lsp/source/view_gox.go @@ -50,6 +50,11 @@ type ParsedGopFile struct { ParseErr scanner.ErrorList } +// HasPkgDecl checks if `package xxx` exists or not. +func (pgf *ParsedGopFile) HasPkgDecl() bool { + return pgf.File.Package != token.NoPos +} + // PositionPos returns the token.Pos of protocol position p within the file. func (pgf *ParsedGopFile) PositionPos(p protocol.Position) (token.Pos, error) { offset, err := pgf.Mapper.PositionOffset(p) @@ -64,6 +69,11 @@ func (pgf *ParsedGopFile) NodeRange(node ast.Node) (protocol.Range, error) { return pgf.Mapper.NodeRange(pgf.Tok, node) } +// NodeLocation returns a protocol Location for the ast.Node interval in this file. +func (pgf *ParsedGopFile) NodeLocation(node ast.Node) (protocol.Location, error) { + return pgf.Mapper.PosLocation(pgf.Tok, node.Pos(), node.End()) +} + // RangePos parses a protocol Range back into the go/token domain. func (pgf *ParsedGopFile) RangePos(r protocol.Range) (token.Pos, token.Pos, error) { start, end, err := pgf.Mapper.RangeOffsets(r) From 29dbc751ab463061d61344287604d2fe64a949a6 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Sun, 22 Oct 2023 04:49:03 +0800 Subject: [PATCH 2/2] github.com/goplus/gop@main --- gopls/go.mod | 2 +- gopls/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gopls/go.mod b/gopls/go.mod index e6e81d92ffb..debf1c35217 100644 --- a/gopls/go.mod +++ b/gopls/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/google/go-cmp v0.5.9 - github.com/goplus/gop v1.1.4-0.20231021132916-568cdda00461 + github.com/goplus/gop v1.1.4-0.20231021204738-dc26c66d30f6 github.com/goplus/mod v0.11.8-0.20231019172744-da5848421263 github.com/jba/printsrc v0.2.2 github.com/jba/templatecheck v0.6.0 diff --git a/gopls/go.sum b/gopls/go.sum index 6989ec65049..e859b2fc014 100644 --- a/gopls/go.sum +++ b/gopls/go.sum @@ -10,8 +10,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/safehtml v0.0.2/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU= github.com/google/safehtml v0.1.0 h1:EwLKo8qawTKfsi0orxcQAZzu07cICaBeFMegAU9eaT8= github.com/google/safehtml v0.1.0/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU= -github.com/goplus/gop v1.1.4-0.20231021132916-568cdda00461 h1:CvURAOe1m28sY9L/rQKexc49oAoKfqBX6aJH/h7znm4= -github.com/goplus/gop v1.1.4-0.20231021132916-568cdda00461/go.mod h1:jfqgaSg3PBpKcatrfL9eJTnj/wAqS1UadGAd6WZPhow= +github.com/goplus/gop v1.1.4-0.20231021204738-dc26c66d30f6 h1:RH70+Sr7USKyJn04f5R3PkvcatCvFXjpUcFDtVnXJlE= +github.com/goplus/gop v1.1.4-0.20231021204738-dc26c66d30f6/go.mod h1:jfqgaSg3PBpKcatrfL9eJTnj/wAqS1UadGAd6WZPhow= github.com/goplus/gox v1.12.2-0.20231020202641-5f657ff4e754 h1:uuXDqFfg4RhmHD7slw15hzwVQtJ51CsdcZn6gQtve/E= github.com/goplus/gox v1.12.2-0.20231020202641-5f657ff4e754/go.mod h1:Ek1YIy3wRaZ1i0DD2XG29i3r5AFdhcOradK0/GGs1YQ= github.com/goplus/mod v0.11.8-0.20231019172744-da5848421263 h1:PE0HveOss5mai9pa52L4/ZvVqZtltogJ9rIUIsdlG/I=