Skip to content

Commit 4d6b19c

Browse files
findleyrstamblerre
authored andcommitted
[gopls-release-branch.0.4] 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 <[email protected]> (cherry picked from commit e7a7e3a) Reviewed-on: https://go-review.googlesource.com/c/tools/+/245321 Run-TryBot: Rebecca Stambler <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 51906d2 commit 4d6b19c

File tree

2 files changed

+134
-6
lines changed

2 files changed

+134
-6
lines changed

internal/lsp/source/identifier.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ func findIdentifier(ctx context.Context, s Snapshot, pkg Package, file *ast.File
139139
qf: qf,
140140
pkg: pkg,
141141
ident: ident,
142-
enclosing: searchForEnclosing(pkg, path),
142+
enclosing: searchForEnclosing(pkg.GetTypesInfo(), path),
143143
}
144144

145145
var wasEmbeddedField bool
@@ -234,15 +234,15 @@ func findIdentifier(ctx context.Context, s Snapshot, pkg Package, file *ast.File
234234
return result, nil
235235
}
236236

237-
func searchForEnclosing(pkg Package, path []ast.Node) types.Type {
237+
func searchForEnclosing(info *types.Info, path []ast.Node) types.Type {
238238
for _, n := range path {
239239
switch n := n.(type) {
240240
case *ast.SelectorExpr:
241-
if sel, ok := pkg.GetTypesInfo().Selections[n]; ok {
241+
if sel, ok := info.Selections[n]; ok {
242242
recv := deref(sel.Recv())
243243

244244
// Keep track of the last exported type seen.
245-
var exported *types.Named
245+
var exported types.Type
246246
if named, ok := recv.(*types.Named); ok && named.Obj().Exported() {
247247
exported = named
248248
}
@@ -259,12 +259,12 @@ func searchForEnclosing(pkg Package, path []ast.Node) types.Type {
259259
return exported
260260
}
261261
case *ast.CompositeLit:
262-
if t, ok := pkg.GetTypesInfo().Types[n]; ok {
262+
if t, ok := info.Types[n]; ok {
263263
return t.Type
264264
}
265265
case *ast.TypeSpec:
266266
if _, ok := n.Type.(*ast.StructType); ok {
267-
if t, ok := pkg.GetTypesInfo().Defs[n.Name]; ok {
267+
if t, ok := info.Defs[n.Name]; ok {
268268
return t.Type()
269269
}
270270
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package source
6+
7+
import (
8+
"bytes"
9+
"go/ast"
10+
"go/parser"
11+
"go/token"
12+
"go/types"
13+
"testing"
14+
)
15+
16+
func TestSearchForEnclosing(t *testing.T) {
17+
tests := []struct {
18+
desc string
19+
// For convenience, consider the first occurence of the identifier "X" in
20+
// src.
21+
src string
22+
// By convention, "" means no type found.
23+
wantTypeName string
24+
}{
25+
{
26+
desc: "self enclosing",
27+
src: `package a; type X struct {}`,
28+
wantTypeName: "X",
29+
},
30+
{
31+
// TODO(rFindley): is this correct, or do we want to resolve I2 here?
32+
desc: "embedded interface in interface",
33+
src: `package a; var y = i1.X; type i1 interface {I2}; type I2 interface{X()}`,
34+
wantTypeName: "",
35+
},
36+
{
37+
desc: "embedded interface in struct",
38+
src: `package a; var y = t.X; type t struct {I}; type I interface{X()}`,
39+
wantTypeName: "I",
40+
},
41+
{
42+
desc: "double embedding",
43+
src: `package a; var y = t1.X; type t1 struct {t2}; type t2 struct {I}; type I interface{X()}`,
44+
wantTypeName: "I",
45+
},
46+
{
47+
desc: "struct field",
48+
src: `package a; type T struct { X int }`,
49+
wantTypeName: "T",
50+
},
51+
{
52+
desc: "nested struct field",
53+
src: `package a; type T struct { E struct { X int } }`,
54+
wantTypeName: "T",
55+
},
56+
{
57+
desc: "slice entry",
58+
src: `package a; type T []int; var S = T{X}; var X int = 2`,
59+
wantTypeName: "T",
60+
},
61+
{
62+
desc: "struct pointer literal",
63+
src: `package a; type T struct {i int}; var L = &T{X}; const X = 2`,
64+
wantTypeName: "T",
65+
},
66+
}
67+
68+
for _, test := range tests {
69+
test := test
70+
t.Run(test.desc, func(t *testing.T) {
71+
fset := token.NewFileSet()
72+
file, err := parser.ParseFile(fset, "a.go", test.src, parser.AllErrors)
73+
if err != nil {
74+
t.Fatal(err)
75+
}
76+
column := 1 + bytes.IndexRune([]byte(test.src), 'X')
77+
pos := posAt(1, column, fset, "a.go")
78+
path := pathEnclosingObjNode(file, pos)
79+
if path == nil {
80+
t.Fatalf("no ident found at (1, %d)", column)
81+
}
82+
info := newInfo()
83+
if _, err = (*types.Config)(nil).Check("p", fset, []*ast.File{file}, info); err != nil {
84+
t.Fatal(err)
85+
}
86+
typ := searchForEnclosing(info, path)
87+
if typ == nil {
88+
if test.wantTypeName != "" {
89+
t.Errorf("searchForEnclosing(...) = <nil>, want %q", test.wantTypeName)
90+
}
91+
return
92+
}
93+
if got := typ.(*types.Named).Obj().Name(); got != test.wantTypeName {
94+
t.Errorf("searchForEnclosing(...) = %q, want %q", got, test.wantTypeName)
95+
}
96+
})
97+
}
98+
}
99+
100+
// posAt returns the token.Pos corresponding to the 1-based (line, column)
101+
// coordinates in the file fname of fset.
102+
func posAt(line, column int, fset *token.FileSet, fname string) token.Pos {
103+
var tok *token.File
104+
fset.Iterate(func(f *token.File) bool {
105+
if f.Name() == fname {
106+
tok = f
107+
return false
108+
}
109+
return true
110+
})
111+
if tok == nil {
112+
return token.NoPos
113+
}
114+
start := tok.LineStart(line)
115+
return start + token.Pos(column-1)
116+
}
117+
118+
// newInfo returns a types.Info with all maps populated.
119+
func newInfo() *types.Info {
120+
return &types.Info{
121+
Types: make(map[ast.Expr]types.TypeAndValue),
122+
Defs: make(map[*ast.Ident]types.Object),
123+
Uses: make(map[*ast.Ident]types.Object),
124+
Implicits: make(map[ast.Node]types.Object),
125+
Selections: make(map[*ast.SelectorExpr]*types.Selection),
126+
Scopes: make(map[ast.Node]*types.Scope),
127+
}
128+
}

0 commit comments

Comments
 (0)