Skip to content

Commit ae56d93

Browse files
committed
internal/modindex: implement Lookups in the index
(*Index).Lookup returns matching symbols from the index. Change-Id: I7b805be9a08116e111bb8d6453ed67f8cc92dd4d Reviewed-on: https://go-review.googlesource.com/c/tools/+/621035 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 044b16f commit ae56d93

File tree

3 files changed

+276
-4
lines changed

3 files changed

+276
-4
lines changed

internal/modindex/directories.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,7 @@ func (r *region) addDir(rt gopathwalk.Root, dir string) {
110110
}
111111

112112
func (r *region) skipDir(_ gopathwalk.Root, dir string) bool {
113-
// The cache directory is alreday ignored in gopathwalk
114-
if filepath.Base(dir) == "vendor" {
115-
return true
116-
}
113+
// The cache directory is already ignored in gopathwalk\
117114
if filepath.Base(dir) == "internal" {
118115
return true
119116
}

internal/modindex/lookup.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright 2024 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 modindex
6+
7+
import (
8+
"slices"
9+
"strconv"
10+
"strings"
11+
)
12+
13+
type Candidate struct {
14+
PkgName string
15+
Name string
16+
Dir string
17+
ImportPath string
18+
Type LexType
19+
// information for Funcs
20+
Results int16 // how many results
21+
Sig []Field // arg names and types
22+
}
23+
24+
type Field struct {
25+
Arg, Type string
26+
}
27+
28+
type LexType int8
29+
30+
const (
31+
Const LexType = iota
32+
Var
33+
Type
34+
Func
35+
)
36+
37+
// Lookup finds all the symbols in the index with the given PkgName and name.
38+
// If prefix is true, it finds all of these with name as a prefix.
39+
func (ix *Index) Lookup(pkg, name string, prefix bool) []Candidate {
40+
loc, ok := slices.BinarySearchFunc(ix.Entries, pkg, func(e Entry, pkg string) int {
41+
return strings.Compare(e.PkgName, pkg)
42+
})
43+
if !ok {
44+
return nil // didn't find the package
45+
}
46+
var ans []Candidate
47+
// loc is the first entry for this package name, but there may be severeal
48+
for i := loc; i < len(ix.Entries); i++ {
49+
e := ix.Entries[i]
50+
if e.PkgName != pkg {
51+
break // end of sorted package names
52+
}
53+
nloc, ok := slices.BinarySearchFunc(e.Names, name, func(s string, name string) int {
54+
if strings.HasPrefix(s, name) {
55+
return 0
56+
}
57+
if s < name {
58+
return -1
59+
}
60+
return 1
61+
})
62+
if !ok {
63+
continue // didn't find the name, nor any symbols with name as a prefix
64+
}
65+
for j := nloc; j < len(e.Names); j++ {
66+
nstr := e.Names[j]
67+
// benchmarks show this makes a difference when there are a lot of Possibilities
68+
flds := fastSplit(nstr)
69+
if !(flds[0] == name || prefix && strings.HasPrefix(flds[0], name)) {
70+
// past range of matching Names
71+
break
72+
}
73+
if len(flds) < 2 {
74+
continue // should never happen
75+
}
76+
px := Candidate{
77+
PkgName: pkg,
78+
Name: flds[0],
79+
Dir: string(e.Dir),
80+
ImportPath: e.ImportPath,
81+
Type: asLexType(flds[1][0]),
82+
}
83+
if flds[1] == "F" {
84+
n, err := strconv.Atoi(flds[2])
85+
if err != nil {
86+
continue // should never happen
87+
}
88+
px.Results = int16(n)
89+
if len(flds) >= 4 {
90+
sig := strings.Split(flds[3], " ")
91+
for i := 0; i < len(sig); i++ {
92+
// $ cannot otherwise occur. removing the spaces
93+
// almost works, but for chan struct{}, e.g.
94+
sig[i] = strings.Replace(sig[i], "$", " ", -1)
95+
}
96+
px.Sig = toFields(sig)
97+
}
98+
}
99+
ans = append(ans, px)
100+
}
101+
}
102+
return ans
103+
}
104+
105+
func toFields(sig []string) []Field {
106+
ans := make([]Field, len(sig)/2)
107+
for i := 0; i < len(ans); i++ {
108+
ans[i] = Field{Arg: sig[2*i], Type: sig[2*i+1]}
109+
}
110+
return ans
111+
}
112+
113+
// benchmarks show this is measurably better than strings.Split
114+
func fastSplit(x string) []string {
115+
ans := make([]string, 0, 4)
116+
nxt := 0
117+
start := 0
118+
for i := 0; i < len(x); i++ {
119+
if x[i] != ' ' {
120+
continue
121+
}
122+
ans = append(ans, x[start:i])
123+
nxt++
124+
start = i + 1
125+
if nxt >= 3 {
126+
break
127+
}
128+
}
129+
ans = append(ans, x[start:])
130+
return ans
131+
}
132+
133+
func asLexType(c byte) LexType {
134+
switch c {
135+
case 'C':
136+
return Const
137+
case 'V':
138+
return Var
139+
case 'T':
140+
return Type
141+
case 'F':
142+
return Func
143+
}
144+
return -1
145+
}

internal/modindex/lookup_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright 2024 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 modindex
6+
7+
import (
8+
"fmt"
9+
"log"
10+
"os"
11+
"path/filepath"
12+
"strings"
13+
"testing"
14+
)
15+
16+
type tdata struct {
17+
fname string
18+
pkg string
19+
items []titem
20+
}
21+
22+
type titem struct {
23+
code string
24+
result result
25+
}
26+
27+
var thedata = tdata{
28+
fname: "cloud.google.com/go/[email protected]/foo.go",
29+
pkg: "foo",
30+
items: []titem{
31+
// these need to be in alphabetical order by symbol
32+
{"func Foo() {}", result{"Foo", Func, 0, nil}},
33+
{"const FooC = 23", result{"FooC", Const, 0, nil}},
34+
{"func FooF(int, float) error {return nil}", result{"FooF", Func, 1,
35+
[]Field{{"_", "int"}, {"_", "float"}}}},
36+
{"type FooT struct{}", result{"FooT", Type, 0, nil}},
37+
{"var FooV int", result{"FooV", Var, 0, nil}},
38+
{"func Ⱋoox(x int) {}", result{"Ⱋoox", Func, 0, []Field{{"x", "int"}}}},
39+
},
40+
}
41+
42+
type result struct {
43+
name string
44+
typ LexType
45+
result int
46+
sig []Field
47+
}
48+
49+
func okresult(r result, p Candidate) bool {
50+
if r.name != p.Name || r.typ != p.Type || r.result != int(p.Results) {
51+
return false
52+
}
53+
if len(r.sig) != len(p.Sig) {
54+
return false
55+
}
56+
for i := 0; i < len(r.sig); i++ {
57+
if r.sig[i] != p.Sig[i] {
58+
return false
59+
}
60+
}
61+
return true
62+
}
63+
64+
func TestLookup(t *testing.T) {
65+
log.SetFlags(log.Lshortfile)
66+
dir := testModCache(t)
67+
wrtData(t, dir, thedata)
68+
if _, err := indexModCache(dir, true); err != nil {
69+
t.Fatal(err)
70+
}
71+
ix, err := ReadIndex(dir)
72+
if err != nil {
73+
t.Fatal(err)
74+
}
75+
if len(ix.Entries) != 1 {
76+
t.Fatalf("got %d Entries, expected 1", len(ix.Entries))
77+
}
78+
// get all the symbols
79+
p := ix.Lookup("foo", "", true)
80+
if len(p) != len(thedata.items) {
81+
// we should have gotten them all
82+
t.Errorf("got %d possibilities for pkg foo, expected %d", len(p), len(thedata.items))
83+
}
84+
for i, r := range thedata.items {
85+
if !okresult(r.result, p[i]) {
86+
t.Errorf("got %#v, expected %#v", p[i], r.result)
87+
}
88+
}
89+
// look for the Foo... and check that each is a Foo...
90+
p = ix.Lookup("foo", "Foo", true)
91+
if len(p) != 5 {
92+
t.Errorf("got %d possibilities for foo.Foo*, expected 5", len(p))
93+
}
94+
for _, r := range p {
95+
if !strings.HasPrefix(r.Name, "Foo") {
96+
t.Errorf("got %s, expected Foo...", r.Name)
97+
}
98+
}
99+
// fail to find something
100+
p = ix.Lookup("foo", "FooVal", false)
101+
if len(p) != 0 {
102+
t.Errorf("got %d possibilities for foo.FooVal, expected 0", len(p))
103+
}
104+
// find an exact match
105+
p = ix.Lookup("foo", "Foo", false)
106+
if len(p) != 1 {
107+
t.Errorf("got %d possibilities for foo.Foo, expected 1", len(p))
108+
}
109+
// "Foo" is the first test datum
110+
if !okresult(thedata.items[0].result, p[0]) {
111+
t.Errorf("got %#v, expected %#v", p[0], thedata.items[0].result)
112+
}
113+
}
114+
115+
func wrtData(t *testing.T, dir string, data tdata) {
116+
t.Helper()
117+
locname := filepath.FromSlash(data.fname)
118+
if err := os.MkdirAll(filepath.Join(dir, filepath.Dir(locname)), 0755); err != nil {
119+
t.Fatal(err)
120+
}
121+
fd, err := os.Create(filepath.Join(dir, locname))
122+
if err != nil {
123+
t.Fatal(err)
124+
}
125+
defer fd.Close()
126+
fd.WriteString(fmt.Sprintf("package %s\n", data.pkg))
127+
for _, item := range data.items {
128+
fd.WriteString(item.code + "\n")
129+
}
130+
}

0 commit comments

Comments
 (0)