// Copyright 2019 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package source import ( "go/types" "strings" "time" "golang.org/x/tools/internal/imports" ) // Limit deep completion results because in most cases there are too many // to be useful. const MaxDeepCompletions = 3 // deepCompletionState stores our state as we search for deep completions. // "deep completion" refers to searching into objects' fields and methods to // find more completion candidates. type deepCompletionState struct { // maxDepth limits the deep completion search depth. 0 means // disabled and -1 means unlimited. maxDepth int // chain holds the traversal path as we do a depth-first search through // objects' members looking for exact type matches. chain []types.Object // chainNames holds the names of the chain objects. This allows us to // save allocations as we build many deep completion items. chainNames []string // highScores tracks the highest deep candidate scores we have found // so far. This is used to avoid work for low scoring deep candidates. highScores [MaxDeepCompletions]float64 // candidateCount is the count of unique deep candidates encountered // so far. candidateCount int } // push pushes obj onto our search stack. If invoke is true then // invocation parens "()" will be appended to the object name. func (s *deepCompletionState) push(obj types.Object, invoke bool) { s.chain = append(s.chain, obj) name := obj.Name() if invoke { name += "()" } s.chainNames = append(s.chainNames, name) } // pop pops the last object off our search stack. func (s *deepCompletionState) pop() { s.chain = s.chain[:len(s.chain)-1] s.chainNames = s.chainNames[:len(s.chainNames)-1] } // chainString joins the chain of objects' names together on ".". func (s *deepCompletionState) chainString(finalName string) string { s.chainNames = append(s.chainNames, finalName) chainStr := strings.Join(s.chainNames, ".") s.chainNames = s.chainNames[:len(s.chainNames)-1] return chainStr } // isHighScore returns whether score is among the top MaxDeepCompletions // deep candidate scores encountered so far. If so, it adds score to // highScores, possibly displacing an existing high score. func (s *deepCompletionState) isHighScore(score float64) bool { // Invariant: s.highScores is sorted with highest score first. Unclaimed // positions are trailing zeros. // First check for an unclaimed spot and claim if available. for i, deepScore := range s.highScores { if deepScore == 0 { s.highScores[i] = score return true } } // Otherwise, if we beat an existing score then take its spot and scoot // all lower scores down one position. for i, deepScore := range s.highScores { if score > deepScore { copy(s.highScores[i+1:], s.highScores[i:]) s.highScores[i] = score return true } } return false } func (c *completer) inDeepCompletion() bool { return len(c.deepState.chain) > 0 } // shouldPrune returns whether we should prune the current deep // candidate search to reduce the overall search scope. The // maximum search depth is reduced gradually as we use up our // completionBudget. func (c *completer) shouldPrune() bool { if !c.inDeepCompletion() { return false } // Check our remaining budget every 100 candidates. if c.opts.Budget > 0 && c.deepState.candidateCount%100 == 0 { spent := float64(time.Since(c.startTime)) / float64(c.opts.Budget) switch { case spent >= 0.90: // We are close to exhausting our budget. Disable deep completions. c.deepState.maxDepth = 0 case spent >= 0.75: // We are running out of budget, reduce max depth again. c.deepState.maxDepth = 2 case spent >= 0.5: // We have used half our budget, reduce max depth again. c.deepState.maxDepth = 3 case spent >= 0.25: // We have used a good chunk of our budget, so start limiting our search. // By default the search depth is unlimited, so this limit, while still // generous, is normally a huge reduction in search scope that will result // in our search completing very soon. c.deepState.maxDepth = 4 } } c.deepState.candidateCount++ if c.deepState.maxDepth >= 0 { return len(c.deepState.chain) >= c.deepState.maxDepth } return false } // deepSearch searches through obj's subordinate objects for more // completion items. func (c *completer) deepSearch(obj types.Object, imp *imports.ImportInfo) { if c.deepState.maxDepth == 0 { return } // If we are definitely completing a struct field name, deep completions // don't make sense. if c.wantStructFieldCompletions() && c.enclosingCompositeLiteral.inKey { return } // Don't search into type names. if isTypeName(obj) { return } if obj.Type() == nil { return } // Don't search embedded fields because they were already included in their // parent's fields. if v, ok := obj.(*types.Var); ok && v.Embedded() { return } if sig, ok := obj.Type().Underlying().(*types.Signature); ok { // If obj is a function that takes no arguments and returns one // value, keep searching across the function call. if sig.Params().Len() == 0 && sig.Results().Len() == 1 { // Pass invoke=true since the function needs to be invoked in // the deep chain. c.deepState.push(obj, true) // The result of a function call is not addressable. c.methodsAndFields(sig.Results().At(0).Type(), false, imp) c.deepState.pop() } } // Push this object onto our search stack. c.deepState.push(obj, false) switch obj := obj.(type) { case *types.PkgName: c.packageMembers(obj.Imported(), imp) default: // For now it is okay to assume obj is addressable since we don't search beyond // function calls. c.methodsAndFields(obj.Type(), true, imp) } // Pop the object off our search stack. c.deepState.pop() }