mirror of
https://github.com/golang/go
synced 2024-11-05 16:46:10 -07:00
ee2abff5cf
I originally made this change to see if it would help with the timeouts. Based on the TryBot results, it doesn't -- but I still think it's more correct to have the contexts this way. It was my mistake to put the context on the completer in the first place, I think. Change-Id: Ib77c8f0ac0b0d0922b82db4120820fb96cb664f4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/227303 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Heschi Kreinick <heschi@google.com>
220 lines
6.3 KiB
Go
220 lines
6.3 KiB
Go
// 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 (
|
|
"context"
|
|
"go/types"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// MaxDeepCompletions limits 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
|
|
}
|
|
|
|
// scorePenalty computes a deep candidate score penalty. A candidate
|
|
// is penalized based on depth to favor shallower candidates. We also
|
|
// give a slight bonus to unexported objects and a slight additional
|
|
// penalty to function objects.
|
|
func (s *deepCompletionState) scorePenalty() float64 {
|
|
var deepPenalty float64
|
|
for _, dc := range s.chain {
|
|
deepPenalty += 1
|
|
|
|
if !dc.Exported() {
|
|
deepPenalty -= 0.1
|
|
}
|
|
|
|
if _, isSig := dc.Type().Underlying().(*types.Signature); isSig {
|
|
deepPenalty += 0.1
|
|
}
|
|
}
|
|
|
|
// Normalize penalty to a max depth of 10.
|
|
return deepPenalty / 10
|
|
}
|
|
|
|
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 cand's subordinate objects for more
|
|
// completion items.
|
|
func (c *completer) deepSearch(ctx context.Context, cand candidate) {
|
|
if c.deepState.maxDepth == 0 {
|
|
return
|
|
}
|
|
|
|
obj := cand.obj
|
|
|
|
// 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(ctx, sig.Results().At(0).Type(), false, cand.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(ctx, obj.Imported(), stdScore, cand.imp)
|
|
default:
|
|
c.methodsAndFields(ctx, obj.Type(), cand.addressable, cand.imp)
|
|
}
|
|
|
|
// Pop the object off our search stack.
|
|
c.deepState.pop()
|
|
}
|