1
0
mirror of https://github.com/golang/go synced 2024-10-01 05:38:32 -06:00
go/internal/lsp/source/deep_completion.go
Heschi Kreinick 818555187f internal/lsp/source: attach Package to completions when available
Unimported completions now try to pull Packages from everywhere, not
just the transitive dependencies of the current package. That confused
the import formatting code, which only looked at deps. Pass the Package
along with the import suggestion, and use it when it's present.

Also change some error messages to be different for diagnostic purposes.

Fixes golang/go#35359.

Change-Id: Ia8ca923e46723e855ddd2da7611e6eb13c02bb4f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/205501
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-11-05 20:21:09 +00:00

197 lines
5.7 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 obj's subordinate objects for more
// completion items.
func (c *completer) deepSearch(obj types.Object, imp *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()
}