github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/source/completion/deep_completion.go (about) 1 // Copyright 2019 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 completion 6 7 import ( 8 "context" 9 "go/types" 10 "strings" 11 "time" 12 ) 13 14 // MaxDeepCompletions limits deep completion results because in most cases 15 // there are too many to be useful. 16 const MaxDeepCompletions = 3 17 18 // deepCompletionState stores our state as we search for deep completions. 19 // "deep completion" refers to searching into objects' fields and methods to 20 // find more completion candidates. 21 type deepCompletionState struct { 22 // enabled indicates wether deep completion is permitted. 23 enabled bool 24 25 // queueClosed is used to disable adding new sub-fields to search queue 26 // once we're running out of our time budget. 27 queueClosed bool 28 29 // thisQueue holds the current breadth first search queue. 30 thisQueue []candidate 31 32 // nextQueue holds the next breadth first search iteration's queue. 33 nextQueue []candidate 34 35 // highScores tracks the highest deep candidate scores we have found 36 // so far. This is used to avoid work for low scoring deep candidates. 37 highScores [MaxDeepCompletions]float64 38 39 // candidateCount is the count of unique deep candidates encountered 40 // so far. 41 candidateCount int 42 } 43 44 // enqueue adds a candidate to the search queue. 45 func (s *deepCompletionState) enqueue(cand candidate) { 46 s.nextQueue = append(s.nextQueue, cand) 47 } 48 49 // dequeue removes and returns the leftmost element from the search queue. 50 func (s *deepCompletionState) dequeue() *candidate { 51 var cand *candidate 52 cand, s.thisQueue = &s.thisQueue[len(s.thisQueue)-1], s.thisQueue[:len(s.thisQueue)-1] 53 return cand 54 } 55 56 // scorePenalty computes a deep candidate score penalty. A candidate is 57 // penalized based on depth to favor shallower candidates. We also give a 58 // slight bonus to unexported objects and a slight additional penalty to 59 // function objects. 60 func (s *deepCompletionState) scorePenalty(cand *candidate) float64 { 61 var deepPenalty float64 62 for _, dc := range cand.path { 63 deepPenalty++ 64 65 if !dc.Exported() { 66 deepPenalty -= 0.1 67 } 68 69 if _, isSig := dc.Type().Underlying().(*types.Signature); isSig { 70 deepPenalty += 0.1 71 } 72 } 73 74 // Normalize penalty to a max depth of 10. 75 return deepPenalty / 10 76 } 77 78 // isHighScore returns whether score is among the top MaxDeepCompletions deep 79 // candidate scores encountered so far. If so, it adds score to highScores, 80 // possibly displacing an existing high score. 81 func (s *deepCompletionState) isHighScore(score float64) bool { 82 // Invariant: s.highScores is sorted with highest score first. Unclaimed 83 // positions are trailing zeros. 84 85 // If we beat an existing score then take its spot. 86 for i, deepScore := range s.highScores { 87 if score <= deepScore { 88 continue 89 } 90 91 if deepScore != 0 && i != len(s.highScores)-1 { 92 // If this wasn't an empty slot then we need to scooch everyone 93 // down one spot. 94 copy(s.highScores[i+1:], s.highScores[i:]) 95 } 96 s.highScores[i] = score 97 return true 98 } 99 100 return false 101 } 102 103 // newPath returns path from search root for an object following a given 104 // candidate. 105 func (s *deepCompletionState) newPath(cand candidate, obj types.Object) []types.Object { 106 path := make([]types.Object, len(cand.path)+1) 107 copy(path, cand.path) 108 path[len(path)-1] = obj 109 110 return path 111 } 112 113 // deepSearch searches a candidate and its subordinate objects for completion 114 // items if deep completion is enabled and adds the valid candidates to 115 // completion items. 116 func (c *completer) deepSearch(ctx context.Context) { 117 defer func() { 118 // We can return early before completing the search, so be sure to 119 // clear out our queues to not impact any further invocations. 120 c.deepState.thisQueue = c.deepState.thisQueue[:0] 121 c.deepState.nextQueue = c.deepState.nextQueue[:0] 122 }() 123 124 for len(c.deepState.nextQueue) > 0 { 125 c.deepState.thisQueue, c.deepState.nextQueue = c.deepState.nextQueue, c.deepState.thisQueue[:0] 126 127 outer: 128 for _, cand := range c.deepState.thisQueue { 129 obj := cand.obj 130 131 if obj == nil { 132 continue 133 } 134 135 // At the top level, dedupe by object. 136 if len(cand.path) == 0 { 137 if c.seen[obj] { 138 continue 139 } 140 c.seen[obj] = true 141 } 142 143 // If obj is not accessible because it lives in another package and is 144 // not exported, don't treat it as a completion candidate unless it's 145 // a package completion candidate. 146 if !c.completionContext.packageCompletion && 147 obj.Pkg() != nil && obj.Pkg() != c.pkg.GetTypes() && !obj.Exported() { 148 continue 149 } 150 151 // If we want a type name, don't offer non-type name candidates. 152 // However, do offer package names since they can contain type names, 153 // and do offer any candidate without a type since we aren't sure if it 154 // is a type name or not (i.e. unimported candidate). 155 if c.wantTypeName() && obj.Type() != nil && !isTypeName(obj) && !isPkgName(obj) { 156 continue 157 } 158 159 // When searching deep, make sure we don't have a cycle in our chain. 160 // We don't dedupe by object because we want to allow both "foo.Baz" 161 // and "bar.Baz" even though "Baz" is represented the same types.Object 162 // in both. 163 for _, seenObj := range cand.path { 164 if seenObj == obj { 165 continue outer 166 } 167 } 168 169 c.addCandidate(ctx, &cand) 170 171 c.deepState.candidateCount++ 172 if c.opts.budget > 0 && c.deepState.candidateCount%100 == 0 { 173 spent := float64(time.Since(c.startTime)) / float64(c.opts.budget) 174 select { 175 case <-ctx.Done(): 176 return 177 default: 178 // If we are almost out of budgeted time, no further elements 179 // should be added to the queue. This ensures remaining time is 180 // used for processing current queue. 181 if !c.deepState.queueClosed && spent >= 0.85 { 182 c.deepState.queueClosed = true 183 } 184 } 185 } 186 187 // if deep search is disabled, don't add any more candidates. 188 if !c.deepState.enabled || c.deepState.queueClosed { 189 continue 190 } 191 192 // Searching members for a type name doesn't make sense. 193 if isTypeName(obj) { 194 continue 195 } 196 if obj.Type() == nil { 197 continue 198 } 199 200 // Don't search embedded fields because they were already included in their 201 // parent's fields. 202 if v, ok := obj.(*types.Var); ok && v.Embedded() { 203 continue 204 } 205 206 if sig, ok := obj.Type().Underlying().(*types.Signature); ok { 207 // If obj is a function that takes no arguments and returns one 208 // value, keep searching across the function call. 209 if sig.Params().Len() == 0 && sig.Results().Len() == 1 { 210 path := c.deepState.newPath(cand, obj) 211 // The result of a function call is not addressable. 212 c.methodsAndFields(sig.Results().At(0).Type(), false, cand.imp, func(newCand candidate) { 213 newCand.pathInvokeMask = cand.pathInvokeMask | (1 << uint64(len(cand.path))) 214 newCand.path = path 215 c.deepState.enqueue(newCand) 216 }) 217 } 218 } 219 220 path := c.deepState.newPath(cand, obj) 221 switch obj := obj.(type) { 222 case *types.PkgName: 223 c.packageMembers(obj.Imported(), stdScore, cand.imp, func(newCand candidate) { 224 newCand.pathInvokeMask = cand.pathInvokeMask 225 newCand.path = path 226 c.deepState.enqueue(newCand) 227 }) 228 default: 229 c.methodsAndFields(obj.Type(), cand.addressable, cand.imp, func(newCand candidate) { 230 newCand.pathInvokeMask = cand.pathInvokeMask 231 newCand.path = path 232 c.deepState.enqueue(newCand) 233 }) 234 } 235 } 236 } 237 } 238 239 // addCandidate adds a completion candidate to suggestions, without searching 240 // its members for more candidates. 241 func (c *completer) addCandidate(ctx context.Context, cand *candidate) { 242 obj := cand.obj 243 if c.matchingCandidate(cand) { 244 cand.score *= highScore 245 246 if p := c.penalty(cand); p > 0 { 247 cand.score *= (1 - p) 248 } 249 } else if isTypeName(obj) { 250 // If obj is a *types.TypeName that didn't otherwise match, check 251 // if a literal object of this type makes a good candidate. 252 253 // We only care about named types (i.e. don't want builtin types). 254 if _, isNamed := obj.Type().(*types.Named); isNamed { 255 c.literal(ctx, obj.Type(), cand.imp) 256 } 257 } 258 259 // Lower score of method calls so we prefer fields and vars over calls. 260 if cand.hasMod(invoke) { 261 if sig, ok := obj.Type().Underlying().(*types.Signature); ok && sig.Recv() != nil { 262 cand.score *= 0.9 263 } 264 } 265 266 // Prefer private objects over public ones. 267 if !obj.Exported() && obj.Parent() != types.Universe { 268 cand.score *= 1.1 269 } 270 271 // Slight penalty for index modifier (e.g. changing "foo" to 272 // "foo[]") to curb false positives. 273 if cand.hasMod(index) { 274 cand.score *= 0.9 275 } 276 277 // Favor shallow matches by lowering score according to depth. 278 cand.score -= cand.score * c.deepState.scorePenalty(cand) 279 280 if cand.score < 0 { 281 cand.score = 0 282 } 283 284 cand.name = deepCandName(cand) 285 if item, err := c.item(ctx, *cand); err == nil { 286 c.items = append(c.items, item) 287 } 288 } 289 290 // deepCandName produces the full candidate name including any 291 // ancestor objects. For example, "foo.bar().baz" for candidate "baz". 292 func deepCandName(cand *candidate) string { 293 totalLen := len(cand.obj.Name()) 294 for i, obj := range cand.path { 295 totalLen += len(obj.Name()) + 1 296 if cand.pathInvokeMask&(1<<uint16(i)) > 0 { 297 totalLen += 2 298 } 299 } 300 301 var buf strings.Builder 302 buf.Grow(totalLen) 303 304 for i, obj := range cand.path { 305 buf.WriteString(obj.Name()) 306 if cand.pathInvokeMask&(1<<uint16(i)) > 0 { 307 buf.WriteByte('(') 308 buf.WriteByte(')') 309 } 310 buf.WriteByte('.') 311 } 312 313 buf.WriteString(cand.obj.Name()) 314 315 return buf.String() 316 } 317 318 // penalty reports a score penalty for cand in the range (0, 1). 319 // For example, a candidate is penalized if it has already been used 320 // in another switch case statement. 321 func (c *completer) penalty(cand *candidate) float64 { 322 for _, p := range c.inference.penalized { 323 if c.objChainMatches(cand, p.objChain) { 324 return p.penalty 325 } 326 } 327 328 return 0 329 } 330 331 // objChainMatches reports whether cand combined with the surrounding 332 // object prefix matches chain. 333 func (c *completer) objChainMatches(cand *candidate, chain []types.Object) bool { 334 // For example, when completing: 335 // 336 // foo.ba<> 337 // 338 // If we are considering the deep candidate "bar.baz", cand is baz, 339 // objChain is [foo] and deepChain is [bar]. We would match the 340 // chain [foo, bar, baz]. 341 if len(chain) != len(c.inference.objChain)+len(cand.path)+1 { 342 return false 343 } 344 345 if chain[len(chain)-1] != cand.obj { 346 return false 347 } 348 349 for i, o := range c.inference.objChain { 350 if chain[i] != o { 351 return false 352 } 353 } 354 355 for i, o := range cand.path { 356 if chain[i+len(c.inference.objChain)] != o { 357 return false 358 } 359 } 360 361 return true 362 }