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  }