mirror of
https://github.com/golang/go
synced 2024-11-18 16:14:46 -07:00
50d618665b
For example: // Prefer functions that return one or two values. Previously // we had no preference. foo, bar := <> // Prefer functions that return "(int)" or "(int, ??)". Previously we // only preferred the former. var foo int foo, bar := <> // Prefer functions that return "(int)" or "(int, int)". Previously we // only preferred the former. var foo func(int, int) foo(<>) In the above example, we don't handle "foo" being variadic yet. I also took the liberty to break up matchingCandidate() into separate functions since it was getting rather long. Updates golang/go#36540. Change-Id: I9140dd989dfde1ddcfcd9d2a14198045c02587f2 Reviewed-on: https://go-review.googlesource.com/c/tools/+/215537 Run-TryBot: Muir Manders <muir@mnd.rs> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
197 lines
5.6 KiB
Go
197 lines
5.6 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 (
|
|
"go/types"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// 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 cand's subordinate objects for more
|
|
// completion items.
|
|
func (c *completer) deepSearch(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(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(obj.Imported(), stdScore, cand.imp)
|
|
default:
|
|
c.methodsAndFields(obj.Type(), cand.addressable, cand.imp)
|
|
}
|
|
|
|
// Pop the object off our search stack.
|
|
c.deepState.pop()
|
|
}
|