mirror of
https://github.com/golang/go
synced 2024-11-18 11:14:39 -07:00
internal/lsp/source: add a unit test for searchForEnclosing
The logic to resolve the enclosing type for an identifier is somewhat tricky. Add a unit test to exercise a few edge cases. This would probably be easier to read and write using a hybrid approach that extracts markers from the source. This test uncovered a bug, that on the SelectorExpr branch we were accidentally returning a nil *Named types.Type, rather than a nil types.Type. Change-Id: I43e096f51999b2a6e109c09d3805ad70a4780398 Reviewed-on: https://go-review.googlesource.com/c/tools/+/244841 Reviewed-by: Heschi Kreinick <heschi@google.com>
This commit is contained in:
parent
b6476686b7
commit
e7a7e3a8a0
@ -135,7 +135,7 @@ func findIdentifier(ctx context.Context, s Snapshot, pkg Package, file *ast.File
|
||||
qf: qf,
|
||||
pkg: pkg,
|
||||
ident: ident,
|
||||
enclosing: searchForEnclosing(pkg, path),
|
||||
enclosing: searchForEnclosing(pkg.GetTypesInfo(), path),
|
||||
}
|
||||
|
||||
var wasEmbeddedField bool
|
||||
@ -226,15 +226,15 @@ func findIdentifier(ctx context.Context, s Snapshot, pkg Package, file *ast.File
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func searchForEnclosing(pkg Package, path []ast.Node) types.Type {
|
||||
func searchForEnclosing(info *types.Info, path []ast.Node) types.Type {
|
||||
for _, n := range path {
|
||||
switch n := n.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
if sel, ok := pkg.GetTypesInfo().Selections[n]; ok {
|
||||
if sel, ok := info.Selections[n]; ok {
|
||||
recv := deref(sel.Recv())
|
||||
|
||||
// Keep track of the last exported type seen.
|
||||
var exported *types.Named
|
||||
var exported types.Type
|
||||
if named, ok := recv.(*types.Named); ok && named.Obj().Exported() {
|
||||
exported = named
|
||||
}
|
||||
@ -251,12 +251,12 @@ func searchForEnclosing(pkg Package, path []ast.Node) types.Type {
|
||||
return exported
|
||||
}
|
||||
case *ast.CompositeLit:
|
||||
if t, ok := pkg.GetTypesInfo().Types[n]; ok {
|
||||
if t, ok := info.Types[n]; ok {
|
||||
return t.Type
|
||||
}
|
||||
case *ast.TypeSpec:
|
||||
if _, ok := n.Type.(*ast.StructType); ok {
|
||||
if t, ok := pkg.GetTypesInfo().Defs[n.Name]; ok {
|
||||
if t, ok := info.Defs[n.Name]; ok {
|
||||
return t.Type()
|
||||
}
|
||||
}
|
||||
|
128
internal/lsp/source/identifier_test.go
Normal file
128
internal/lsp/source/identifier_test.go
Normal file
@ -0,0 +1,128 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package source
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSearchForEnclosing(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
// For convenience, consider the first occurence of the identifier "X" in
|
||||
// src.
|
||||
src string
|
||||
// By convention, "" means no type found.
|
||||
wantTypeName string
|
||||
}{
|
||||
{
|
||||
desc: "self enclosing",
|
||||
src: `package a; type X struct {}`,
|
||||
wantTypeName: "X",
|
||||
},
|
||||
{
|
||||
// TODO(rFindley): is this correct, or do we want to resolve I2 here?
|
||||
desc: "embedded interface in interface",
|
||||
src: `package a; var y = i1.X; type i1 interface {I2}; type I2 interface{X()}`,
|
||||
wantTypeName: "",
|
||||
},
|
||||
{
|
||||
desc: "embedded interface in struct",
|
||||
src: `package a; var y = t.X; type t struct {I}; type I interface{X()}`,
|
||||
wantTypeName: "I",
|
||||
},
|
||||
{
|
||||
desc: "double embedding",
|
||||
src: `package a; var y = t1.X; type t1 struct {t2}; type t2 struct {I}; type I interface{X()}`,
|
||||
wantTypeName: "I",
|
||||
},
|
||||
{
|
||||
desc: "struct field",
|
||||
src: `package a; type T struct { X int }`,
|
||||
wantTypeName: "T",
|
||||
},
|
||||
{
|
||||
desc: "nested struct field",
|
||||
src: `package a; type T struct { E struct { X int } }`,
|
||||
wantTypeName: "T",
|
||||
},
|
||||
{
|
||||
desc: "slice entry",
|
||||
src: `package a; type T []int; var S = T{X}; var X int = 2`,
|
||||
wantTypeName: "T",
|
||||
},
|
||||
{
|
||||
desc: "struct pointer literal",
|
||||
src: `package a; type T struct {i int}; var L = &T{X}; const X = 2`,
|
||||
wantTypeName: "T",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
fset := token.NewFileSet()
|
||||
file, err := parser.ParseFile(fset, "a.go", test.src, parser.AllErrors)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
column := 1 + bytes.IndexRune([]byte(test.src), 'X')
|
||||
pos := posAt(1, column, fset, "a.go")
|
||||
path := pathEnclosingObjNode(file, pos)
|
||||
if path == nil {
|
||||
t.Fatalf("no ident found at (1, %d)", column)
|
||||
}
|
||||
info := newInfo()
|
||||
if _, err = (*types.Config)(nil).Check("p", fset, []*ast.File{file}, info); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
typ := searchForEnclosing(info, path)
|
||||
if typ == nil {
|
||||
if test.wantTypeName != "" {
|
||||
t.Errorf("searchForEnclosing(...) = <nil>, want %q", test.wantTypeName)
|
||||
}
|
||||
return
|
||||
}
|
||||
if got := typ.(*types.Named).Obj().Name(); got != test.wantTypeName {
|
||||
t.Errorf("searchForEnclosing(...) = %q, want %q", got, test.wantTypeName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// posAt returns the token.Pos corresponding to the 1-based (line, column)
|
||||
// coordinates in the file fname of fset.
|
||||
func posAt(line, column int, fset *token.FileSet, fname string) token.Pos {
|
||||
var tok *token.File
|
||||
fset.Iterate(func(f *token.File) bool {
|
||||
if f.Name() == fname {
|
||||
tok = f
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if tok == nil {
|
||||
return token.NoPos
|
||||
}
|
||||
start := tok.LineStart(line)
|
||||
return start + token.Pos(column-1)
|
||||
}
|
||||
|
||||
// newInfo returns a types.Info with all maps populated.
|
||||
func newInfo() *types.Info {
|
||||
return &types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
Uses: make(map[*ast.Ident]types.Object),
|
||||
Implicits: make(map[ast.Node]types.Object),
|
||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||
Scopes: make(map[ast.Node]*types.Scope),
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user