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