lookup.go (4170B)
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 "golang.org/x/mod/module" 13 ) 14 15 type Candidate struct { 16 PkgName string 17 Name string 18 Dir string 19 ImportPath string 20 Type LexType 21 Deprecated bool 22 // information for Funcs 23 Results int16 // how many results 24 Sig []Field // arg names and types 25 } 26 27 type Field struct { 28 Arg, Type string 29 } 30 31 type LexType int8 32 33 const ( 34 Const LexType = iota 35 Var 36 Type 37 Func 38 ) 39 40 // LookupAll only returns those Candidates whose import path 41 // finds all the names. 42 func (ix *Index) LookupAll(pkgName string, names ...string) map[string][]Candidate { 43 // this can be made faster when benchmarks show that it needs to be 44 names = uniquify(names) 45 byImpPath := make(map[string][]Candidate) 46 for _, nm := range names { 47 cands := ix.Lookup(pkgName, nm, false) 48 for _, c := range cands { 49 byImpPath[c.ImportPath] = append(byImpPath[c.ImportPath], c) 50 } 51 } 52 for k, v := range byImpPath { 53 if len(v) != len(names) { 54 delete(byImpPath, k) 55 } 56 } 57 return byImpPath 58 } 59 60 // remove duplicates 61 func uniquify(in []string) []string { 62 if len(in) == 0 { 63 return in 64 } 65 in = slices.Clone(in) 66 slices.Sort(in) 67 return slices.Compact(in) 68 } 69 70 // Lookup finds all the symbols in the index with the given PkgName and name. 71 // If prefix is true, it finds all of these with name as a prefix. 72 func (ix *Index) Lookup(pkgName, name string, prefix bool) []Candidate { 73 loc, ok := slices.BinarySearchFunc(ix.Entries, pkgName, func(e Entry, pkg string) int { 74 return strings.Compare(e.PkgName, pkgName) 75 }) 76 if !ok { 77 return nil // didn't find the package 78 } 79 var ans []Candidate 80 // loc is the first entry for this package name, but there may be several 81 for i := loc; i < len(ix.Entries); i++ { 82 e := ix.Entries[i] 83 if e.PkgName != pkgName { 84 break // end of sorted package names 85 } 86 nloc, ok := slices.BinarySearchFunc(e.Names, name, func(s string, name string) int { 87 if strings.HasPrefix(s, name) { 88 return 0 89 } 90 if s < name { 91 return -1 92 } 93 return 1 94 }) 95 if !ok { 96 continue // didn't find the name, nor any symbols with name as a prefix 97 } 98 for j := nloc; j < len(e.Names); j++ { 99 nstr := e.Names[j] 100 // benchmarks show this makes a difference when there are a lot of Possibilities 101 flds := fastSplit(nstr) 102 if !(flds[0] == name || prefix && strings.HasPrefix(flds[0], name)) { 103 // past range of matching Names 104 break 105 } 106 if len(flds) < 2 { 107 continue // should never happen 108 } 109 impPath, err := module.UnescapePath(e.ImportPath) 110 if err != nil { 111 continue 112 } 113 px := Candidate{ 114 PkgName: pkgName, 115 Name: flds[0], 116 Dir: string(e.Dir), 117 ImportPath: impPath, 118 Type: asLexType(flds[1][0]), 119 Deprecated: len(flds[1]) > 1 && flds[1][1] == 'D', 120 } 121 if px.Type == Func { 122 n, err := strconv.Atoi(flds[2]) 123 if err != nil { 124 continue // should never happen 125 } 126 px.Results = int16(n) 127 if len(flds) >= 4 { 128 sig := strings.Split(flds[3], " ") 129 for i := range sig { 130 // $ cannot otherwise occur. removing the spaces 131 // almost works, but for chan struct{}, e.g. 132 sig[i] = strings.Replace(sig[i], "$", " ", -1) 133 } 134 px.Sig = toFields(sig) 135 } 136 } 137 ans = append(ans, px) 138 } 139 } 140 return ans 141 } 142 143 func toFields(sig []string) []Field { 144 ans := make([]Field, len(sig)/2) 145 for i := range ans { 146 ans[i] = Field{Arg: sig[2*i], Type: sig[2*i+1]} 147 } 148 return ans 149 } 150 151 // benchmarks show this is measurably better than strings.Split 152 // split into first 4 fields separated by single space 153 func fastSplit(x string) []string { 154 ans := make([]string, 0, 4) 155 nxt := 0 156 start := 0 157 for i := 0; i < len(x); i++ { 158 if x[i] != ' ' { 159 continue 160 } 161 ans = append(ans, x[start:i]) 162 nxt++ 163 start = i + 1 164 if nxt >= 3 { 165 break 166 } 167 } 168 ans = append(ans, x[start:]) 169 return ans 170 } 171 172 func asLexType(c byte) LexType { 173 switch c { 174 case 'C': 175 return Const 176 case 'V': 177 return Var 178 case 'T': 179 return Type 180 case 'F': 181 return Func 182 } 183 return -1 184 }