internal/lsp/source: adjust object position when formatting full AST

Thread through an adjusted object position when we format an re-parsed
AST that wasn't type-checked to produce the object. Also remove some
unnecessary handling of the object type.

Fixes golang/go#46158

Change-Id: Ic8bee2bb4cb992d577d085599213fca1deab3b4e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/384697
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Heschi Kreinick <heschi@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Robert Findley 2022-02-09 18:28:04 -05:00
parent cda4201ef0
commit fd59bdfe0d
3 changed files with 48 additions and 21 deletions

View File

@ -24,6 +24,7 @@ go 1.12
package structs
type Mixed struct {
// Exported comment
Exported int
unexported string
}
@ -40,7 +41,7 @@ go 1.12
require golang.org/x/structs v1.0.0
-- go.sum --
golang.org/x/structs v1.0.0 h1:3DlrFfd3OsEen7FnCHfqtnJvjBZ8ZFKmrD/+HjpdJj0=
golang.org/x/structs v1.0.0 h1:Ito/a7hBYZaNKShFrZKjfBA/SIPvmBrcPCBWPx5QeKk=
golang.org/x/structs v1.0.0/go.mod h1:47gkSIdo5AaQaWJS0upVORsxfEr1LL1MWv9dmYF3iq4=
-- main.go --
package main
@ -48,9 +49,11 @@ package main
import "golang.org/x/structs"
func main() {
var _ structs.Mixed
var m structs.Mixed
_ = m.Exported
}
`
// TODO: use a nested workspace folder here.
WithOptions(
ProxyFiles(proxy),
@ -61,12 +64,19 @@ func main() {
if !strings.Contains(got.Value, "unexported") {
t.Errorf("Workspace hover: missing expected field 'unexported'. Got:\n%q", got.Value)
}
cacheFile, _ := env.GoToDefinition("main.go", mixedPos)
argPos := env.RegexpSearch(cacheFile, "printMixed.*(Mixed)")
got, _ = env.Hover(cacheFile, argPos)
if !strings.Contains(got.Value, "unexported") {
t.Errorf("Non-workspace hover: missing expected field 'unexported'. Got:\n%q", got.Value)
}
exportedFieldPos := env.RegexpSearch("main.go", "Exported")
got, _ = env.Hover("main.go", exportedFieldPos)
if !strings.Contains(got.Value, "comment") {
t.Errorf("Workspace hover: missing comment for field 'Exported'. Got:\n%q", got.Value)
}
})
}

View File

@ -439,9 +439,10 @@ func objectString(obj types.Object, qf types.Qualifier, inferred *types.Signatur
return str
}
// HoverInfo returns a HoverInformation struct for an ast node and its type
// object. node should be the actual node used in type checking, while fullNode
// could be a separate node with more complete syntactic information.
// HoverInfo returns a HoverInformation struct for an ast node and its
// declaration object. node should be the actual node used in type checking,
// while fullNode could be a separate node with more complete syntactic
// information.
func HoverInfo(ctx context.Context, s Snapshot, pkg Package, obj types.Object, pkgNode ast.Node, fullDecl ast.Decl) (*HoverInformation, error) {
var info *HoverInformation
@ -497,15 +498,23 @@ func HoverInfo(ctx context.Context, s Snapshot, pkg Package, obj types.Object, p
if err != nil {
return nil, err
}
tok2 := s.FileSet().File(node.Pos())
// fullTok and fullPos are the *token.File and object position in for the
// full AST.
fullTok := s.FileSet().File(node.Pos())
fullPos, err := Pos(fullTok, offset)
if err != nil {
return nil, err
}
var spec ast.Spec
for _, s := range node.Specs {
// Avoid panics by guarding the calls to token.Offset (golang/go#48249).
start, err := Offset(tok2, s.Pos())
start, err := Offset(fullTok, s.Pos())
if err != nil {
return nil, err
}
end, err := Offset(tok2, s.End())
end, err := Offset(fullTok, s.End())
if err != nil {
return nil, err
}
@ -514,7 +523,8 @@ func HoverInfo(ctx context.Context, s Snapshot, pkg Package, obj types.Object, p
break
}
}
info, err = formatGenDecl(node, spec, obj, obj.Type())
info, err = formatGenDecl(node, spec, fullPos, obj)
if err != nil {
return nil, err
}
@ -584,21 +594,19 @@ func isFunctionParam(obj types.Object, node *ast.FuncDecl) bool {
return false
}
func formatGenDecl(node *ast.GenDecl, spec ast.Spec, obj types.Object, typ types.Type) (*HoverInformation, error) {
if _, ok := typ.(*types.Named); ok {
switch typ.Underlying().(type) {
case *types.Interface, *types.Struct:
return formatGenDecl(node, spec, obj, typ.Underlying())
}
}
// formatGenDecl returns hover information an object declared via spec inside
// of the GenDecl node. obj is the type-checked object corresponding to the
// declaration, but may have been type-checked using a different AST than the
// given nodes; fullPos is the position of obj in node's AST.
func formatGenDecl(node *ast.GenDecl, spec ast.Spec, fullPos token.Pos, obj types.Object) (*HoverInformation, error) {
if spec == nil {
return nil, errors.Errorf("no spec for node %v at position %v", node, obj.Pos())
return nil, errors.Errorf("no spec for node %v at position %v", node, fullPos)
}
// If we have a field or method.
switch obj.(type) {
case *types.Var, *types.Const, *types.Func:
return formatVar(spec, obj, node), nil
return formatVar(spec, fullPos, obj, node), nil
}
// Handle types.
switch spec := spec.(type) {
@ -628,7 +636,7 @@ func formatTypeSpec(spec *ast.TypeSpec, decl *ast.GenDecl) *HoverInformation {
}
}
func formatVar(node ast.Spec, obj types.Object, decl *ast.GenDecl) *HoverInformation {
func formatVar(node ast.Spec, fullPos token.Pos, obj types.Object, decl *ast.GenDecl) *HoverInformation {
var fieldList *ast.FieldList
switch spec := node.(type) {
case *ast.TypeSpec:
@ -664,7 +672,7 @@ func formatVar(node ast.Spec, obj types.Object, decl *ast.GenDecl) *HoverInforma
}
if fieldList != nil {
comment := findFieldComment(obj.Pos(), fieldList)
comment := findFieldComment(fullPos, fieldList)
return &HoverInformation{source: obj, comment: comment}
}
return &HoverInformation{source: obj, comment: decl.Doc}

View File

@ -540,7 +540,7 @@ func IsCommandLineArguments(s string) bool {
return strings.Contains(s, "command-line-arguments")
}
// Offset returns tok.Offset(pos), but it also checks that the pos is in range
// Offset returns tok.Offset(pos), but first checks that the pos is in range
// for the given file.
func Offset(tok *token.File, pos token.Pos) (int, error) {
if !InRange(tok, pos) {
@ -549,6 +549,15 @@ func Offset(tok *token.File, pos token.Pos) (int, error) {
return tok.Offset(pos), nil
}
// Pos returns tok.Pos(offset), but first checks that the offset is valid for
// the given file.
func Pos(tok *token.File, offset int) (token.Pos, error) {
if offset < 0 || offset > tok.Size() {
return token.NoPos, fmt.Errorf("offset %v is not in range for file of size %v", offset, tok.Size())
}
return tok.Pos(offset), nil
}
// InRange reports whether the given position is in the given token.File.
func InRange(tok *token.File, pos token.Pos) bool {
size := tok.Pos(tok.Size())