2019-04-24 17:26:34 -06:00
|
|
|
// Copyright 2018 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.
|
|
|
|
|
2018-11-07 18:57:08 -07:00
|
|
|
package source
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"go/ast"
|
2019-09-13 13:15:53 -06:00
|
|
|
"go/constant"
|
2020-01-29 22:52:22 -07:00
|
|
|
"go/scanner"
|
2018-11-07 18:57:08 -07:00
|
|
|
"go/token"
|
|
|
|
"go/types"
|
2019-12-27 13:45:09 -07:00
|
|
|
"math"
|
2020-01-16 16:00:13 -07:00
|
|
|
"sort"
|
2019-11-12 14:58:00 -07:00
|
|
|
"strconv"
|
internal/lsp: add fuzzy completion matching
Make use of the existing fuzzy matcher to perform server side fuzzy
completion matching. Previously the server did exact prefix matching
for completion candidates and left fancy filtering to the
client. Having the server do fuzzy matching has two main benefits:
- Deep completions now update as you type. The completion candidates
returned to the client are marked "incomplete", causing the client
to refresh the candidates after every keystroke. This lets the
server pick the most relevant set of deep completion candidates.
- All editors get fuzzy matching for free. VSCode has fuzzy matching
out of the box, but some editors either don't provide it, or it can
be difficult to set up.
I modified the fuzzy matcher to allow matches where the input doesn't
match the final segment of the candidate. For example, previously "ab"
would not match "abc.def" because the "b" in "ab" did not match the
final segment "def". I can see how this is useful when the text
matching happens in a vacuum and candidate's final segment is the most
specific part. But, in our case, we have various other methods to
order candidates, so we don't want to exclude them just because the
final segment doesn't match. For example, if we know our candidate
needs to be type "context.Context" and "foo.ctx" is of the right type,
we want to suggest "foo.ctx" as soon as the user starts inputting
"foo", even though "foo" doesn't match "ctx" at all.
Note that fuzzy matching is behind the "useDeepCompletions" config
flag for the time being.
Change-Id: Ic7674f0cf885af770c30daef472f2e3c5ac4db78
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190099
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-08-13 14:45:19 -06:00
|
|
|
"strings"
|
2019-12-27 13:46:49 -07:00
|
|
|
"sync"
|
internal/lsp: limit deep completion search scope
Deep completions can take a long time (500ms+) if there are many
large, deeply nested structs in scope. To make sure we return
completion results in a timely manner we now notice if we have spent
"too long" searching for deep completions and reduce the search scope.
In particular, our overall completion budget is 100ms. This value is
often cited as the longest latency that still feels instantaneous to
most people. As we spend 25%, 50%, and 75% of our budget we limit our
deep completion candidate search depth to 4, 3, and 2,
respectively. If we hit 90% of our budget, we disable deep completions
entirely.
In my testing, limiting the search scope to 4 normally makes even
enormous searches finish in a few milliseconds. Of course, you can
have arbitrarily many objects in scope with arbitrarily many fields,
so to cover our bases we continue to dial down the search depth as
needed.
I replaced the "enabled" field with a "maxDepth" field that disables
deep search when set to 0.
Change-Id: I9b5a07de70709895c065503ae6082d1ea615d1af
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190978
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2019-08-18 13:55:34 -06:00
|
|
|
"time"
|
2020-07-31 11:11:32 -06:00
|
|
|
"unicode"
|
2018-11-07 18:57:08 -07:00
|
|
|
|
|
|
|
"golang.org/x/tools/go/ast/astutil"
|
2020-04-17 07:32:56 -06:00
|
|
|
"golang.org/x/tools/internal/event"
|
2019-11-13 15:03:07 -07:00
|
|
|
"golang.org/x/tools/internal/imports"
|
2019-07-01 15:08:29 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/fuzzy"
|
2019-08-16 15:05:40 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
2019-04-28 21:19:54 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/snippet"
|
2019-08-06 13:13:11 -06:00
|
|
|
errors "golang.org/x/xerrors"
|
2018-11-07 18:57:08 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
type CompletionItem struct {
|
2019-04-28 21:19:54 -06:00
|
|
|
// Label is the primary text the user sees for this completion item.
|
|
|
|
Label string
|
|
|
|
|
2019-04-29 17:47:54 -06:00
|
|
|
// Detail is supplemental information to present to the user.
|
|
|
|
// This often contains the type or return type of the completion item.
|
2019-04-28 21:19:54 -06:00
|
|
|
Detail string
|
|
|
|
|
2019-04-29 17:47:54 -06:00
|
|
|
// InsertText is the text to insert if this item is selected.
|
|
|
|
// Any of the prefix that has already been typed is not trimmed.
|
|
|
|
// The insert text does not contain snippets.
|
|
|
|
InsertText string
|
2019-04-28 21:19:54 -06:00
|
|
|
|
2019-09-24 22:46:57 -06:00
|
|
|
Kind protocol.CompletionItemKind
|
2019-04-28 21:19:54 -06:00
|
|
|
|
2019-08-14 15:25:47 -06:00
|
|
|
// An optional array of additional TextEdits that are applied when
|
|
|
|
// selecting this completion.
|
|
|
|
//
|
|
|
|
// Additional text edits should be used to change text unrelated to the current cursor position
|
|
|
|
// (for example adding an import statement at the top of the file if the completion item will
|
|
|
|
// insert an unqualified type).
|
2019-08-16 15:05:40 -06:00
|
|
|
AdditionalTextEdits []protocol.TextEdit
|
2019-08-14 15:25:47 -06:00
|
|
|
|
2019-06-27 11:50:01 -06:00
|
|
|
// Depth is how many levels were searched to find this completion.
|
|
|
|
// For example when completing "foo<>", "fooBar" is depth 0, and
|
|
|
|
// "fooBar.Baz" is depth 1.
|
|
|
|
Depth int
|
|
|
|
|
2019-04-29 17:47:54 -06:00
|
|
|
// Score is the internal relevance score.
|
|
|
|
// A higher score indicates that this completion item is more relevant.
|
2019-04-28 21:19:54 -06:00
|
|
|
Score float64
|
|
|
|
|
2019-09-04 11:23:14 -06:00
|
|
|
// snippet is the LSP snippet for the completion item. The LSP
|
|
|
|
// specification contains details about LSP snippets. For example, a
|
|
|
|
// snippet for a function with the following signature:
|
2019-04-29 17:47:54 -06:00
|
|
|
//
|
|
|
|
// func foo(a, b, c int)
|
|
|
|
//
|
|
|
|
// would be:
|
|
|
|
//
|
|
|
|
// foo(${1:a int}, ${2: b int}, ${3: c int})
|
|
|
|
//
|
2019-09-04 11:23:14 -06:00
|
|
|
// If Placeholders is false in the CompletionOptions, the above
|
|
|
|
// snippet would instead be:
|
|
|
|
//
|
|
|
|
// foo(${1:})
|
|
|
|
snippet *snippet.Builder
|
2019-07-02 15:31:31 -06:00
|
|
|
|
|
|
|
// Documentation is the documentation for the completion item.
|
|
|
|
Documentation string
|
2020-03-01 15:33:21 -07:00
|
|
|
|
|
|
|
// obj is the object from which this candidate was derived, if any.
|
|
|
|
// obj is for internal use only.
|
|
|
|
obj types.Object
|
2019-05-14 12:15:18 -06:00
|
|
|
}
|
|
|
|
|
2019-09-04 11:23:14 -06:00
|
|
|
// Snippet is a convenience returns the snippet if available, otherwise
|
|
|
|
// the InsertText.
|
2019-05-14 12:15:18 -06:00
|
|
|
// used for an item, depending on if the callee wants placeholders or not.
|
2019-09-04 11:23:14 -06:00
|
|
|
func (i *CompletionItem) Snippet() string {
|
|
|
|
if i.snippet != nil {
|
|
|
|
return i.snippet.String()
|
2019-05-14 12:15:18 -06:00
|
|
|
}
|
|
|
|
return i.InsertText
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
// Scoring constants are used for weighting the relevance of different candidates.
|
|
|
|
const (
|
|
|
|
// stdScore is the base score for all completion items.
|
|
|
|
stdScore float64 = 1.0
|
|
|
|
|
|
|
|
// highScore indicates a very relevant completion item.
|
|
|
|
highScore float64 = 10.0
|
|
|
|
|
|
|
|
// lowScore indicates an irrelevant or not useful completion item.
|
|
|
|
lowScore float64 = 0.01
|
|
|
|
)
|
|
|
|
|
2019-09-13 23:13:55 -06:00
|
|
|
// matcher matches a candidate's label against the user input. The
|
|
|
|
// returned score reflects the quality of the match. A score of zero
|
|
|
|
// indicates no match, and a score of one means a perfect match.
|
internal/lsp: add fuzzy completion matching
Make use of the existing fuzzy matcher to perform server side fuzzy
completion matching. Previously the server did exact prefix matching
for completion candidates and left fancy filtering to the
client. Having the server do fuzzy matching has two main benefits:
- Deep completions now update as you type. The completion candidates
returned to the client are marked "incomplete", causing the client
to refresh the candidates after every keystroke. This lets the
server pick the most relevant set of deep completion candidates.
- All editors get fuzzy matching for free. VSCode has fuzzy matching
out of the box, but some editors either don't provide it, or it can
be difficult to set up.
I modified the fuzzy matcher to allow matches where the input doesn't
match the final segment of the candidate. For example, previously "ab"
would not match "abc.def" because the "b" in "ab" did not match the
final segment "def". I can see how this is useful when the text
matching happens in a vacuum and candidate's final segment is the most
specific part. But, in our case, we have various other methods to
order candidates, so we don't want to exclude them just because the
final segment doesn't match. For example, if we know our candidate
needs to be type "context.Context" and "foo.ctx" is of the right type,
we want to suggest "foo.ctx" as soon as the user starts inputting
"foo", even though "foo" doesn't match "ctx" at all.
Note that fuzzy matching is behind the "useDeepCompletions" config
flag for the time being.
Change-Id: Ic7674f0cf885af770c30daef472f2e3c5ac4db78
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190099
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-08-13 14:45:19 -06:00
|
|
|
type matcher interface {
|
|
|
|
Score(candidateLabel string) (score float32)
|
|
|
|
}
|
|
|
|
|
2019-09-26 05:59:06 -06:00
|
|
|
// prefixMatcher implements case sensitive prefix matching.
|
internal/lsp: add fuzzy completion matching
Make use of the existing fuzzy matcher to perform server side fuzzy
completion matching. Previously the server did exact prefix matching
for completion candidates and left fancy filtering to the
client. Having the server do fuzzy matching has two main benefits:
- Deep completions now update as you type. The completion candidates
returned to the client are marked "incomplete", causing the client
to refresh the candidates after every keystroke. This lets the
server pick the most relevant set of deep completion candidates.
- All editors get fuzzy matching for free. VSCode has fuzzy matching
out of the box, but some editors either don't provide it, or it can
be difficult to set up.
I modified the fuzzy matcher to allow matches where the input doesn't
match the final segment of the candidate. For example, previously "ab"
would not match "abc.def" because the "b" in "ab" did not match the
final segment "def". I can see how this is useful when the text
matching happens in a vacuum and candidate's final segment is the most
specific part. But, in our case, we have various other methods to
order candidates, so we don't want to exclude them just because the
final segment doesn't match. For example, if we know our candidate
needs to be type "context.Context" and "foo.ctx" is of the right type,
we want to suggest "foo.ctx" as soon as the user starts inputting
"foo", even though "foo" doesn't match "ctx" at all.
Note that fuzzy matching is behind the "useDeepCompletions" config
flag for the time being.
Change-Id: Ic7674f0cf885af770c30daef472f2e3c5ac4db78
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190099
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-08-13 14:45:19 -06:00
|
|
|
type prefixMatcher string
|
|
|
|
|
|
|
|
func (pm prefixMatcher) Score(candidateLabel string) float32 {
|
2019-09-26 05:59:06 -06:00
|
|
|
if strings.HasPrefix(candidateLabel, string(pm)) {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
// insensitivePrefixMatcher implements case insensitive prefix matching.
|
|
|
|
type insensitivePrefixMatcher string
|
|
|
|
|
|
|
|
func (ipm insensitivePrefixMatcher) Score(candidateLabel string) float32 {
|
|
|
|
if strings.HasPrefix(strings.ToLower(candidateLabel), string(ipm)) {
|
internal/lsp: add fuzzy completion matching
Make use of the existing fuzzy matcher to perform server side fuzzy
completion matching. Previously the server did exact prefix matching
for completion candidates and left fancy filtering to the
client. Having the server do fuzzy matching has two main benefits:
- Deep completions now update as you type. The completion candidates
returned to the client are marked "incomplete", causing the client
to refresh the candidates after every keystroke. This lets the
server pick the most relevant set of deep completion candidates.
- All editors get fuzzy matching for free. VSCode has fuzzy matching
out of the box, but some editors either don't provide it, or it can
be difficult to set up.
I modified the fuzzy matcher to allow matches where the input doesn't
match the final segment of the candidate. For example, previously "ab"
would not match "abc.def" because the "b" in "ab" did not match the
final segment "def". I can see how this is useful when the text
matching happens in a vacuum and candidate's final segment is the most
specific part. But, in our case, we have various other methods to
order candidates, so we don't want to exclude them just because the
final segment doesn't match. For example, if we know our candidate
needs to be type "context.Context" and "foo.ctx" is of the right type,
we want to suggest "foo.ctx" as soon as the user starts inputting
"foo", even though "foo" doesn't match "ctx" at all.
Note that fuzzy matching is behind the "useDeepCompletions" config
flag for the time being.
Change-Id: Ic7674f0cf885af770c30daef472f2e3c5ac4db78
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190099
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-08-13 14:45:19 -06:00
|
|
|
return 1
|
|
|
|
}
|
2019-08-27 17:41:48 -06:00
|
|
|
return -1
|
internal/lsp: add fuzzy completion matching
Make use of the existing fuzzy matcher to perform server side fuzzy
completion matching. Previously the server did exact prefix matching
for completion candidates and left fancy filtering to the
client. Having the server do fuzzy matching has two main benefits:
- Deep completions now update as you type. The completion candidates
returned to the client are marked "incomplete", causing the client
to refresh the candidates after every keystroke. This lets the
server pick the most relevant set of deep completion candidates.
- All editors get fuzzy matching for free. VSCode has fuzzy matching
out of the box, but some editors either don't provide it, or it can
be difficult to set up.
I modified the fuzzy matcher to allow matches where the input doesn't
match the final segment of the candidate. For example, previously "ab"
would not match "abc.def" because the "b" in "ab" did not match the
final segment "def". I can see how this is useful when the text
matching happens in a vacuum and candidate's final segment is the most
specific part. But, in our case, we have various other methods to
order candidates, so we don't want to exclude them just because the
final segment doesn't match. For example, if we know our candidate
needs to be type "context.Context" and "foo.ctx" is of the right type,
we want to suggest "foo.ctx" as soon as the user starts inputting
"foo", even though "foo" doesn't match "ctx" at all.
Note that fuzzy matching is behind the "useDeepCompletions" config
flag for the time being.
Change-Id: Ic7674f0cf885af770c30daef472f2e3c5ac4db78
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190099
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-08-13 14:45:19 -06:00
|
|
|
}
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
// completer contains the necessary information for a single completion request.
|
|
|
|
type completer struct {
|
2019-09-27 11:17:59 -06:00
|
|
|
snapshot Snapshot
|
|
|
|
pkg Package
|
2019-12-29 00:22:12 -07:00
|
|
|
qf types.Qualifier
|
|
|
|
opts *completionOptions
|
2019-04-29 19:08:16 -06:00
|
|
|
|
2020-08-24 17:49:55 -06:00
|
|
|
// triggerCharacter is the character that triggered this request, if any.
|
|
|
|
triggerCharacter string
|
|
|
|
|
2019-08-14 15:25:47 -06:00
|
|
|
// filename is the name of the file associated with this completion request.
|
|
|
|
filename string
|
|
|
|
|
|
|
|
// file is the AST of the file associated with this completion request.
|
|
|
|
file *ast.File
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
// pos is the position at which the request was triggered.
|
|
|
|
pos token.Pos
|
2018-12-05 15:00:36 -07:00
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
// path is the path of AST nodes enclosing the position.
|
|
|
|
path []ast.Node
|
|
|
|
|
|
|
|
// seen is the map that ensures we do not return duplicate results.
|
|
|
|
seen map[types.Object]bool
|
|
|
|
|
|
|
|
// items is the list of completion items returned.
|
|
|
|
items []CompletionItem
|
|
|
|
|
2019-05-17 11:45:59 -06:00
|
|
|
// surrounding describes the identifier surrounding the position.
|
|
|
|
surrounding *Selection
|
2019-04-24 17:26:34 -06:00
|
|
|
|
2020-01-17 14:53:10 -07:00
|
|
|
// inference contains information we've inferred about ideal
|
|
|
|
// candidates such as the candidate's type.
|
|
|
|
inference candidateInference
|
2019-04-24 17:26:34 -06:00
|
|
|
|
2019-09-18 13:26:39 -06:00
|
|
|
// enclosingFunc contains information about the function enclosing
|
|
|
|
// the position.
|
|
|
|
enclosingFunc *funcInfo
|
2019-04-24 17:26:34 -06:00
|
|
|
|
2019-05-13 15:37:08 -06:00
|
|
|
// enclosingCompositeLiteral contains information about the composite literal
|
|
|
|
// enclosing the position.
|
|
|
|
enclosingCompositeLiteral *compLitInfo
|
2019-06-27 11:50:01 -06:00
|
|
|
|
|
|
|
// deepState contains the current state of our deep completion search.
|
|
|
|
deepState deepCompletionState
|
2019-07-01 15:08:29 -06:00
|
|
|
|
internal/lsp: add fuzzy completion matching
Make use of the existing fuzzy matcher to perform server side fuzzy
completion matching. Previously the server did exact prefix matching
for completion candidates and left fancy filtering to the
client. Having the server do fuzzy matching has two main benefits:
- Deep completions now update as you type. The completion candidates
returned to the client are marked "incomplete", causing the client
to refresh the candidates after every keystroke. This lets the
server pick the most relevant set of deep completion candidates.
- All editors get fuzzy matching for free. VSCode has fuzzy matching
out of the box, but some editors either don't provide it, or it can
be difficult to set up.
I modified the fuzzy matcher to allow matches where the input doesn't
match the final segment of the candidate. For example, previously "ab"
would not match "abc.def" because the "b" in "ab" did not match the
final segment "def". I can see how this is useful when the text
matching happens in a vacuum and candidate's final segment is the most
specific part. But, in our case, we have various other methods to
order candidates, so we don't want to exclude them just because the
final segment doesn't match. For example, if we know our candidate
needs to be type "context.Context" and "foo.ctx" is of the right type,
we want to suggest "foo.ctx" as soon as the user starts inputting
"foo", even though "foo" doesn't match "ctx" at all.
Note that fuzzy matching is behind the "useDeepCompletions" config
flag for the time being.
Change-Id: Ic7674f0cf885af770c30daef472f2e3c5ac4db78
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190099
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-08-13 14:45:19 -06:00
|
|
|
// matcher matches the candidates against the surrounding prefix.
|
|
|
|
matcher matcher
|
2019-08-16 10:45:09 -06:00
|
|
|
|
|
|
|
// methodSetCache caches the types.NewMethodSet call, which is relatively
|
|
|
|
// expensive and can be called many times for the same type while searching
|
|
|
|
// for deep completions.
|
|
|
|
methodSetCache map[methodSetKey]*types.MethodSet
|
2019-08-16 15:05:40 -06:00
|
|
|
|
|
|
|
// mapper converts the positions in the file from which the completion originated.
|
|
|
|
mapper *protocol.ColumnMapper
|
internal/lsp: limit deep completion search scope
Deep completions can take a long time (500ms+) if there are many
large, deeply nested structs in scope. To make sure we return
completion results in a timely manner we now notice if we have spent
"too long" searching for deep completions and reduce the search scope.
In particular, our overall completion budget is 100ms. This value is
often cited as the longest latency that still feels instantaneous to
most people. As we spend 25%, 50%, and 75% of our budget we limit our
deep completion candidate search depth to 4, 3, and 2,
respectively. If we hit 90% of our budget, we disable deep completions
entirely.
In my testing, limiting the search scope to 4 normally makes even
enormous searches finish in a few milliseconds. Of course, you can
have arbitrarily many objects in scope with arbitrarily many fields,
so to cover our bases we continue to dial down the search depth as
needed.
I replaced the "enabled" field with a "maxDepth" field that disables
deep search when set to 0.
Change-Id: I9b5a07de70709895c065503ae6082d1ea615d1af
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190978
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2019-08-18 13:55:34 -06:00
|
|
|
|
|
|
|
// startTime is when we started processing this completion request. It does
|
|
|
|
// not include any time the request spent in the queue.
|
|
|
|
startTime time.Time
|
2019-05-13 15:37:08 -06:00
|
|
|
}
|
|
|
|
|
2019-09-18 13:26:39 -06:00
|
|
|
// funcInfo holds info about a function object.
|
|
|
|
type funcInfo struct {
|
|
|
|
// sig is the function declaration enclosing the position.
|
|
|
|
sig *types.Signature
|
|
|
|
|
|
|
|
// body is the function's body.
|
|
|
|
body *ast.BlockStmt
|
|
|
|
}
|
|
|
|
|
2019-05-13 15:37:08 -06:00
|
|
|
type compLitInfo struct {
|
|
|
|
// cl is the *ast.CompositeLit enclosing the position.
|
|
|
|
cl *ast.CompositeLit
|
|
|
|
|
|
|
|
// clType is the type of cl.
|
|
|
|
clType types.Type
|
2019-04-28 21:19:54 -06:00
|
|
|
|
2019-05-13 15:37:08 -06:00
|
|
|
// kv is the *ast.KeyValueExpr enclosing the position, if any.
|
|
|
|
kv *ast.KeyValueExpr
|
2019-04-28 21:19:54 -06:00
|
|
|
|
2019-05-13 15:37:08 -06:00
|
|
|
// inKey is true if we are certain the position is in the key side
|
|
|
|
// of a key-value pair.
|
|
|
|
inKey bool
|
|
|
|
|
|
|
|
// maybeInFieldName is true if inKey is false and it is possible
|
|
|
|
// we are completing a struct field name. For example,
|
|
|
|
// "SomeStruct{<>}" will be inKey=false, but maybeInFieldName=true
|
|
|
|
// because we _could_ be completing a field name.
|
|
|
|
maybeInFieldName bool
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
|
|
|
|
2019-11-05 12:53:55 -07:00
|
|
|
type importInfo struct {
|
|
|
|
importPath string
|
|
|
|
name string
|
|
|
|
pkg Package
|
|
|
|
}
|
|
|
|
|
2019-08-16 10:45:09 -06:00
|
|
|
type methodSetKey struct {
|
|
|
|
typ types.Type
|
|
|
|
addressable bool
|
|
|
|
}
|
|
|
|
|
2019-05-17 11:45:59 -06:00
|
|
|
// A Selection represents the cursor position and surrounding identifier.
|
|
|
|
type Selection struct {
|
2019-08-16 15:05:40 -06:00
|
|
|
content string
|
|
|
|
cursor token.Pos
|
|
|
|
mappedRange
|
2019-05-13 14:49:29 -06:00
|
|
|
}
|
|
|
|
|
2020-08-21 15:16:29 -06:00
|
|
|
func (p Selection) Content() string {
|
|
|
|
return p.content
|
|
|
|
}
|
|
|
|
|
2019-05-17 11:45:59 -06:00
|
|
|
func (p Selection) Prefix() string {
|
2019-08-16 15:05:40 -06:00
|
|
|
return p.content[:p.cursor-p.spanRange.Start]
|
2019-05-17 11:45:59 -06:00
|
|
|
}
|
|
|
|
|
2019-09-17 20:48:41 -06:00
|
|
|
func (p Selection) Suffix() string {
|
|
|
|
return p.content[p.cursor-p.spanRange.Start:]
|
|
|
|
}
|
|
|
|
|
2019-05-17 11:45:59 -06:00
|
|
|
func (c *completer) setSurrounding(ident *ast.Ident) {
|
|
|
|
if c.surrounding != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !(ident.Pos() <= c.pos && c.pos <= ident.End()) {
|
|
|
|
return
|
|
|
|
}
|
2019-09-17 20:48:41 -06:00
|
|
|
|
2019-05-17 11:45:59 -06:00
|
|
|
c.surrounding = &Selection{
|
2019-08-16 15:05:40 -06:00
|
|
|
content: ident.Name,
|
|
|
|
cursor: c.pos,
|
2019-11-22 13:20:08 -07:00
|
|
|
// Overwrite the prefix only.
|
2020-07-28 15:00:10 -06:00
|
|
|
mappedRange: newMappedRange(c.snapshot.FileSet(), c.mapper, ident.Pos(), ident.End()),
|
2019-05-17 11:45:59 -06:00
|
|
|
}
|
internal/lsp: add fuzzy completion matching
Make use of the existing fuzzy matcher to perform server side fuzzy
completion matching. Previously the server did exact prefix matching
for completion candidates and left fancy filtering to the
client. Having the server do fuzzy matching has two main benefits:
- Deep completions now update as you type. The completion candidates
returned to the client are marked "incomplete", causing the client
to refresh the candidates after every keystroke. This lets the
server pick the most relevant set of deep completion candidates.
- All editors get fuzzy matching for free. VSCode has fuzzy matching
out of the box, but some editors either don't provide it, or it can
be difficult to set up.
I modified the fuzzy matcher to allow matches where the input doesn't
match the final segment of the candidate. For example, previously "ab"
would not match "abc.def" because the "b" in "ab" did not match the
final segment "def". I can see how this is useful when the text
matching happens in a vacuum and candidate's final segment is the most
specific part. But, in our case, we have various other methods to
order candidates, so we don't want to exclude them just because the
final segment doesn't match. For example, if we know our candidate
needs to be type "context.Context" and "foo.ctx" is of the right type,
we want to suggest "foo.ctx" as soon as the user starts inputting
"foo", even though "foo" doesn't match "ctx" at all.
Note that fuzzy matching is behind the "useDeepCompletions" config
flag for the time being.
Change-Id: Ic7674f0cf885af770c30daef472f2e3c5ac4db78
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190099
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-08-13 14:45:19 -06:00
|
|
|
|
2020-08-21 16:22:36 -06:00
|
|
|
c.setMatcherFromPrefix(c.surrounding.Prefix())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *completer) setMatcherFromPrefix(prefix string) {
|
2019-12-29 00:22:12 -07:00
|
|
|
switch c.opts.matcher {
|
|
|
|
case Fuzzy:
|
2020-08-21 16:22:36 -06:00
|
|
|
c.matcher = fuzzy.NewMatcher(prefix)
|
2019-12-29 00:22:12 -07:00
|
|
|
case CaseSensitive:
|
2020-08-21 16:22:36 -06:00
|
|
|
c.matcher = prefixMatcher(prefix)
|
2019-12-29 00:22:12 -07:00
|
|
|
default:
|
2020-08-21 16:22:36 -06:00
|
|
|
c.matcher = insensitivePrefixMatcher(strings.ToLower(prefix))
|
2019-07-01 15:08:29 -06:00
|
|
|
}
|
2019-05-17 11:45:59 -06:00
|
|
|
}
|
2019-05-13 14:49:29 -06:00
|
|
|
|
2019-08-16 15:05:40 -06:00
|
|
|
func (c *completer) getSurrounding() *Selection {
|
|
|
|
if c.surrounding == nil {
|
|
|
|
c.surrounding = &Selection{
|
2019-11-22 13:20:08 -07:00
|
|
|
content: "",
|
|
|
|
cursor: c.pos,
|
2020-07-28 15:00:10 -06:00
|
|
|
mappedRange: newMappedRange(c.snapshot.FileSet(), c.mapper, c.pos, c.pos),
|
2019-08-16 15:05:40 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return c.surrounding
|
|
|
|
}
|
|
|
|
|
2019-06-27 11:50:01 -06:00
|
|
|
// found adds a candidate completion. We will also search through the object's
|
|
|
|
// members for more candidates.
|
2020-04-05 23:18:15 -06:00
|
|
|
func (c *completer) found(ctx context.Context, cand candidate) {
|
2019-12-21 16:39:02 -07:00
|
|
|
obj := cand.obj
|
|
|
|
|
2019-09-09 18:22:42 -06:00
|
|
|
if obj.Pkg() != nil && obj.Pkg() != c.pkg.GetTypes() && !obj.Exported() {
|
2019-08-16 10:45:09 -06:00
|
|
|
// obj is not accessible because it lives in another package and is not
|
|
|
|
// exported. Don't treat it as a completion candidate.
|
|
|
|
return
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
2019-06-27 11:50:01 -06:00
|
|
|
|
|
|
|
if c.inDeepCompletion() {
|
|
|
|
// When searching deep, just make sure we don't have a cycle in our chain.
|
|
|
|
// We don't dedupe by object because we want to allow both "foo.Baz" and
|
|
|
|
// "bar.Baz" even though "Baz" is represented the same types.Object in both.
|
|
|
|
for _, seenObj := range c.deepState.chain {
|
|
|
|
if seenObj == obj {
|
2019-08-16 10:45:09 -06:00
|
|
|
return
|
2019-06-27 11:50:01 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// At the top level, dedupe by object.
|
|
|
|
if c.seen[obj] {
|
2019-08-16 10:45:09 -06:00
|
|
|
return
|
2019-06-27 11:50:01 -06:00
|
|
|
}
|
|
|
|
c.seen[obj] = true
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
2019-06-19 16:24:05 -06:00
|
|
|
|
internal/lsp: limit deep completion search scope
Deep completions can take a long time (500ms+) if there are many
large, deeply nested structs in scope. To make sure we return
completion results in a timely manner we now notice if we have spent
"too long" searching for deep completions and reduce the search scope.
In particular, our overall completion budget is 100ms. This value is
often cited as the longest latency that still feels instantaneous to
most people. As we spend 25%, 50%, and 75% of our budget we limit our
deep completion candidate search depth to 4, 3, and 2,
respectively. If we hit 90% of our budget, we disable deep completions
entirely.
In my testing, limiting the search scope to 4 normally makes even
enormous searches finish in a few milliseconds. Of course, you can
have arbitrarily many objects in scope with arbitrarily many fields,
so to cover our bases we continue to dial down the search depth as
needed.
I replaced the "enabled" field with a "maxDepth" field that disables
deep search when set to 0.
Change-Id: I9b5a07de70709895c065503ae6082d1ea615d1af
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190978
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2019-08-18 13:55:34 -06:00
|
|
|
// If we are running out of budgeted time we must limit our search for deep
|
|
|
|
// completion candidates.
|
|
|
|
if c.shouldPrune() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-01-27 11:04:02 -07:00
|
|
|
// If we know we want a type name, don't offer non-type name
|
|
|
|
// candidates. However, do offer package names since they can
|
|
|
|
// contain type names, and do offer any candidate without a type
|
|
|
|
// since we aren't sure if it is a type name or not (i.e. unimported
|
|
|
|
// candidate).
|
|
|
|
if c.wantTypeName() && obj.Type() != nil && !isTypeName(obj) && !isPkgName(obj) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
if c.matchingCandidate(&cand) {
|
2019-06-19 16:24:05 -06:00
|
|
|
cand.score *= highScore
|
2020-08-06 23:33:47 -06:00
|
|
|
|
2020-08-07 23:50:58 -06:00
|
|
|
if p := c.penalty(&cand); p > 0 {
|
|
|
|
cand.score *= (1 - p)
|
2020-08-06 23:33:47 -06:00
|
|
|
}
|
internal/lsp: add literal completion candidates
Add support for literal completion candidates such as "[]int{}" or
"make([]int, 0)". We support both named and unnamed types. I used the
existing type matching logic, so, for example, if the expected type is
an interface, we will suggest literal candidates that implement the
interface.
The literal candidates have a lower score than normal matching
candidates, so they shouldn't be disruptive in cases where you don't
want a literal candidate.
This commit adds support for slice, array, struct, map, and channel
literal candidates since they are pretty similar. Functions will be
supported in a subsequent commit.
I also added support for setting a snippet's final tab stop. This is
useful if you want the cursor to end up somewhere other than the
character after the snippet.
Change-Id: Id3b74260fff4d61703989b422267021b00cec005
Reviewed-on: https://go-review.googlesource.com/c/tools/+/193698
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-09-04 17:12:37 -06:00
|
|
|
} else if isTypeName(obj) {
|
|
|
|
// If obj is a *types.TypeName that didn't otherwise match, check
|
|
|
|
// if a literal object of this type makes a good candidate.
|
2019-12-06 15:29:09 -07:00
|
|
|
|
|
|
|
// We only care about named types (i.e. don't want builtin types).
|
|
|
|
if _, isNamed := obj.Type().(*types.Named); isNamed {
|
2020-04-05 23:18:15 -06:00
|
|
|
c.literal(ctx, obj.Type(), cand.imp)
|
2019-12-06 15:29:09 -07:00
|
|
|
}
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
2019-06-19 16:24:05 -06:00
|
|
|
|
2020-08-01 13:34:17 -06:00
|
|
|
// Lower score of method calls so we prefer fields and vars over calls.
|
2020-03-05 22:46:49 -07:00
|
|
|
if cand.expandFuncCall {
|
2020-08-01 13:34:17 -06:00
|
|
|
if sig, ok := obj.Type().Underlying().(*types.Signature); ok && sig.Recv() != nil {
|
|
|
|
cand.score *= 0.9
|
|
|
|
}
|
2020-03-05 22:46:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Prefer private objects over public ones.
|
|
|
|
if !obj.Exported() && obj.Parent() != types.Universe {
|
|
|
|
cand.score *= 1.1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Favor shallow matches by lowering score according to depth.
|
|
|
|
cand.score -= cand.score * c.deepState.scorePenalty()
|
|
|
|
|
2019-08-27 17:41:48 -06:00
|
|
|
if cand.score < 0 {
|
|
|
|
cand.score = 0
|
|
|
|
}
|
internal/lsp: add fuzzy completion matching
Make use of the existing fuzzy matcher to perform server side fuzzy
completion matching. Previously the server did exact prefix matching
for completion candidates and left fancy filtering to the
client. Having the server do fuzzy matching has two main benefits:
- Deep completions now update as you type. The completion candidates
returned to the client are marked "incomplete", causing the client
to refresh the candidates after every keystroke. This lets the
server pick the most relevant set of deep completion candidates.
- All editors get fuzzy matching for free. VSCode has fuzzy matching
out of the box, but some editors either don't provide it, or it can
be difficult to set up.
I modified the fuzzy matcher to allow matches where the input doesn't
match the final segment of the candidate. For example, previously "ab"
would not match "abc.def" because the "b" in "ab" did not match the
final segment "def". I can see how this is useful when the text
matching happens in a vacuum and candidate's final segment is the most
specific part. But, in our case, we have various other methods to
order candidates, so we don't want to exclude them just because the
final segment doesn't match. For example, if we know our candidate
needs to be type "context.Context" and "foo.ctx" is of the right type,
we want to suggest "foo.ctx" as soon as the user starts inputting
"foo", even though "foo" doesn't match "ctx" at all.
Note that fuzzy matching is behind the "useDeepCompletions" config
flag for the time being.
Change-Id: Ic7674f0cf885af770c30daef472f2e3c5ac4db78
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190099
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-08-13 14:45:19 -06:00
|
|
|
|
2019-08-16 10:45:09 -06:00
|
|
|
cand.name = c.deepState.chainString(obj.Name())
|
|
|
|
matchScore := c.matcher.Score(cand.name)
|
2019-09-13 23:13:55 -06:00
|
|
|
if matchScore > 0 {
|
2019-08-16 10:45:09 -06:00
|
|
|
cand.score *= float64(matchScore)
|
|
|
|
|
|
|
|
// Avoid calling c.item() for deep candidates that wouldn't be in the top
|
|
|
|
// MaxDeepCompletions anyway.
|
|
|
|
if !c.inDeepCompletion() || c.deepState.isHighScore(cand.score) {
|
2020-04-05 23:18:15 -06:00
|
|
|
if item, err := c.item(ctx, cand); err == nil {
|
2019-08-16 10:45:09 -06:00
|
|
|
c.items = append(c.items, item)
|
|
|
|
}
|
internal/lsp: add fuzzy completion matching
Make use of the existing fuzzy matcher to perform server side fuzzy
completion matching. Previously the server did exact prefix matching
for completion candidates and left fancy filtering to the
client. Having the server do fuzzy matching has two main benefits:
- Deep completions now update as you type. The completion candidates
returned to the client are marked "incomplete", causing the client
to refresh the candidates after every keystroke. This lets the
server pick the most relevant set of deep completion candidates.
- All editors get fuzzy matching for free. VSCode has fuzzy matching
out of the box, but some editors either don't provide it, or it can
be difficult to set up.
I modified the fuzzy matcher to allow matches where the input doesn't
match the final segment of the candidate. For example, previously "ab"
would not match "abc.def" because the "b" in "ab" did not match the
final segment "def". I can see how this is useful when the text
matching happens in a vacuum and candidate's final segment is the most
specific part. But, in our case, we have various other methods to
order candidates, so we don't want to exclude them just because the
final segment doesn't match. For example, if we know our candidate
needs to be type "context.Context" and "foo.ctx" is of the right type,
we want to suggest "foo.ctx" as soon as the user starts inputting
"foo", even though "foo" doesn't match "ctx" at all.
Note that fuzzy matching is behind the "useDeepCompletions" config
flag for the time being.
Change-Id: Ic7674f0cf885af770c30daef472f2e3c5ac4db78
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190099
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-08-13 14:45:19 -06:00
|
|
|
}
|
|
|
|
}
|
2019-06-27 11:50:01 -06:00
|
|
|
|
2020-04-05 23:18:15 -06:00
|
|
|
c.deepSearch(ctx, cand)
|
2019-06-19 16:24:05 -06:00
|
|
|
}
|
|
|
|
|
2020-08-07 23:50:58 -06:00
|
|
|
// penalty reports a score penalty for cand in the range (0, 1).
|
|
|
|
// For example, a candidate is penalized if it has already been used
|
|
|
|
// in another switch case statement.
|
|
|
|
func (c *completer) penalty(cand *candidate) float64 {
|
|
|
|
for _, p := range c.inference.penalized {
|
|
|
|
if c.objChainMatches(cand.obj, p.objChain) {
|
|
|
|
return p.penalty
|
2020-08-06 23:33:47 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-07 23:50:58 -06:00
|
|
|
return 0
|
2020-08-06 23:33:47 -06:00
|
|
|
}
|
|
|
|
|
2019-06-19 16:24:05 -06:00
|
|
|
// candidate represents a completion candidate.
|
|
|
|
type candidate struct {
|
|
|
|
// obj is the types.Object to complete to.
|
|
|
|
obj types.Object
|
|
|
|
|
|
|
|
// score is used to rank candidates.
|
|
|
|
score float64
|
|
|
|
|
2019-08-16 10:45:09 -06:00
|
|
|
// name is the deep object name path, e.g. "foo.bar"
|
|
|
|
name string
|
|
|
|
|
2019-06-19 16:24:05 -06:00
|
|
|
// expandFuncCall is true if obj should be invoked in the completion.
|
|
|
|
// For example, expandFuncCall=true yields "foo()", expandFuncCall=false yields "foo".
|
|
|
|
expandFuncCall bool
|
2019-08-14 15:25:47 -06:00
|
|
|
|
2019-12-22 10:58:14 -07:00
|
|
|
// takeAddress is true if the completion should take a pointer to obj.
|
|
|
|
// For example, takeAddress=true yields "&foo", takeAddress=false yields "foo".
|
|
|
|
takeAddress bool
|
|
|
|
|
|
|
|
// addressable is true if a pointer can be taken to the candidate.
|
|
|
|
addressable bool
|
|
|
|
|
2019-12-27 09:57:52 -07:00
|
|
|
// makePointer is true if the candidate type name T should be made into *T.
|
|
|
|
makePointer bool
|
|
|
|
|
2020-02-07 14:55:25 -07:00
|
|
|
// dereference is a count of how many times to dereference the candidate obj.
|
|
|
|
// For example, dereference=2 turns "foo" into "**foo" when formatting.
|
|
|
|
dereference int
|
2020-01-18 14:33:26 -07:00
|
|
|
|
2020-08-11 15:16:46 -06:00
|
|
|
// variadic is true if this candidate fills a variadic param and
|
|
|
|
// needs "..." appended.
|
|
|
|
variadic bool
|
|
|
|
|
2019-08-14 15:25:47 -06:00
|
|
|
// imp is the import that needs to be added to this package in order
|
|
|
|
// for this candidate to be valid. nil if no import needed.
|
2019-11-05 12:53:55 -07:00
|
|
|
imp *importInfo
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
2018-12-05 15:00:36 -07:00
|
|
|
|
2019-09-17 16:52:19 -06:00
|
|
|
// ErrIsDefinition is an error that informs the user they got no
|
|
|
|
// completions because they tried to complete the name of a new object
|
|
|
|
// being defined.
|
|
|
|
type ErrIsDefinition struct {
|
|
|
|
objStr string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e ErrIsDefinition) Error() string {
|
|
|
|
msg := "this is a definition"
|
|
|
|
if e.objStr != "" {
|
|
|
|
msg += " of " + e.objStr
|
|
|
|
}
|
|
|
|
return msg
|
|
|
|
}
|
|
|
|
|
2018-12-05 15:00:36 -07:00
|
|
|
// Completion returns a list of possible candidates for completion, given a
|
2019-04-24 17:26:34 -06:00
|
|
|
// a file and a position.
|
|
|
|
//
|
2019-05-17 11:45:59 -06:00
|
|
|
// The selection is computed based on the preceding identifier and can be used by
|
2019-04-24 17:26:34 -06:00
|
|
|
// the client to score the quality of the completion. For instance, some clients
|
|
|
|
// may tolerate imperfect matches as valid completion results, since users may make typos.
|
2020-08-24 17:49:55 -06:00
|
|
|
func Completion(ctx context.Context, snapshot Snapshot, fh FileHandle, protoPos protocol.Position, triggerCharacter string) ([]CompletionItem, *Selection, error) {
|
2020-04-20 10:14:12 -06:00
|
|
|
ctx, done := event.Start(ctx, "source.Completion")
|
2019-06-26 20:46:12 -06:00
|
|
|
defer done()
|
2019-07-11 19:05:55 -06:00
|
|
|
|
internal/lsp: limit deep completion search scope
Deep completions can take a long time (500ms+) if there are many
large, deeply nested structs in scope. To make sure we return
completion results in a timely manner we now notice if we have spent
"too long" searching for deep completions and reduce the search scope.
In particular, our overall completion budget is 100ms. This value is
often cited as the longest latency that still feels instantaneous to
most people. As we spend 25%, 50%, and 75% of our budget we limit our
deep completion candidate search depth to 4, 3, and 2,
respectively. If we hit 90% of our budget, we disable deep completions
entirely.
In my testing, limiting the search scope to 4 normally makes even
enormous searches finish in a few milliseconds. Of course, you can
have arbitrarily many objects in scope with arbitrarily many fields,
so to cover our bases we continue to dial down the search depth as
needed.
I replaced the "enabled" field with a "maxDepth" field that disables
deep search when set to 0.
Change-Id: I9b5a07de70709895c065503ae6082d1ea615d1af
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190978
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2019-08-18 13:55:34 -06:00
|
|
|
startTime := time.Now()
|
|
|
|
|
2020-07-22 09:32:32 -06:00
|
|
|
pkg, pgf, err := getParsedFile(ctx, snapshot, fh, NarrowestPackage)
|
2020-08-26 15:54:26 -06:00
|
|
|
if err != nil || pgf.File.Package == token.NoPos {
|
|
|
|
// If we can't parse this file or find position for the package
|
|
|
|
// keyword, it may be missing a package declaration. Try offering
|
|
|
|
// suggestions for the package declaration.
|
|
|
|
// Note that this would be the case even if the keyword 'package' is
|
|
|
|
// present but no package name exists.
|
|
|
|
items, surrounding, innerErr := packageClauseCompletions(ctx, snapshot, fh, protoPos)
|
|
|
|
if innerErr != nil {
|
|
|
|
// return the error for getParsedFile since it's more relevant in this situation.
|
|
|
|
return nil, nil, errors.Errorf("getting file for Completion: %w", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
return items, surrounding, nil
|
2019-08-16 15:05:40 -06:00
|
|
|
}
|
2020-07-21 13:15:06 -06:00
|
|
|
spn, err := pgf.Mapper.PointSpan(protoPos)
|
2019-09-17 09:19:11 -06:00
|
|
|
if err != nil {
|
2019-07-11 19:05:55 -06:00
|
|
|
return nil, nil, err
|
2019-05-20 13:23:02 -06:00
|
|
|
}
|
2020-07-21 13:15:06 -06:00
|
|
|
rng, err := spn.Range(pgf.Mapper.Converter)
|
2019-07-09 15:52:23 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
2019-03-11 15:14:55 -06:00
|
|
|
}
|
2019-04-23 16:00:40 -06:00
|
|
|
// Completion is based on what precedes the cursor.
|
2019-04-24 17:26:34 -06:00
|
|
|
// Find the path to the position before pos.
|
2020-07-21 13:15:06 -06:00
|
|
|
path, _ := astutil.PathEnclosingInterval(pgf.File, rng.Start-1, rng.Start-1)
|
2018-11-07 18:57:08 -07:00
|
|
|
if path == nil {
|
2019-08-06 13:13:11 -06:00
|
|
|
return nil, nil, errors.Errorf("cannot find node enclosing position")
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
2019-12-10 14:40:37 -07:00
|
|
|
|
2020-03-05 21:34:50 -07:00
|
|
|
pos := rng.Start
|
|
|
|
|
2020-05-06 20:26:25 -06:00
|
|
|
// Check if completion at this position is valid. If not, return early.
|
2020-03-05 21:34:50 -07:00
|
|
|
switch n := path[0].(type) {
|
|
|
|
case *ast.BasicLit:
|
2020-08-20 16:49:00 -06:00
|
|
|
// Skip completion inside literals except for ImportSpec
|
|
|
|
if len(path) > 1 {
|
|
|
|
if _, ok := path[1].(*ast.ImportSpec); ok {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2019-05-17 11:45:59 -06:00
|
|
|
return nil, nil, nil
|
2020-03-05 21:34:50 -07:00
|
|
|
case *ast.CallExpr:
|
|
|
|
if n.Ellipsis.IsValid() && pos > n.Ellipsis && pos <= n.Ellipsis+token.Pos(len("...")) {
|
|
|
|
// Don't offer completions inside or directly after "...". For
|
|
|
|
// example, don't offer completions at "<>" in "foo(bar...<>").
|
|
|
|
return nil, nil, nil
|
|
|
|
}
|
2020-05-06 20:26:25 -06:00
|
|
|
case *ast.Ident:
|
|
|
|
// reject defining identifiers
|
|
|
|
if obj, ok := pkg.GetTypesInfo().Defs[n]; ok {
|
|
|
|
if v, ok := obj.(*types.Var); ok && v.IsField() && v.Embedded() {
|
|
|
|
// An anonymous field is also a reference to a type.
|
2020-08-26 15:54:26 -06:00
|
|
|
} else if pgf.File.Name == n {
|
|
|
|
// Don't skip completions if Ident is for package name.
|
|
|
|
break
|
2020-05-06 20:26:25 -06:00
|
|
|
} else {
|
|
|
|
objStr := ""
|
|
|
|
if obj != nil {
|
|
|
|
qual := types.RelativeTo(pkg.GetTypes())
|
|
|
|
objStr = types.ObjectString(obj, qual)
|
|
|
|
}
|
|
|
|
return nil, nil, ErrIsDefinition{objStr: objStr}
|
|
|
|
}
|
|
|
|
}
|
2019-01-15 23:36:31 -07:00
|
|
|
}
|
|
|
|
|
2019-12-29 00:22:12 -07:00
|
|
|
opts := snapshot.View().Options()
|
2019-04-24 17:26:34 -06:00
|
|
|
c := &completer{
|
2019-09-09 18:22:42 -06:00
|
|
|
pkg: pkg,
|
2019-09-27 11:17:59 -06:00
|
|
|
snapshot: snapshot,
|
2020-07-21 13:15:06 -06:00
|
|
|
qf: qualifier(pgf.File, pkg.GetTypes(), pkg.GetTypesInfo()),
|
2020-08-24 17:49:55 -06:00
|
|
|
triggerCharacter: triggerCharacter,
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
filename: fh.URI().Filename(),
|
2020-07-21 13:15:06 -06:00
|
|
|
file: pgf.File,
|
2019-04-28 21:19:54 -06:00
|
|
|
path: path,
|
2020-03-05 21:34:50 -07:00
|
|
|
pos: pos,
|
2019-04-28 21:19:54 -06:00
|
|
|
seen: make(map[types.Object]bool),
|
2020-03-27 10:52:40 -06:00
|
|
|
enclosingFunc: enclosingFunction(path, pkg.GetTypesInfo()),
|
2019-09-18 13:26:39 -06:00
|
|
|
enclosingCompositeLiteral: enclosingCompositeLiteral(path, rng.Start, pkg.GetTypesInfo()),
|
2019-12-29 00:22:12 -07:00
|
|
|
opts: &completionOptions{
|
|
|
|
matcher: opts.Matcher,
|
|
|
|
deepCompletion: opts.DeepCompletion,
|
|
|
|
unimported: opts.UnimportedCompletion,
|
|
|
|
documentation: opts.CompletionDocumentation,
|
|
|
|
fullDocumentation: opts.HoverKind == FullDocumentation,
|
|
|
|
placeholders: opts.Placeholders,
|
2020-03-01 15:33:21 -07:00
|
|
|
literal: opts.LiteralCompletions && opts.InsertTextFormat == protocol.SnippetTextFormat,
|
2019-12-29 00:22:12 -07:00
|
|
|
budget: opts.CompletionBudget,
|
|
|
|
},
|
2019-08-16 10:45:09 -06:00
|
|
|
// default to a matcher that always matches
|
|
|
|
matcher: prefixMatcher(""),
|
|
|
|
methodSetCache: make(map[methodSetKey]*types.MethodSet),
|
2020-07-21 13:15:06 -06:00
|
|
|
mapper: pgf.Mapper,
|
internal/lsp: limit deep completion search scope
Deep completions can take a long time (500ms+) if there are many
large, deeply nested structs in scope. To make sure we return
completion results in a timely manner we now notice if we have spent
"too long" searching for deep completions and reduce the search scope.
In particular, our overall completion budget is 100ms. This value is
often cited as the longest latency that still feels instantaneous to
most people. As we spend 25%, 50%, and 75% of our budget we limit our
deep completion candidate search depth to 4, 3, and 2,
respectively. If we hit 90% of our budget, we disable deep completions
entirely.
In my testing, limiting the search scope to 4 normally makes even
enormous searches finish in a few milliseconds. Of course, you can
have arbitrarily many objects in scope with arbitrarily many fields,
so to cover our bases we continue to dial down the search depth as
needed.
I replaced the "enabled" field with a "maxDepth" field that disables
deep search when set to 0.
Change-Id: I9b5a07de70709895c065503ae6082d1ea615d1af
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190978
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2019-08-18 13:55:34 -06:00
|
|
|
startTime: startTime,
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
2019-01-31 23:40:03 -07:00
|
|
|
|
2019-12-29 00:22:12 -07:00
|
|
|
if c.opts.deepCompletion {
|
2019-08-27 17:41:48 -06:00
|
|
|
// Initialize max search depth to unlimited.
|
internal/lsp: limit deep completion search scope
Deep completions can take a long time (500ms+) if there are many
large, deeply nested structs in scope. To make sure we return
completion results in a timely manner we now notice if we have spent
"too long" searching for deep completions and reduce the search scope.
In particular, our overall completion budget is 100ms. This value is
often cited as the longest latency that still feels instantaneous to
most people. As we spend 25%, 50%, and 75% of our budget we limit our
deep completion candidate search depth to 4, 3, and 2,
respectively. If we hit 90% of our budget, we disable deep completions
entirely.
In my testing, limiting the search scope to 4 normally makes even
enormous searches finish in a few milliseconds. Of course, you can
have arbitrarily many objects in scope with arbitrarily many fields,
so to cover our bases we continue to dial down the search depth as
needed.
I replaced the "enabled" field with a "maxDepth" field that disables
deep search when set to 0.
Change-Id: I9b5a07de70709895c065503ae6082d1ea615d1af
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190978
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2019-08-18 13:55:34 -06:00
|
|
|
c.deepState.maxDepth = -1
|
|
|
|
}
|
2019-06-27 11:50:01 -06:00
|
|
|
|
2020-04-05 23:18:15 -06:00
|
|
|
var cancel context.CancelFunc
|
|
|
|
if c.opts.budget == 0 {
|
|
|
|
ctx, cancel = context.WithCancel(ctx)
|
|
|
|
} else {
|
|
|
|
ctx, cancel = context.WithDeadline(ctx, c.startTime.Add(c.opts.budget))
|
|
|
|
}
|
|
|
|
defer cancel()
|
|
|
|
|
2020-07-21 13:15:06 -06:00
|
|
|
if surrounding := c.containingIdent(pgf.Src); surrounding != nil {
|
2020-02-22 19:56:15 -07:00
|
|
|
c.setSurrounding(surrounding)
|
2019-05-13 15:37:08 -06:00
|
|
|
}
|
2019-05-10 10:26:15 -06:00
|
|
|
|
2020-04-05 23:18:15 -06:00
|
|
|
c.inference = expectedCandidate(ctx, c)
|
2019-04-24 17:26:34 -06:00
|
|
|
|
2020-01-16 16:00:13 -07:00
|
|
|
defer c.sortItems()
|
|
|
|
|
2020-08-20 16:49:00 -06:00
|
|
|
// Inside import blocks, return completions for unimported packages.
|
|
|
|
for _, importSpec := range pgf.File.Imports {
|
|
|
|
if !(importSpec.Path.Pos() <= rng.Start && rng.Start <= importSpec.Path.End()) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err := c.populateImportCompletions(ctx, importSpec); err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return c.items, c.getSurrounding(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Inside comments, offer completions for the name of the relevant symbol.
|
2020-07-21 13:15:06 -06:00
|
|
|
for _, comment := range pgf.File.Comments {
|
2020-02-02 22:21:07 -07:00
|
|
|
if comment.Pos() < rng.Start && rng.Start <= comment.End() {
|
2020-08-19 16:48:34 -06:00
|
|
|
// deep completion doesn't work properly in comments since we don't
|
|
|
|
// have a type object to complete further
|
|
|
|
c.deepState.maxDepth = 0
|
2020-04-05 23:18:15 -06:00
|
|
|
c.populateCommentCompletions(ctx, comment)
|
2019-12-10 14:40:37 -07:00
|
|
|
return c.items, c.getSurrounding(), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-13 15:37:08 -06:00
|
|
|
// Struct literals are handled entirely separately.
|
|
|
|
if c.wantStructFieldCompletions() {
|
2020-04-05 23:18:15 -06:00
|
|
|
if err := c.structLiteralFieldName(ctx); err != nil {
|
2019-05-17 11:45:59 -06:00
|
|
|
return nil, nil, err
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
2019-08-16 15:05:40 -06:00
|
|
|
return c.items, c.getSurrounding(), nil
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
|
2019-09-18 13:26:39 -06:00
|
|
|
if lt := c.wantLabelCompletion(); lt != labelNone {
|
2020-04-05 23:18:15 -06:00
|
|
|
c.labels(ctx, lt)
|
2019-09-18 13:26:39 -06:00
|
|
|
return c.items, c.getSurrounding(), nil
|
|
|
|
}
|
|
|
|
|
2020-02-22 19:56:15 -07:00
|
|
|
if c.emptySwitchStmt() {
|
|
|
|
// Empty switch statements only admit "default" and "case" keywords.
|
|
|
|
c.addKeywordItems(map[string]bool{}, highScore, CASE, DEFAULT)
|
|
|
|
return c.items, c.getSurrounding(), nil
|
|
|
|
}
|
|
|
|
|
2018-11-07 18:57:08 -07:00
|
|
|
switch n := path[0].(type) {
|
|
|
|
case *ast.Ident:
|
2020-08-26 15:54:26 -06:00
|
|
|
if pgf.File.Name == n {
|
|
|
|
if err := c.packageNameCompletions(ctx, fh.URI(), n); err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return c.items, c.getSurrounding(), nil
|
|
|
|
} else if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == n {
|
|
|
|
// Is this the Sel part of a selector?
|
2020-04-05 23:18:15 -06:00
|
|
|
if err := c.selector(ctx, sel); err != nil {
|
2019-05-17 11:45:59 -06:00
|
|
|
return nil, nil, err
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
2020-03-01 15:33:21 -07:00
|
|
|
} else if err := c.lexical(ctx); err != nil {
|
2019-05-17 11:45:59 -06:00
|
|
|
return nil, nil, err
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
2018-11-07 18:57:08 -07:00
|
|
|
// The function name hasn't been typed yet, but the parens are there:
|
|
|
|
// recv.‸(arg)
|
|
|
|
case *ast.TypeAssertExpr:
|
|
|
|
// Create a fake selector expression.
|
2020-04-05 23:18:15 -06:00
|
|
|
if err := c.selector(ctx, &ast.SelectorExpr{X: n.X}); err != nil {
|
2019-05-17 11:45:59 -06:00
|
|
|
return nil, nil, err
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
2018-11-07 18:57:08 -07:00
|
|
|
|
|
|
|
case *ast.SelectorExpr:
|
2020-04-05 23:18:15 -06:00
|
|
|
if err := c.selector(ctx, n); err != nil {
|
2019-05-17 11:45:59 -06:00
|
|
|
return nil, nil, err
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
2018-11-07 18:57:08 -07:00
|
|
|
|
2020-02-02 22:21:07 -07:00
|
|
|
// At the file scope, only keywords are allowed.
|
|
|
|
case *ast.BadDecl, *ast.File:
|
|
|
|
c.addKeywordCompletions()
|
|
|
|
|
2018-11-07 18:57:08 -07:00
|
|
|
default:
|
|
|
|
// fallback to lexical completions
|
2020-04-05 23:18:15 -06:00
|
|
|
if err := c.lexical(ctx); err != nil {
|
2019-05-17 11:45:59 -06:00
|
|
|
return nil, nil, err
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
2019-05-13 15:37:08 -06:00
|
|
|
|
2020-03-01 15:33:21 -07:00
|
|
|
// Statement candidates offer an entire statement in certain
|
|
|
|
// contexts, as opposed to a single object. Add statement candidates
|
|
|
|
// last because they depend on other candidates having already been
|
|
|
|
// collected.
|
|
|
|
c.addStatementCandidates()
|
|
|
|
|
2019-08-16 15:05:40 -06:00
|
|
|
return c.items, c.getSurrounding(), nil
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
|
2020-02-22 19:56:15 -07:00
|
|
|
// containingIdent returns the *ast.Ident containing pos, if any. It
|
|
|
|
// synthesizes an *ast.Ident to allow completion in the face of
|
|
|
|
// certain syntax errors.
|
|
|
|
func (c *completer) containingIdent(src []byte) *ast.Ident {
|
|
|
|
// In the normal case, our leaf AST node is the identifer being completed.
|
|
|
|
if ident, ok := c.path[0].(*ast.Ident); ok {
|
|
|
|
return ident
|
|
|
|
}
|
|
|
|
|
|
|
|
pos, tkn, lit := c.scanToken(src)
|
|
|
|
if !pos.IsValid() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
fakeIdent := &ast.Ident{Name: lit, NamePos: pos}
|
|
|
|
|
|
|
|
if _, isBadDecl := c.path[0].(*ast.BadDecl); isBadDecl {
|
|
|
|
// You don't get *ast.Idents at the file level, so look for bad
|
|
|
|
// decls and use the manually extracted token.
|
|
|
|
return fakeIdent
|
|
|
|
} else if c.emptySwitchStmt() {
|
|
|
|
// Only keywords are allowed in empty switch statements.
|
|
|
|
// *ast.Idents are not parsed, so we must use the manually
|
|
|
|
// extracted token.
|
|
|
|
return fakeIdent
|
|
|
|
} else if tkn.IsKeyword() {
|
|
|
|
// Otherwise, manually extract the prefix if our containing token
|
|
|
|
// is a keyword. This improves completion after an "accidental
|
|
|
|
// keyword", e.g. completing to "variance" in "someFunc(var<>)".
|
|
|
|
return fakeIdent
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-02-02 22:21:07 -07:00
|
|
|
// scanToken scans pgh's contents for the token containing pos.
|
2020-02-22 19:56:15 -07:00
|
|
|
func (c *completer) scanToken(contents []byte) (token.Pos, token.Token, string) {
|
2020-07-28 15:00:10 -06:00
|
|
|
tok := c.snapshot.FileSet().File(c.pos)
|
2020-02-02 22:21:07 -07:00
|
|
|
|
2020-01-29 22:52:22 -07:00
|
|
|
var s scanner.Scanner
|
2020-02-02 22:21:07 -07:00
|
|
|
s.Init(tok, contents, nil, 0)
|
2020-01-29 22:52:22 -07:00
|
|
|
for {
|
|
|
|
tknPos, tkn, lit := s.Scan()
|
2020-02-02 22:21:07 -07:00
|
|
|
if tkn == token.EOF || tknPos >= c.pos {
|
|
|
|
return token.NoPos, token.ILLEGAL, ""
|
2020-01-29 22:52:22 -07:00
|
|
|
}
|
|
|
|
|
2020-02-02 22:21:07 -07:00
|
|
|
if len(lit) > 0 && tknPos <= c.pos && c.pos <= tknPos+token.Pos(len(lit)) {
|
|
|
|
return tknPos, tkn, lit
|
2020-01-29 22:52:22 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-16 16:00:13 -07:00
|
|
|
func (c *completer) sortItems() {
|
|
|
|
sort.SliceStable(c.items, func(i, j int) bool {
|
|
|
|
// Sort by score first.
|
|
|
|
if c.items[i].Score != c.items[j].Score {
|
|
|
|
return c.items[i].Score > c.items[j].Score
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then sort by label so order stays consistent. This also has the
|
|
|
|
// effect of prefering shorter candidates.
|
|
|
|
return c.items[i].Label < c.items[j].Label
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-02-22 19:56:15 -07:00
|
|
|
// emptySwitchStmt reports whether pos is in an empty switch or select
|
|
|
|
// statement.
|
|
|
|
func (c *completer) emptySwitchStmt() bool {
|
|
|
|
block, ok := c.path[0].(*ast.BlockStmt)
|
|
|
|
if !ok || len(block.List) > 0 || len(c.path) == 1 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
switch c.path[1].(type) {
|
|
|
|
case *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-20 16:49:00 -06:00
|
|
|
// populateImportCompletions yields completions for an import path around the cursor.
|
|
|
|
//
|
|
|
|
// Completions are suggested at the directory depth of the given import path so
|
|
|
|
// that we don't overwhelm the user with a large list of possibilities. As an
|
|
|
|
// example, a completion for the prefix "golang" results in "golang.org/".
|
|
|
|
// Completions for "golang.org/" yield its subdirectories
|
|
|
|
// (i.e. "golang.org/x/"). The user is meant to accept completion suggestions
|
|
|
|
// until they reach a complete import path.
|
|
|
|
func (c *completer) populateImportCompletions(ctx context.Context, searchImport *ast.ImportSpec) error {
|
|
|
|
c.surrounding = &Selection{
|
|
|
|
content: searchImport.Path.Value,
|
|
|
|
cursor: c.pos,
|
|
|
|
mappedRange: newMappedRange(c.snapshot.FileSet(), c.mapper, searchImport.Path.Pos(), searchImport.Path.End()),
|
|
|
|
}
|
|
|
|
|
|
|
|
seenImports := make(map[string]struct{})
|
|
|
|
for _, importSpec := range c.file.Imports {
|
|
|
|
if importSpec.Path.Value == searchImport.Path.Value {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
importPath, err := strconv.Unquote(importSpec.Path.Value)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
seenImports[importPath] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
prefixEnd := c.pos - searchImport.Path.ValuePos
|
|
|
|
// Extract the text between the quotes (if any) in an import spec.
|
|
|
|
// prefix is the part of import path before the cursor.
|
|
|
|
prefix := strings.Trim(searchImport.Path.Value[:prefixEnd], `"`)
|
|
|
|
|
|
|
|
// The number of directories in the import path gives us the depth at
|
|
|
|
// which to search.
|
|
|
|
depth := len(strings.Split(prefix, "/")) - 1
|
|
|
|
|
|
|
|
var mu sync.Mutex // guard c.items locally, since searchImports is called in parallel
|
|
|
|
seen := make(map[string]struct{})
|
|
|
|
searchImports := func(pkg imports.ImportFix) {
|
|
|
|
path := pkg.StmtInfo.ImportPath
|
|
|
|
if _, ok := seenImports[path]; ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Any package path containing fewer directories than the search
|
|
|
|
// prefix is not a match.
|
|
|
|
pkgDirList := strings.Split(path, "/")
|
|
|
|
if len(pkgDirList) < depth+1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
pkgToConsider := strings.Join(pkgDirList[:depth+1], "/")
|
|
|
|
|
|
|
|
score := float64(pkg.Relevance)
|
|
|
|
if len(pkgDirList)-1 == depth {
|
|
|
|
score *= highScore
|
|
|
|
} else {
|
|
|
|
// For incomplete package paths, add a terminal slash to indicate that the
|
|
|
|
// user should keep triggering completions.
|
|
|
|
pkgToConsider += "/"
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := seen[pkgToConsider]; ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
seen[pkgToConsider] = struct{}{}
|
|
|
|
|
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
|
|
|
|
|
|
|
obj := types.NewPkgName(0, nil, pkg.IdentName, types.NewPackage(pkgToConsider, pkg.IdentName))
|
|
|
|
// Running goimports logic in completions is expensive, and the
|
|
|
|
// (*completer).found method imposes a 100ms budget. Work-around this
|
|
|
|
// by adding to c.items directly.
|
|
|
|
cand := candidate{obj: obj, name: `"` + pkgToConsider + `"`, score: score}
|
|
|
|
if item, err := c.item(ctx, cand); err == nil {
|
|
|
|
c.items = append(c.items, item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.snapshot.View().RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
|
|
|
|
return imports.GetImportPaths(ctx, searchImports, prefix, c.filename, c.pkg.GetTypes().Name(), opts.Env)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// populateCommentCompletions yields completions for comments preceding or in declarations.
|
2020-04-05 23:18:15 -06:00
|
|
|
func (c *completer) populateCommentCompletions(ctx context.Context, comment *ast.CommentGroup) {
|
2020-08-24 17:49:55 -06:00
|
|
|
// If the completion was triggered by a period, ignore it. These types of
|
|
|
|
// completions will not be useful in comments.
|
|
|
|
if c.triggerCharacter == "." {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-12-10 14:40:37 -07:00
|
|
|
// Using the comment position find the line after
|
2020-07-28 15:00:10 -06:00
|
|
|
file := c.snapshot.FileSet().File(comment.End())
|
2019-12-10 14:40:37 -07:00
|
|
|
if file == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-08-19 16:48:34 -06:00
|
|
|
commentLine := file.Line(comment.End())
|
2019-12-10 14:40:37 -07:00
|
|
|
|
2020-07-31 11:11:32 -06:00
|
|
|
// comment is valid, set surrounding as word boundaries around cursor
|
|
|
|
c.setSurroundingForComment(comment)
|
|
|
|
|
2020-05-02 00:57:12 -06:00
|
|
|
// Using the next line pos, grab and parse the exported symbol on that line
|
2019-12-10 14:40:37 -07:00
|
|
|
for _, n := range c.file.Decls {
|
2020-08-19 16:48:34 -06:00
|
|
|
declLine := file.Line(n.Pos())
|
|
|
|
// if the comment is not in, directly above or on the same line as a declaration
|
|
|
|
if declLine != commentLine && declLine != commentLine+1 &&
|
|
|
|
!(n.Pos() <= comment.Pos() && comment.End() <= n.End()) {
|
2019-12-10 14:40:37 -07:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
switch node := n.(type) {
|
2020-05-02 00:57:12 -06:00
|
|
|
// handle const, vars, and types
|
2019-12-10 14:40:37 -07:00
|
|
|
case *ast.GenDecl:
|
|
|
|
for _, spec := range node.Specs {
|
2020-05-28 18:38:19 -06:00
|
|
|
switch spec := spec.(type) {
|
2020-05-02 00:57:12 -06:00
|
|
|
case *ast.ValueSpec:
|
2020-05-28 18:38:19 -06:00
|
|
|
for _, name := range spec.Names {
|
2020-08-21 16:22:36 -06:00
|
|
|
if name.String() == "_" {
|
2019-12-10 14:40:37 -07:00
|
|
|
continue
|
|
|
|
}
|
2020-05-02 00:57:12 -06:00
|
|
|
obj := c.pkg.GetTypesInfo().ObjectOf(name)
|
|
|
|
c.found(ctx, candidate{obj: obj, score: stdScore})
|
|
|
|
}
|
|
|
|
case *ast.TypeSpec:
|
2020-08-19 16:48:34 -06:00
|
|
|
// add TypeSpec fields to completion
|
|
|
|
switch typeNode := spec.Type.(type) {
|
|
|
|
case *ast.StructType:
|
|
|
|
c.addFieldItems(ctx, typeNode.Fields)
|
|
|
|
case *ast.FuncType:
|
|
|
|
c.addFieldItems(ctx, typeNode.Params)
|
|
|
|
c.addFieldItems(ctx, typeNode.Results)
|
|
|
|
case *ast.InterfaceType:
|
|
|
|
c.addFieldItems(ctx, typeNode.Methods)
|
|
|
|
}
|
|
|
|
|
2020-08-21 16:22:36 -06:00
|
|
|
if spec.Name.String() == "_" {
|
2020-05-02 00:57:12 -06:00
|
|
|
continue
|
2019-12-10 14:40:37 -07:00
|
|
|
}
|
2020-08-19 16:48:34 -06:00
|
|
|
|
2020-05-28 18:38:19 -06:00
|
|
|
obj := c.pkg.GetTypesInfo().ObjectOf(spec.Name)
|
2020-08-19 16:48:34 -06:00
|
|
|
// Type name should get a higher score than fields but not highScore by default
|
|
|
|
// since field near a comment cursor gets a highScore
|
|
|
|
score := stdScore * 1.1
|
|
|
|
// If type declaration is on the line after comment, give it a highScore.
|
|
|
|
if declLine == commentLine+1 {
|
|
|
|
score = highScore
|
|
|
|
}
|
|
|
|
|
|
|
|
// we use c.item in addFieldItems so we have to use c.item here to ensure scoring
|
|
|
|
// order is maintained. c.found manipulates the score
|
|
|
|
if item, err := c.item(ctx, candidate{obj: obj, name: obj.Name(), score: score}); err == nil {
|
|
|
|
c.items = append(c.items, item)
|
|
|
|
}
|
2019-12-10 14:40:37 -07:00
|
|
|
}
|
|
|
|
}
|
2020-05-02 00:57:12 -06:00
|
|
|
// handle functions
|
|
|
|
case *ast.FuncDecl:
|
2020-08-19 16:48:34 -06:00
|
|
|
c.addFieldItems(ctx, node.Recv)
|
|
|
|
c.addFieldItems(ctx, node.Type.Params)
|
|
|
|
c.addFieldItems(ctx, node.Type.Results)
|
|
|
|
|
|
|
|
// collect receiver struct fields
|
|
|
|
if node.Recv != nil {
|
|
|
|
for _, fields := range node.Recv.List {
|
|
|
|
for _, name := range fields.Names {
|
|
|
|
obj := c.pkg.GetTypesInfo().ObjectOf(name)
|
|
|
|
if obj == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
recvType := obj.Type().Underlying()
|
|
|
|
if ptr, ok := recvType.(*types.Pointer); ok {
|
|
|
|
recvType = ptr.Elem()
|
|
|
|
}
|
|
|
|
recvStruct, ok := recvType.Underlying().(*types.Struct)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for i := 0; i < recvStruct.NumFields(); i++ {
|
|
|
|
field := recvStruct.Field(i)
|
|
|
|
// we use c.item in addFieldItems so we have to use c.item here to ensure scoring
|
|
|
|
// order is maintained. c.found maniplulates the score
|
|
|
|
item, err := c.item(ctx, candidate{obj: field, name: field.Name(), score: lowScore})
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
c.items = append(c.items, item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-21 16:22:36 -06:00
|
|
|
if node.Name.String() == "_" {
|
2020-05-02 00:57:12 -06:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
obj := c.pkg.GetTypesInfo().ObjectOf(node.Name)
|
2020-07-05 06:03:18 -06:00
|
|
|
if obj == nil || obj.Pkg() != nil && obj.Pkg() != c.pkg.GetTypes() {
|
|
|
|
continue
|
|
|
|
}
|
2020-05-02 00:57:12 -06:00
|
|
|
|
2020-08-19 16:48:34 -06:00
|
|
|
// We don't want to expandFuncCall inside comments.
|
|
|
|
// c.found() doesn't respect this setting
|
2020-05-02 00:57:12 -06:00
|
|
|
item, err := c.item(ctx, candidate{
|
|
|
|
obj: obj,
|
|
|
|
name: obj.Name(),
|
|
|
|
expandFuncCall: false,
|
2020-08-19 16:48:34 -06:00
|
|
|
score: highScore,
|
2020-05-02 00:57:12 -06:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
c.items = append(c.items, item)
|
2019-12-10 14:40:37 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-31 11:11:32 -06:00
|
|
|
// sets word boundaries surrounding a cursor for a comment
|
|
|
|
func (c *completer) setSurroundingForComment(comments *ast.CommentGroup) {
|
|
|
|
var cursorComment *ast.Comment
|
|
|
|
for _, comment := range comments.List {
|
|
|
|
if c.pos >= comment.Pos() && c.pos <= comment.End() {
|
|
|
|
cursorComment = comment
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if cursor isn't in the comment
|
|
|
|
if cursorComment == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// index of cursor in comment text
|
|
|
|
cursorOffset := int(c.pos - cursorComment.Pos())
|
|
|
|
start, end := cursorOffset, cursorOffset
|
|
|
|
for start > 0 && isValidIdentifierChar(cursorComment.Text[start-1]) {
|
|
|
|
start--
|
|
|
|
}
|
|
|
|
for end < len(cursorComment.Text) && isValidIdentifierChar(cursorComment.Text[end]) {
|
|
|
|
end++
|
|
|
|
}
|
|
|
|
|
|
|
|
c.surrounding = &Selection{
|
|
|
|
content: cursorComment.Text[start:end],
|
|
|
|
cursor: c.pos,
|
2020-07-28 15:00:10 -06:00
|
|
|
mappedRange: newMappedRange(c.snapshot.FileSet(), c.mapper,
|
2020-07-31 11:11:32 -06:00
|
|
|
token.Pos(int(cursorComment.Slash)+start), token.Pos(int(cursorComment.Slash)+end)),
|
|
|
|
}
|
2020-08-21 16:22:36 -06:00
|
|
|
c.setMatcherFromPrefix(c.surrounding.Prefix())
|
2020-07-31 11:11:32 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// isValidIdentifierChar returns true if a byte is a valid go identifier character
|
|
|
|
// i.e unicode letter or digit or undescore
|
|
|
|
func isValidIdentifierChar(char byte) bool {
|
|
|
|
charRune := rune(char)
|
|
|
|
return unicode.In(charRune, unicode.Letter, unicode.Digit) || char == '_'
|
|
|
|
}
|
|
|
|
|
2020-08-19 16:48:34 -06:00
|
|
|
// adds struct fields, interface methods, function declaration fields to completion
|
|
|
|
func (c *completer) addFieldItems(ctx context.Context, fields *ast.FieldList) {
|
|
|
|
if fields == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor := c.surrounding.cursor
|
|
|
|
for _, field := range fields.List {
|
|
|
|
for _, name := range field.Names {
|
2020-08-21 16:22:36 -06:00
|
|
|
if name.String() == "_" {
|
2020-08-19 16:48:34 -06:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
obj := c.pkg.GetTypesInfo().ObjectOf(name)
|
|
|
|
|
|
|
|
// if we're in a field comment/doc, score that field as more relevant
|
|
|
|
score := stdScore
|
|
|
|
if field.Comment != nil && field.Comment.Pos() <= cursor && cursor <= field.Comment.End() {
|
|
|
|
score = highScore
|
|
|
|
} else if field.Doc != nil && field.Doc.Pos() <= cursor && cursor <= field.Doc.End() {
|
|
|
|
score = highScore
|
|
|
|
}
|
|
|
|
|
|
|
|
cand := candidate{
|
|
|
|
obj: obj,
|
|
|
|
name: obj.Name(),
|
|
|
|
expandFuncCall: false,
|
|
|
|
score: score,
|
|
|
|
}
|
|
|
|
// We don't want to expandFuncCall inside comments.
|
|
|
|
// c.found() doesn't respect this setting
|
|
|
|
if item, err := c.item(ctx, cand); err == nil {
|
|
|
|
c.items = append(c.items, item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-13 15:37:08 -06:00
|
|
|
func (c *completer) wantStructFieldCompletions() bool {
|
|
|
|
clInfo := c.enclosingCompositeLiteral
|
|
|
|
if clInfo == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return clInfo.isStruct() && (clInfo.inKey || clInfo.maybeInFieldName)
|
|
|
|
}
|
|
|
|
|
2019-06-17 12:11:13 -06:00
|
|
|
func (c *completer) wantTypeName() bool {
|
2020-01-17 14:53:10 -07:00
|
|
|
return c.inference.typeName.wantTypeName
|
2019-06-17 12:11:13 -06:00
|
|
|
}
|
|
|
|
|
2019-12-05 16:08:19 -07:00
|
|
|
// See https://golang.org/issue/36001. Unimported completions are expensive.
|
2020-02-10 12:54:59 -07:00
|
|
|
const (
|
|
|
|
maxUnimportedPackageNames = 5
|
|
|
|
unimportedMemberTarget = 100
|
|
|
|
)
|
2019-12-05 16:08:19 -07:00
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
// selector finds completions for the specified selector expression.
|
2020-04-05 23:18:15 -06:00
|
|
|
func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error {
|
2020-08-06 23:33:47 -06:00
|
|
|
c.inference.objChain = objChain(c.pkg.GetTypesInfo(), sel.X)
|
|
|
|
|
2018-11-07 18:57:08 -07:00
|
|
|
// Is sel a qualified identifier?
|
|
|
|
if id, ok := sel.X.(*ast.Ident); ok {
|
2020-02-10 12:54:59 -07:00
|
|
|
if pkgName, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok {
|
2020-04-05 23:18:15 -06:00
|
|
|
c.packageMembers(ctx, pkgName.Imported(), stdScore, nil)
|
2019-04-24 17:26:34 -06:00
|
|
|
return nil
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
// Invariant: sel is a true selector.
|
2019-09-09 18:22:42 -06:00
|
|
|
tv, ok := c.pkg.GetTypesInfo().Types[sel.X]
|
2019-11-01 11:50:21 -06:00
|
|
|
if ok {
|
2020-04-05 23:18:15 -06:00
|
|
|
return c.methodsAndFields(ctx, tv.Type, tv.Addressable(), nil)
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
|
2019-11-01 11:50:21 -06:00
|
|
|
// Try unimported packages.
|
2020-02-10 12:54:59 -07:00
|
|
|
if id, ok := sel.X.(*ast.Ident); ok && c.opts.unimported {
|
2020-04-05 23:18:15 -06:00
|
|
|
if err := c.unimportedMembers(ctx, id); err != nil {
|
2019-12-30 11:29:58 -07:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-05 23:18:15 -06:00
|
|
|
func (c *completer) unimportedMembers(ctx context.Context, id *ast.Ident) error {
|
2019-12-30 11:29:58 -07:00
|
|
|
// Try loaded packages first. They're relevant, fast, and fully typed.
|
2020-04-05 23:18:15 -06:00
|
|
|
known, err := c.snapshot.CachedImportPaths(ctx)
|
2020-01-10 15:18:59 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-02-10 12:54:59 -07:00
|
|
|
|
2020-01-16 12:54:35 -07:00
|
|
|
var paths []string
|
|
|
|
for path, pkg := range known {
|
|
|
|
if pkg.GetTypes().Name() != id.Name {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
paths = append(paths, path)
|
|
|
|
}
|
2020-02-10 12:54:59 -07:00
|
|
|
|
2020-01-16 12:54:35 -07:00
|
|
|
var relevances map[string]int
|
|
|
|
if len(paths) != 0 {
|
2020-07-06 13:18:50 -06:00
|
|
|
if err := c.snapshot.View().RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
|
|
|
|
var err error
|
|
|
|
relevances, err = imports.ScoreImportPaths(ctx, opts.Env, paths)
|
|
|
|
return err
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-16 12:54:35 -07:00
|
|
|
}
|
2020-05-01 13:24:44 -06:00
|
|
|
sort.Slice(paths, func(i, j int) bool {
|
|
|
|
return relevances[paths[i]] > relevances[paths[j]]
|
|
|
|
})
|
2020-02-10 12:54:59 -07:00
|
|
|
|
2020-05-01 13:24:44 -06:00
|
|
|
for _, path := range paths {
|
2020-03-31 11:19:04 -06:00
|
|
|
pkg := known[path]
|
2019-12-30 11:29:58 -07:00
|
|
|
if pkg.GetTypes().Name() != id.Name {
|
|
|
|
continue
|
|
|
|
}
|
2020-01-03 16:46:37 -07:00
|
|
|
imp := &importInfo{
|
2019-12-30 11:29:58 -07:00
|
|
|
importPath: path,
|
|
|
|
pkg: pkg,
|
2020-01-03 16:46:37 -07:00
|
|
|
}
|
|
|
|
if imports.ImportPathToAssumedName(path) != pkg.GetTypes().Name() {
|
|
|
|
imp.name = pkg.GetTypes().Name()
|
|
|
|
}
|
2020-05-01 13:24:44 -06:00
|
|
|
c.packageMembers(ctx, pkg.GetTypes(), unimportedScore(relevances[path]), imp)
|
2020-02-10 12:54:59 -07:00
|
|
|
if len(c.items) >= unimportedMemberTarget {
|
2019-12-30 11:29:58 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-05 23:18:15 -06:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
2019-12-30 11:29:58 -07:00
|
|
|
defer cancel()
|
2020-04-05 23:18:15 -06:00
|
|
|
|
2019-12-30 11:29:58 -07:00
|
|
|
var mu sync.Mutex
|
|
|
|
add := func(pkgExport imports.PackageExport) {
|
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
|
|
|
if _, ok := known[pkgExport.Fix.StmtInfo.ImportPath]; ok {
|
|
|
|
return // We got this one above.
|
|
|
|
}
|
|
|
|
|
|
|
|
// Continue with untyped proposals.
|
|
|
|
pkg := types.NewPackage(pkgExport.Fix.StmtInfo.ImportPath, pkgExport.Fix.IdentName)
|
|
|
|
for _, export := range pkgExport.Exports {
|
2020-05-01 12:27:15 -06:00
|
|
|
score := unimportedScore(pkgExport.Fix.Relevance)
|
2020-04-05 23:18:15 -06:00
|
|
|
c.found(ctx, candidate{
|
2019-12-30 11:29:58 -07:00
|
|
|
obj: types.NewVar(0, pkg, export, nil),
|
|
|
|
score: score,
|
|
|
|
imp: &importInfo{
|
2019-11-05 12:53:55 -07:00
|
|
|
importPath: pkgExport.Fix.StmtInfo.ImportPath,
|
|
|
|
name: pkgExport.Fix.StmtInfo.Name,
|
2019-12-30 11:29:58 -07:00
|
|
|
},
|
|
|
|
})
|
2019-11-01 11:50:21 -06:00
|
|
|
}
|
2020-02-10 12:54:59 -07:00
|
|
|
if len(c.items) >= unimportedMemberTarget {
|
2019-12-30 11:29:58 -07:00
|
|
|
cancel()
|
2019-12-27 13:46:49 -07:00
|
|
|
}
|
2019-11-01 11:50:21 -06:00
|
|
|
}
|
2019-12-30 11:29:58 -07:00
|
|
|
return c.snapshot.View().RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
|
2020-07-06 13:18:50 -06:00
|
|
|
return imports.GetPackageExports(ctx, add, id.Name, c.filename, c.pkg.GetTypes().Name(), opts.Env)
|
2019-12-30 11:29:58 -07:00
|
|
|
})
|
2019-06-27 11:50:01 -06:00
|
|
|
}
|
|
|
|
|
2020-05-01 12:27:15 -06:00
|
|
|
// unimportedScore returns a score for an unimported package that is generally
|
|
|
|
// lower than other candidates.
|
|
|
|
func unimportedScore(relevance int) float64 {
|
|
|
|
return (stdScore + .1*float64(relevance)) / 2
|
|
|
|
}
|
|
|
|
|
2020-04-05 23:18:15 -06:00
|
|
|
func (c *completer) packageMembers(ctx context.Context, pkg *types.Package, score float64, imp *importInfo) {
|
2019-10-15 15:42:30 -06:00
|
|
|
scope := pkg.Scope()
|
2019-06-27 11:50:01 -06:00
|
|
|
for _, name := range scope.Names() {
|
2019-12-22 10:58:14 -07:00
|
|
|
obj := scope.Lookup(name)
|
2020-04-05 23:18:15 -06:00
|
|
|
c.found(ctx, candidate{
|
2019-12-22 10:58:14 -07:00
|
|
|
obj: obj,
|
2019-12-30 11:29:58 -07:00
|
|
|
score: score,
|
2019-12-22 10:58:14 -07:00
|
|
|
imp: imp,
|
|
|
|
addressable: isVar(obj),
|
2019-12-21 16:39:02 -07:00
|
|
|
})
|
2019-06-27 11:50:01 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-05 23:18:15 -06:00
|
|
|
func (c *completer) methodsAndFields(ctx context.Context, typ types.Type, addressable bool, imp *importInfo) error {
|
2019-08-16 10:45:09 -06:00
|
|
|
mset := c.methodSetCache[methodSetKey{typ, addressable}]
|
|
|
|
if mset == nil {
|
|
|
|
if addressable && !types.IsInterface(typ) && !isPointer(typ) {
|
|
|
|
// Add methods of *T, which includes methods with receiver T.
|
|
|
|
mset = types.NewMethodSet(types.NewPointer(typ))
|
|
|
|
} else {
|
|
|
|
// Add methods of T.
|
|
|
|
mset = types.NewMethodSet(typ)
|
|
|
|
}
|
|
|
|
c.methodSetCache[methodSetKey{typ, addressable}] = mset
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
|
2019-06-27 11:50:01 -06:00
|
|
|
for i := 0; i < mset.Len(); i++ {
|
2020-04-05 23:18:15 -06:00
|
|
|
c.found(ctx, candidate{
|
2019-12-22 10:58:14 -07:00
|
|
|
obj: mset.At(i).Obj(),
|
|
|
|
score: stdScore,
|
|
|
|
imp: imp,
|
|
|
|
addressable: addressable || isPointer(typ),
|
2019-12-21 16:39:02 -07:00
|
|
|
})
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
// Add fields of T.
|
2020-03-13 13:10:42 -06:00
|
|
|
eachField(typ, func(v *types.Var) {
|
2020-04-05 23:18:15 -06:00
|
|
|
c.found(ctx, candidate{
|
2020-03-13 13:10:42 -06:00
|
|
|
obj: v,
|
2019-12-27 13:45:09 -07:00
|
|
|
score: stdScore - 0.01,
|
2019-12-22 10:58:14 -07:00
|
|
|
imp: imp,
|
|
|
|
addressable: addressable || isPointer(typ),
|
2019-12-21 16:39:02 -07:00
|
|
|
})
|
2020-03-13 13:10:42 -06:00
|
|
|
})
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
return nil
|
2019-01-31 23:40:03 -07:00
|
|
|
}
|
|
|
|
|
2018-11-07 18:57:08 -07:00
|
|
|
// lexical finds completions in the lexical environment.
|
2020-04-05 23:18:15 -06:00
|
|
|
func (c *completer) lexical(ctx context.Context) error {
|
2020-07-23 21:24:36 -06:00
|
|
|
scopes := collectScopes(c.pkg.GetTypesInfo(), c.path, c.pos)
|
2019-09-09 18:22:42 -06:00
|
|
|
scopes = append(scopes, c.pkg.GetTypes().Scope(), types.Universe)
|
2018-11-07 18:57:08 -07:00
|
|
|
|
2019-12-09 11:41:28 -07:00
|
|
|
var (
|
|
|
|
builtinIota = types.Universe.Lookup("iota")
|
|
|
|
builtinNil = types.Universe.Lookup("nil")
|
|
|
|
)
|
2019-09-12 14:21:36 -06:00
|
|
|
|
2019-04-25 15:19:24 -06:00
|
|
|
// Track seen variables to avoid showing completions for shadowed variables.
|
|
|
|
// This works since we look at scopes from innermost to outermost.
|
|
|
|
seen := make(map[string]struct{})
|
|
|
|
|
2018-11-07 18:57:08 -07:00
|
|
|
// Process scopes innermost first.
|
|
|
|
for i, scope := range scopes {
|
|
|
|
if scope == nil {
|
|
|
|
continue
|
|
|
|
}
|
2019-12-22 16:04:15 -07:00
|
|
|
|
|
|
|
Names:
|
2018-11-07 18:57:08 -07:00
|
|
|
for _, name := range scope.Names() {
|
2019-04-24 17:26:34 -06:00
|
|
|
declScope, obj := scope.LookupParent(name, c.pos)
|
2018-11-07 18:57:08 -07:00
|
|
|
if declScope != scope {
|
|
|
|
continue // Name was declared in some enclosing scope, or not at all.
|
|
|
|
}
|
2019-12-22 16:04:15 -07:00
|
|
|
|
2018-11-07 18:57:08 -07:00
|
|
|
// If obj's type is invalid, find the AST node that defines the lexical block
|
|
|
|
// containing the declaration of obj. Don't resolve types for packages.
|
2020-01-27 11:04:02 -07:00
|
|
|
if !isPkgName(obj) && !typeIsValid(obj.Type()) {
|
2018-11-07 18:57:08 -07:00
|
|
|
// Match the scope to its ast.Node. If the scope is the package scope,
|
|
|
|
// use the *ast.File as the starting node.
|
|
|
|
var node ast.Node
|
2019-04-24 17:26:34 -06:00
|
|
|
if i < len(c.path) {
|
|
|
|
node = c.path[i]
|
|
|
|
} else if i == len(c.path) { // use the *ast.File for package scope
|
|
|
|
node = c.path[i-1]
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
if node != nil {
|
2020-07-28 15:00:10 -06:00
|
|
|
if resolved := resolveInvalid(c.snapshot.FileSet(), obj, node, c.pkg.GetTypesInfo()); resolved != nil {
|
2018-11-07 18:57:08 -07:00
|
|
|
obj = resolved
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-22 16:04:15 -07:00
|
|
|
// Don't use LHS of value spec in RHS.
|
2020-03-27 10:52:40 -06:00
|
|
|
if vs := enclosingValueSpec(c.path); vs != nil {
|
2019-12-22 16:04:15 -07:00
|
|
|
for _, ident := range vs.Names {
|
|
|
|
if obj.Pos() == ident.Pos() {
|
|
|
|
continue Names
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-12 14:21:36 -06:00
|
|
|
// Don't suggest "iota" outside of const decls.
|
|
|
|
if obj == builtinIota && !c.inConstDecl() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-12-27 13:45:09 -07:00
|
|
|
// Rank outer scopes lower than inner.
|
|
|
|
score := stdScore * math.Pow(.99, float64(i))
|
2019-12-09 11:41:28 -07:00
|
|
|
|
|
|
|
// Dowrank "nil" a bit so it is ranked below more interesting candidates.
|
|
|
|
if obj == builtinNil {
|
|
|
|
score /= 2
|
|
|
|
}
|
|
|
|
|
2019-04-25 15:19:24 -06:00
|
|
|
// If we haven't already added a candidate for an object with this name.
|
|
|
|
if _, ok := seen[obj.Name()]; !ok {
|
|
|
|
seen[obj.Name()] = struct{}{}
|
2020-04-05 23:18:15 -06:00
|
|
|
c.found(ctx, candidate{
|
2019-12-22 10:58:14 -07:00
|
|
|
obj: obj,
|
|
|
|
score: score,
|
|
|
|
addressable: isVar(obj),
|
2019-12-21 16:39:02 -07:00
|
|
|
})
|
2019-08-14 15:25:47 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-17 14:53:10 -07:00
|
|
|
if c.inference.objType != nil {
|
|
|
|
if named, _ := deref(c.inference.objType).(*types.Named); named != nil {
|
2019-10-15 15:42:30 -06:00
|
|
|
// If we expected a named type, check the type's package for
|
|
|
|
// completion items. This is useful when the current file hasn't
|
|
|
|
// imported the type's package yet.
|
|
|
|
|
|
|
|
if named.Obj() != nil && named.Obj().Pkg() != nil {
|
|
|
|
pkg := named.Obj().Pkg()
|
|
|
|
|
|
|
|
// Make sure the package name isn't already in use by another
|
|
|
|
// object, and that this file doesn't import the package yet.
|
|
|
|
if _, ok := seen[pkg.Name()]; !ok && pkg != c.pkg.GetTypes() && !alreadyImports(c.file, pkg.Path()) {
|
|
|
|
seen[pkg.Name()] = struct{}{}
|
|
|
|
obj := types.NewPkgName(0, nil, pkg.Name(), pkg)
|
2019-11-13 15:03:07 -07:00
|
|
|
imp := &importInfo{
|
2019-11-05 12:53:55 -07:00
|
|
|
importPath: pkg.Path(),
|
2019-11-13 15:03:07 -07:00
|
|
|
}
|
|
|
|
if imports.ImportPathToAssumedName(pkg.Path()) != pkg.Name() {
|
|
|
|
imp.name = pkg.Name()
|
|
|
|
}
|
2020-04-05 23:18:15 -06:00
|
|
|
c.found(ctx, candidate{
|
2019-12-21 16:39:02 -07:00
|
|
|
obj: obj,
|
|
|
|
score: stdScore,
|
|
|
|
imp: imp,
|
|
|
|
})
|
2019-10-15 15:42:30 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-10 12:54:59 -07:00
|
|
|
if c.opts.unimported {
|
2020-04-05 23:18:15 -06:00
|
|
|
if err := c.unimportedPackages(ctx, seen); err != nil {
|
2019-12-27 13:46:49 -07:00
|
|
|
return err
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
}
|
internal/lsp: add literal completion candidates
Add support for literal completion candidates such as "[]int{}" or
"make([]int, 0)". We support both named and unnamed types. I used the
existing type matching logic, so, for example, if the expected type is
an interface, we will suggest literal candidates that implement the
interface.
The literal candidates have a lower score than normal matching
candidates, so they shouldn't be disruptive in cases where you don't
want a literal candidate.
This commit adds support for slice, array, struct, map, and channel
literal candidates since they are pretty similar. Functions will be
supported in a subsequent commit.
I also added support for setting a snippet's final tab stop. This is
useful if you want the cursor to end up somewhere other than the
character after the snippet.
Change-Id: Id3b74260fff4d61703989b422267021b00cec005
Reviewed-on: https://go-review.googlesource.com/c/tools/+/193698
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-09-04 17:12:37 -06:00
|
|
|
|
2020-03-05 20:33:48 -07:00
|
|
|
if t := c.inference.objType; t != nil {
|
|
|
|
t = deref(t)
|
|
|
|
|
2020-08-01 23:54:33 -06:00
|
|
|
// If we have an expected type and it is _not_ a named type,
|
|
|
|
// handle it specially. Non-named types like "[]int" will never be
|
|
|
|
// considered via a lexical search, so we need to directly inject
|
|
|
|
// them.
|
2019-12-26 22:31:56 -07:00
|
|
|
if _, named := t.(*types.Named); !named {
|
2020-08-01 23:54:33 -06:00
|
|
|
// If our expected type is "[]int", this will add a literal
|
|
|
|
// candidate of "[]int{}".
|
2020-04-05 23:18:15 -06:00
|
|
|
c.literal(ctx, t, nil)
|
2020-08-01 23:54:33 -06:00
|
|
|
|
|
|
|
if _, isBasic := t.(*types.Basic); !isBasic {
|
|
|
|
// If we expect a non-basic type name (e.g. "[]int"), hack up
|
|
|
|
// a named type whose name is literally "[]int". This allows
|
|
|
|
// us to reuse our object based completion machinery.
|
|
|
|
fakeNamedType := candidate{
|
|
|
|
obj: types.NewTypeName(token.NoPos, nil, types.TypeString(t, c.qf), t),
|
|
|
|
score: stdScore,
|
|
|
|
}
|
|
|
|
// Make sure the type name matches before considering
|
|
|
|
// candidate. This cuts down on useless candidates.
|
|
|
|
if c.matchingTypeName(&fakeNamedType) {
|
|
|
|
c.found(ctx, fakeNamedType)
|
|
|
|
}
|
|
|
|
}
|
internal/lsp: add literal completion candidates
Add support for literal completion candidates such as "[]int{}" or
"make([]int, 0)". We support both named and unnamed types. I used the
existing type matching logic, so, for example, if the expected type is
an interface, we will suggest literal candidates that implement the
interface.
The literal candidates have a lower score than normal matching
candidates, so they shouldn't be disruptive in cases where you don't
want a literal candidate.
This commit adds support for slice, array, struct, map, and channel
literal candidates since they are pretty similar. Functions will be
supported in a subsequent commit.
I also added support for setting a snippet's final tab stop. This is
useful if you want the cursor to end up somewhere other than the
character after the snippet.
Change-Id: Id3b74260fff4d61703989b422267021b00cec005
Reviewed-on: https://go-review.googlesource.com/c/tools/+/193698
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-09-04 17:12:37 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-02 22:21:07 -07:00
|
|
|
// Add keyword completion items appropriate in the current context.
|
|
|
|
c.addKeywordCompletions()
|
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
return nil
|
2019-01-15 23:36:31 -07:00
|
|
|
}
|
|
|
|
|
2020-07-23 21:24:36 -06:00
|
|
|
func collectScopes(info *types.Info, path []ast.Node, pos token.Pos) []*types.Scope {
|
2020-06-24 07:52:23 -06:00
|
|
|
// scopes[i], where i<len(path), is the possibly nil Scope of path[i].
|
|
|
|
var scopes []*types.Scope
|
|
|
|
for _, n := range path {
|
|
|
|
// Include *FuncType scope if pos is inside the function body.
|
|
|
|
switch node := n.(type) {
|
|
|
|
case *ast.FuncDecl:
|
|
|
|
if node.Body != nil && nodeContains(node.Body, pos) {
|
|
|
|
n = node.Type
|
|
|
|
}
|
|
|
|
case *ast.FuncLit:
|
|
|
|
if node.Body != nil && nodeContains(node.Body, pos) {
|
|
|
|
n = node.Type
|
|
|
|
}
|
|
|
|
}
|
2020-07-23 21:24:36 -06:00
|
|
|
scopes = append(scopes, info.Scopes[n])
|
2020-06-24 07:52:23 -06:00
|
|
|
}
|
|
|
|
return scopes
|
|
|
|
}
|
|
|
|
|
2020-04-05 23:18:15 -06:00
|
|
|
func (c *completer) unimportedPackages(ctx context.Context, seen map[string]struct{}) error {
|
|
|
|
var prefix string
|
2020-03-31 11:19:04 -06:00
|
|
|
if c.surrounding != nil {
|
|
|
|
prefix = c.surrounding.Prefix()
|
|
|
|
}
|
2020-05-01 12:27:15 -06:00
|
|
|
count := 0
|
2020-03-31 11:19:04 -06:00
|
|
|
|
2020-04-05 23:18:15 -06:00
|
|
|
known, err := c.snapshot.CachedImportPaths(ctx)
|
2020-03-31 11:19:04 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
var paths []string
|
2020-05-01 12:27:15 -06:00
|
|
|
for path, pkg := range known {
|
|
|
|
if !strings.HasPrefix(pkg.GetTypes().Name(), prefix) {
|
2020-03-31 11:19:04 -06:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
paths = append(paths, path)
|
|
|
|
}
|
|
|
|
|
|
|
|
var relevances map[string]int
|
|
|
|
if len(paths) != 0 {
|
2020-07-06 13:18:50 -06:00
|
|
|
if err := c.snapshot.View().RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
|
|
|
|
var err error
|
|
|
|
relevances, err = imports.ScoreImportPaths(ctx, opts.Env, paths)
|
|
|
|
return err
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-03-31 11:19:04 -06:00
|
|
|
}
|
2020-05-01 13:24:44 -06:00
|
|
|
sort.Slice(paths, func(i, j int) bool {
|
|
|
|
return relevances[paths[i]] > relevances[paths[j]]
|
|
|
|
})
|
2020-03-31 11:19:04 -06:00
|
|
|
|
2020-05-01 13:24:44 -06:00
|
|
|
for _, path := range paths {
|
2020-03-31 11:19:04 -06:00
|
|
|
pkg := known[path]
|
2020-05-01 12:27:15 -06:00
|
|
|
if _, ok := seen[pkg.GetTypes().Name()]; ok {
|
|
|
|
continue
|
|
|
|
}
|
2020-03-31 11:19:04 -06:00
|
|
|
imp := &importInfo{
|
|
|
|
importPath: path,
|
|
|
|
pkg: pkg,
|
|
|
|
}
|
|
|
|
if imports.ImportPathToAssumedName(path) != pkg.GetTypes().Name() {
|
|
|
|
imp.name = pkg.GetTypes().Name()
|
|
|
|
}
|
2020-05-01 12:27:15 -06:00
|
|
|
if count >= maxUnimportedPackageNames {
|
|
|
|
return nil
|
|
|
|
}
|
2020-04-05 23:18:15 -06:00
|
|
|
c.found(ctx, candidate{
|
2020-03-31 11:19:04 -06:00
|
|
|
obj: types.NewPkgName(0, nil, pkg.GetTypes().Name(), pkg.GetTypes()),
|
2020-05-01 13:24:44 -06:00
|
|
|
score: unimportedScore(relevances[path]),
|
2020-03-31 11:19:04 -06:00
|
|
|
imp: imp,
|
|
|
|
})
|
2020-05-01 12:27:15 -06:00
|
|
|
count++
|
2020-03-31 11:19:04 -06:00
|
|
|
}
|
|
|
|
|
2020-04-05 23:18:15 -06:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
defer cancel()
|
|
|
|
|
2020-03-31 11:19:04 -06:00
|
|
|
var mu sync.Mutex
|
|
|
|
add := func(pkg imports.ImportFix) {
|
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
|
|
|
if _, ok := seen[pkg.IdentName]; ok {
|
|
|
|
return
|
|
|
|
}
|
2020-05-01 12:27:15 -06:00
|
|
|
if _, ok := relevances[pkg.StmtInfo.ImportPath]; ok {
|
|
|
|
return
|
|
|
|
}
|
2020-03-31 11:19:04 -06:00
|
|
|
|
2020-05-01 12:27:15 -06:00
|
|
|
if count >= maxUnimportedPackageNames {
|
2020-03-31 11:19:04 -06:00
|
|
|
cancel()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do not add the unimported packages to seen, since we can have
|
|
|
|
// multiple packages of the same name as completion suggestions, since
|
|
|
|
// only one will be chosen.
|
|
|
|
obj := types.NewPkgName(0, nil, pkg.IdentName, types.NewPackage(pkg.StmtInfo.ImportPath, pkg.IdentName))
|
2020-04-05 23:18:15 -06:00
|
|
|
c.found(ctx, candidate{
|
2020-03-31 11:19:04 -06:00
|
|
|
obj: obj,
|
2020-05-01 12:27:15 -06:00
|
|
|
score: unimportedScore(pkg.Relevance),
|
2020-03-31 11:19:04 -06:00
|
|
|
imp: &importInfo{
|
|
|
|
importPath: pkg.StmtInfo.ImportPath,
|
|
|
|
name: pkg.StmtInfo.Name,
|
|
|
|
},
|
|
|
|
})
|
2020-05-01 12:27:15 -06:00
|
|
|
count++
|
2020-03-31 11:19:04 -06:00
|
|
|
}
|
|
|
|
return c.snapshot.View().RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
|
2020-07-06 13:18:50 -06:00
|
|
|
return imports.GetAllCandidates(ctx, add, prefix, c.filename, c.pkg.GetTypes().Name(), opts.Env)
|
2020-03-31 11:19:04 -06:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-11-12 14:58:00 -07:00
|
|
|
// alreadyImports reports whether f has an import with the specified path.
|
|
|
|
func alreadyImports(f *ast.File, path string) bool {
|
|
|
|
for _, s := range f.Imports {
|
|
|
|
if importPath(s) == path {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// importPath returns the unquoted import path of s,
|
|
|
|
// or "" if the path is not properly quoted.
|
|
|
|
func importPath(s *ast.ImportSpec) string {
|
|
|
|
t, err := strconv.Unquote(s.Path.Value)
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2019-09-17 16:10:05 -06:00
|
|
|
func nodeContains(n ast.Node, pos token.Pos) bool {
|
2019-11-20 12:15:53 -07:00
|
|
|
return n != nil && n.Pos() <= pos && pos <= n.End()
|
2019-09-17 16:10:05 -06:00
|
|
|
}
|
|
|
|
|
2019-09-12 14:21:36 -06:00
|
|
|
func (c *completer) inConstDecl() bool {
|
|
|
|
for _, n := range c.path {
|
|
|
|
if decl, ok := n.(*ast.GenDecl); ok && decl.Tok == token.CONST {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-05-13 15:37:08 -06:00
|
|
|
// structLiteralFieldName finds completions for struct field names inside a struct literal.
|
2020-04-05 23:18:15 -06:00
|
|
|
func (c *completer) structLiteralFieldName(ctx context.Context) error {
|
2019-05-13 15:37:08 -06:00
|
|
|
clInfo := c.enclosingCompositeLiteral
|
|
|
|
|
2018-11-07 18:57:08 -07:00
|
|
|
// Mark fields of the composite literal that have already been set,
|
|
|
|
// except for the current field.
|
|
|
|
addedFields := make(map[*types.Var]bool)
|
2019-05-13 15:37:08 -06:00
|
|
|
for _, el := range clInfo.cl.Elts {
|
2019-04-24 17:26:34 -06:00
|
|
|
if kvExpr, ok := el.(*ast.KeyValueExpr); ok {
|
2019-05-13 15:37:08 -06:00
|
|
|
if clInfo.kv == kvExpr {
|
2018-11-07 18:57:08 -07:00
|
|
|
continue
|
|
|
|
}
|
2019-04-24 17:26:34 -06:00
|
|
|
|
|
|
|
if key, ok := kvExpr.Key.(*ast.Ident); ok {
|
2019-09-09 18:22:42 -06:00
|
|
|
if used, ok := c.pkg.GetTypesInfo().Uses[key]; ok {
|
2018-11-07 18:57:08 -07:00
|
|
|
if usedVar, ok := used.(*types.Var); ok {
|
|
|
|
addedFields[usedVar] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-13 15:37:08 -06:00
|
|
|
|
|
|
|
switch t := clInfo.clType.(type) {
|
|
|
|
case *types.Struct:
|
|
|
|
for i := 0; i < t.NumFields(); i++ {
|
|
|
|
field := t.Field(i)
|
|
|
|
if !addedFields[field] {
|
2020-04-05 23:18:15 -06:00
|
|
|
c.found(ctx, candidate{
|
2019-12-21 16:39:02 -07:00
|
|
|
obj: field,
|
|
|
|
score: highScore,
|
|
|
|
})
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
2019-05-13 15:37:08 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add lexical completions if we aren't certain we are in the key part of a
|
|
|
|
// key-value pair.
|
|
|
|
if clInfo.maybeInFieldName {
|
2020-04-05 23:18:15 -06:00
|
|
|
return c.lexical(ctx)
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
2019-05-13 15:37:08 -06:00
|
|
|
default:
|
2020-04-05 23:18:15 -06:00
|
|
|
return c.lexical(ctx)
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
2019-05-13 15:37:08 -06:00
|
|
|
|
2019-04-24 17:26:34 -06:00
|
|
|
return nil
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
|
2019-05-13 15:37:08 -06:00
|
|
|
func (cl *compLitInfo) isStruct() bool {
|
|
|
|
_, ok := cl.clType.(*types.Struct)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
|
|
|
// enclosingCompositeLiteral returns information about the composite literal enclosing the
|
|
|
|
// position.
|
|
|
|
func enclosingCompositeLiteral(path []ast.Node, pos token.Pos, info *types.Info) *compLitInfo {
|
2019-04-28 21:19:54 -06:00
|
|
|
for _, n := range path {
|
2019-04-23 16:00:40 -06:00
|
|
|
switch n := n.(type) {
|
|
|
|
case *ast.CompositeLit:
|
2019-04-24 17:26:34 -06:00
|
|
|
// The enclosing node will be a composite literal if the user has just
|
|
|
|
// opened the curly brace (e.g. &x{<>) or the completion request is triggered
|
|
|
|
// from an already completed composite literal expression (e.g. &x{foo: 1, <>})
|
|
|
|
//
|
|
|
|
// The position is not part of the composite literal unless it falls within the
|
|
|
|
// curly braces (e.g. "foo.Foo<>Struct{}").
|
2019-09-13 13:15:53 -06:00
|
|
|
if !(n.Lbrace < pos && pos <= n.Rbrace) {
|
|
|
|
// Keep searching since we may yet be inside a composite literal.
|
|
|
|
// For example "Foo{B: Ba<>{}}".
|
|
|
|
break
|
2019-05-13 15:37:08 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
tv, ok := info.Types[n]
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
clInfo := compLitInfo{
|
|
|
|
cl: n,
|
2019-08-28 21:53:48 -06:00
|
|
|
clType: deref(tv.Type).Underlying(),
|
2019-05-13 15:37:08 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
expr ast.Expr
|
|
|
|
hasKeys bool
|
|
|
|
)
|
|
|
|
for _, el := range n.Elts {
|
|
|
|
// Remember the expression that the position falls in, if any.
|
|
|
|
if el.Pos() <= pos && pos <= el.End() {
|
|
|
|
expr = el
|
|
|
|
}
|
|
|
|
|
|
|
|
if kv, ok := el.(*ast.KeyValueExpr); ok {
|
|
|
|
hasKeys = true
|
|
|
|
// If expr == el then we know the position falls in this expression,
|
|
|
|
// so also record kv as the enclosing *ast.KeyValueExpr.
|
|
|
|
if expr == el {
|
|
|
|
clInfo.kv = kv
|
|
|
|
break
|
|
|
|
}
|
2019-04-23 16:00:40 -06:00
|
|
|
}
|
|
|
|
}
|
2019-05-13 15:37:08 -06:00
|
|
|
|
|
|
|
if clInfo.kv != nil {
|
|
|
|
// If in a *ast.KeyValueExpr, we know we are in the key if the position
|
|
|
|
// is to the left of the colon (e.g. "Foo{F<>: V}".
|
|
|
|
clInfo.inKey = pos <= clInfo.kv.Colon
|
|
|
|
} else if hasKeys {
|
|
|
|
// If we aren't in a *ast.KeyValueExpr but the composite literal has
|
|
|
|
// other *ast.KeyValueExprs, we must be on the key side of a new
|
|
|
|
// *ast.KeyValueExpr (e.g. "Foo{F: V, <>}").
|
|
|
|
clInfo.inKey = true
|
|
|
|
} else {
|
|
|
|
switch clInfo.clType.(type) {
|
|
|
|
case *types.Struct:
|
|
|
|
if len(n.Elts) == 0 {
|
|
|
|
// If the struct literal is empty, next could be a struct field
|
|
|
|
// name or an expression (e.g. "Foo{<>}" could become "Foo{F:}"
|
|
|
|
// or "Foo{someVar}").
|
|
|
|
clInfo.maybeInFieldName = true
|
|
|
|
} else if len(n.Elts) == 1 {
|
|
|
|
// If there is one expression and the position is in that expression
|
|
|
|
// and the expression is an identifier, we may be writing a field
|
|
|
|
// name or an expression (e.g. "Foo{F<>}").
|
|
|
|
_, clInfo.maybeInFieldName = expr.(*ast.Ident)
|
|
|
|
}
|
|
|
|
case *types.Map:
|
|
|
|
// If we aren't in a *ast.KeyValueExpr we must be adding a new key
|
|
|
|
// to the map.
|
|
|
|
clInfo.inKey = true
|
|
|
|
}
|
2019-04-24 17:26:34 -06:00
|
|
|
}
|
2019-05-13 15:37:08 -06:00
|
|
|
|
|
|
|
return &clInfo
|
2019-05-10 10:26:15 -06:00
|
|
|
default:
|
2020-08-09 22:42:16 -06:00
|
|
|
if breaksExpectedTypeInference(n, pos) {
|
2019-05-13 15:37:08 -06:00
|
|
|
return nil
|
2019-05-10 10:26:15 -06:00
|
|
|
}
|
2019-04-23 16:00:40 -06:00
|
|
|
}
|
|
|
|
}
|
2019-05-13 15:37:08 -06:00
|
|
|
|
|
|
|
return nil
|
2019-04-23 16:00:40 -06:00
|
|
|
}
|
|
|
|
|
2019-09-18 13:26:39 -06:00
|
|
|
// enclosingFunction returns the signature and body of the function
|
|
|
|
// enclosing the given position.
|
2020-03-27 10:52:40 -06:00
|
|
|
func enclosingFunction(path []ast.Node, info *types.Info) *funcInfo {
|
2018-11-07 18:57:08 -07:00
|
|
|
for _, node := range path {
|
|
|
|
switch t := node.(type) {
|
|
|
|
case *ast.FuncDecl:
|
|
|
|
if obj, ok := info.Defs[t.Name]; ok {
|
2019-09-18 13:26:39 -06:00
|
|
|
return &funcInfo{
|
|
|
|
sig: obj.Type().(*types.Signature),
|
|
|
|
body: t.Body,
|
|
|
|
}
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
case *ast.FuncLit:
|
|
|
|
if typ, ok := info.Types[t]; ok {
|
2019-09-18 13:26:39 -06:00
|
|
|
return &funcInfo{
|
|
|
|
sig: typ.Type.(*types.Signature),
|
|
|
|
body: t.Body,
|
|
|
|
}
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-05-13 15:37:08 -06:00
|
|
|
func (c *completer) expectedCompositeLiteralType() types.Type {
|
|
|
|
clInfo := c.enclosingCompositeLiteral
|
|
|
|
switch t := clInfo.clType.(type) {
|
2019-04-23 16:00:40 -06:00
|
|
|
case *types.Slice:
|
2019-05-13 15:37:08 -06:00
|
|
|
if clInfo.inKey {
|
|
|
|
return types.Typ[types.Int]
|
|
|
|
}
|
2019-04-23 16:00:40 -06:00
|
|
|
return t.Elem()
|
|
|
|
case *types.Array:
|
2019-05-13 15:37:08 -06:00
|
|
|
if clInfo.inKey {
|
|
|
|
return types.Typ[types.Int]
|
|
|
|
}
|
2019-04-23 16:00:40 -06:00
|
|
|
return t.Elem()
|
|
|
|
case *types.Map:
|
2019-05-13 15:37:08 -06:00
|
|
|
if clInfo.inKey {
|
2019-04-23 16:00:40 -06:00
|
|
|
return t.Key()
|
|
|
|
}
|
|
|
|
return t.Elem()
|
|
|
|
case *types.Struct:
|
2019-05-13 15:37:08 -06:00
|
|
|
// If we are completing a key (i.e. field name), there is no expected type.
|
|
|
|
if clInfo.inKey {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we are in a key-value pair, but not in the key, then we must be on the
|
|
|
|
// value side. The expected type of the value will be determined from the key.
|
|
|
|
if clInfo.kv != nil {
|
|
|
|
if key, ok := clInfo.kv.Key.(*ast.Ident); ok {
|
2019-04-23 16:00:40 -06:00
|
|
|
for i := 0; i < t.NumFields(); i++ {
|
2019-04-24 17:26:34 -06:00
|
|
|
if field := t.Field(i); field.Name() == key.Name {
|
2019-04-23 16:00:40 -06:00
|
|
|
return field.Type()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-13 15:37:08 -06:00
|
|
|
} else {
|
|
|
|
// If we aren't in a key-value pair and aren't in the key, we must be using
|
|
|
|
// implicit field names.
|
|
|
|
|
|
|
|
// The order of the literal fields must match the order in the struct definition.
|
|
|
|
// Find the element that the position belongs to and suggest that field's type.
|
2020-01-25 16:22:03 -07:00
|
|
|
if i := exprAtPos(c.pos, clInfo.cl.Elts); i < t.NumFields() {
|
2019-05-13 15:37:08 -06:00
|
|
|
return t.Field(i).Type()
|
2019-04-23 16:00:40 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-05-15 16:29:19 -06:00
|
|
|
// typeModifier represents an operator that changes the expected type.
|
2019-09-13 13:15:53 -06:00
|
|
|
type typeModifier struct {
|
|
|
|
mod typeMod
|
|
|
|
arrayLen int64
|
|
|
|
}
|
|
|
|
|
|
|
|
type typeMod int
|
2019-05-15 16:29:19 -06:00
|
|
|
|
|
|
|
const (
|
2020-08-09 22:42:16 -06:00
|
|
|
dereference typeMod = iota // pointer indirection: "*"
|
|
|
|
reference // adds level of pointer: "&" for values, "*" for type names
|
|
|
|
chanRead // channel read operator ("<-")
|
|
|
|
slice // make a slice type ("[]" in "[]int")
|
|
|
|
array // make an array type ("[2]" in "[2]int")
|
2019-05-15 16:29:19 -06:00
|
|
|
)
|
|
|
|
|
2019-12-30 17:49:53 -07:00
|
|
|
type objKind int
|
|
|
|
|
|
|
|
const (
|
2020-08-03 17:50:08 -06:00
|
|
|
kindAny objKind = 0
|
2019-12-30 17:49:53 -07:00
|
|
|
kindArray objKind = 1 << iota
|
|
|
|
kindSlice
|
|
|
|
kindChan
|
|
|
|
kindMap
|
|
|
|
kindStruct
|
|
|
|
kindString
|
2020-08-03 17:50:08 -06:00
|
|
|
kindInt
|
|
|
|
kindBool
|
|
|
|
kindBytes
|
|
|
|
kindPtr
|
|
|
|
kindFloat
|
|
|
|
kindComplex
|
|
|
|
kindError
|
|
|
|
kindStringer
|
2020-08-01 15:18:10 -06:00
|
|
|
kindFunc
|
2019-12-30 17:49:53 -07:00
|
|
|
)
|
|
|
|
|
2020-08-07 23:50:58 -06:00
|
|
|
// penalizedObj represents an object that should be disfavored as a
|
|
|
|
// completion candidate.
|
|
|
|
type penalizedObj struct {
|
|
|
|
// objChain is the full "chain", e.g. "foo.bar().baz" becomes
|
|
|
|
// []types.Object{foo, bar, baz}.
|
|
|
|
objChain []types.Object
|
|
|
|
// penalty is score penalty in the range (0, 1).
|
|
|
|
penalty float64
|
|
|
|
}
|
|
|
|
|
2020-01-17 14:53:10 -07:00
|
|
|
// candidateInference holds information we have inferred about a type that can be
|
2019-06-17 12:11:13 -06:00
|
|
|
// used at the current position.
|
2020-01-17 14:53:10 -07:00
|
|
|
type candidateInference struct {
|
2019-06-17 12:11:13 -06:00
|
|
|
// objType is the desired type of an object used at the query position.
|
|
|
|
objType types.Type
|
|
|
|
|
2019-12-30 17:49:53 -07:00
|
|
|
// objKind is a mask of expected kinds of types such as "map", "slice", etc.
|
|
|
|
objKind objKind
|
|
|
|
|
2020-08-03 23:16:11 -06:00
|
|
|
// variadic is true if we are completing the initial variadic
|
|
|
|
// parameter. For example:
|
|
|
|
// append([]T{}, <>) // objType=T variadic=true
|
|
|
|
// append([]T{}, T{}, <>) // objType=T variadic=false
|
|
|
|
variadic bool
|
internal/lsp: add literal completion candidates
Add support for literal completion candidates such as "[]int{}" or
"make([]int, 0)". We support both named and unnamed types. I used the
existing type matching logic, so, for example, if the expected type is
an interface, we will suggest literal candidates that implement the
interface.
The literal candidates have a lower score than normal matching
candidates, so they shouldn't be disruptive in cases where you don't
want a literal candidate.
This commit adds support for slice, array, struct, map, and channel
literal candidates since they are pretty similar. Functions will be
supported in a subsequent commit.
I also added support for setting a snippet's final tab stop. This is
useful if you want the cursor to end up somewhere other than the
character after the snippet.
Change-Id: Id3b74260fff4d61703989b422267021b00cec005
Reviewed-on: https://go-review.googlesource.com/c/tools/+/193698
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-09-04 17:12:37 -06:00
|
|
|
|
2019-09-13 13:15:53 -06:00
|
|
|
// modifiers are prefixes such as "*", "&" or "<-" that influence how
|
|
|
|
// a candidate type relates to the expected type.
|
|
|
|
modifiers []typeModifier
|
|
|
|
|
|
|
|
// convertibleTo is a type our candidate type must be convertible to.
|
|
|
|
convertibleTo types.Type
|
|
|
|
|
|
|
|
// typeName holds information about the expected type name at
|
|
|
|
// position, if any.
|
|
|
|
typeName typeNameInference
|
2020-01-20 15:50:36 -07:00
|
|
|
|
|
|
|
// assignees are the types that would receive a function call's
|
|
|
|
// results at the position. For example:
|
|
|
|
//
|
|
|
|
// foo := 123
|
|
|
|
// foo, bar := <>
|
|
|
|
//
|
|
|
|
// at "<>", the assignees are [int, <invalid>].
|
|
|
|
assignees []types.Type
|
2020-01-20 17:19:26 -07:00
|
|
|
|
|
|
|
// variadicAssignees is true if we could be completing an inner
|
|
|
|
// function call that fills out an outer function call's variadic
|
|
|
|
// params. For example:
|
|
|
|
//
|
|
|
|
// func foo(int, ...string) {}
|
|
|
|
//
|
|
|
|
// foo(<>) // variadicAssignees=true
|
|
|
|
// foo(bar<>) // variadicAssignees=true
|
|
|
|
// foo(bar, baz<>) // variadicAssignees=false
|
|
|
|
variadicAssignees bool
|
2020-08-06 23:33:47 -06:00
|
|
|
|
2020-08-07 23:50:58 -06:00
|
|
|
// penalized holds expressions that should be disfavored as
|
|
|
|
// candidates. For example, it tracks expressions already used in a
|
|
|
|
// switch statement's other cases. Each expression is tracked using
|
|
|
|
// its entire object "chain" allowing differentiation between
|
|
|
|
// "a.foo" and "b.foo" when "a" and "b" are the same type.
|
|
|
|
penalized []penalizedObj
|
2020-08-06 23:33:47 -06:00
|
|
|
|
|
|
|
// objChain contains the chain of objects representing the
|
|
|
|
// surrounding *ast.SelectorExpr. For example, if we are completing
|
|
|
|
// "foo.bar.ba<>", objChain will contain []types.Object{foo, bar}.
|
|
|
|
objChain []types.Object
|
2019-09-13 13:15:53 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// typeNameInference holds information about the expected type name at
|
|
|
|
// position.
|
|
|
|
type typeNameInference struct {
|
2019-06-17 12:11:13 -06:00
|
|
|
// wantTypeName is true if we expect the name of a type.
|
|
|
|
wantTypeName bool
|
2019-06-19 10:12:57 -06:00
|
|
|
|
|
|
|
// modifiers are prefixes such as "*", "&" or "<-" that influence how
|
|
|
|
// a candidate type relates to the expected type.
|
|
|
|
modifiers []typeModifier
|
2019-06-26 16:30:01 -06:00
|
|
|
|
|
|
|
// assertableFrom is a type that must be assertable to our candidate type.
|
|
|
|
assertableFrom types.Type
|
2019-09-17 15:59:27 -06:00
|
|
|
|
|
|
|
// wantComparable is true if we want a comparable type.
|
|
|
|
wantComparable bool
|
2020-08-05 23:17:55 -06:00
|
|
|
|
|
|
|
// seenTypeSwitchCases tracks types that have already been used by
|
|
|
|
// the containing type switch.
|
|
|
|
seenTypeSwitchCases []types.Type
|
2020-08-09 22:42:16 -06:00
|
|
|
|
|
|
|
// compLitType is true if we are completing a composite literal type
|
|
|
|
// name, e.g "foo<>{}".
|
|
|
|
compLitType bool
|
2019-06-17 12:11:13 -06:00
|
|
|
}
|
|
|
|
|
2020-01-17 14:53:10 -07:00
|
|
|
// expectedCandidate returns information about the expected candidate
|
|
|
|
// for an expression at the query position.
|
2020-04-05 23:18:15 -06:00
|
|
|
func expectedCandidate(ctx context.Context, c *completer) (inf candidateInference) {
|
2019-11-06 12:39:30 -07:00
|
|
|
inf.typeName = expectTypeName(c)
|
2019-06-17 12:11:13 -06:00
|
|
|
|
2019-05-13 15:37:08 -06:00
|
|
|
if c.enclosingCompositeLiteral != nil {
|
2019-09-13 13:15:53 -06:00
|
|
|
inf.objType = c.expectedCompositeLiteralType()
|
2019-05-13 15:37:08 -06:00
|
|
|
}
|
|
|
|
|
2019-05-10 10:26:15 -06:00
|
|
|
Nodes:
|
2019-05-15 16:29:19 -06:00
|
|
|
for i, node := range c.path {
|
|
|
|
switch node := node.(type) {
|
2018-11-07 18:57:08 -07:00
|
|
|
case *ast.BinaryExpr:
|
|
|
|
// Determine if query position comes from left or right of op.
|
2019-05-15 16:29:19 -06:00
|
|
|
e := node.X
|
|
|
|
if c.pos < node.OpPos {
|
|
|
|
e = node.Y
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
2019-09-09 18:22:42 -06:00
|
|
|
if tv, ok := c.pkg.GetTypesInfo().Types[e]; ok {
|
2020-08-02 12:54:56 -06:00
|
|
|
switch node.Op {
|
|
|
|
case token.LAND, token.LOR:
|
|
|
|
// Don't infer "bool" type for "&&" or "||". Often you want
|
|
|
|
// to compose a boolean expression from non-boolean
|
|
|
|
// candidates.
|
|
|
|
default:
|
|
|
|
inf.objType = tv.Type
|
|
|
|
}
|
2019-05-10 10:26:15 -06:00
|
|
|
break Nodes
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
case *ast.AssignStmt:
|
|
|
|
// Only rank completions if you are on the right side of the token.
|
2019-05-15 16:29:19 -06:00
|
|
|
if c.pos > node.TokPos {
|
2020-01-25 16:22:03 -07:00
|
|
|
i := exprAtPos(c.pos, node.Rhs)
|
2019-05-15 16:29:19 -06:00
|
|
|
if i >= len(node.Lhs) {
|
|
|
|
i = len(node.Lhs) - 1
|
2019-05-10 10:26:15 -06:00
|
|
|
}
|
2019-09-09 18:22:42 -06:00
|
|
|
if tv, ok := c.pkg.GetTypesInfo().Types[node.Lhs[i]]; ok {
|
2019-09-13 13:15:53 -06:00
|
|
|
inf.objType = tv.Type
|
2019-05-10 10:26:15 -06:00
|
|
|
}
|
2020-01-20 15:50:36 -07:00
|
|
|
|
|
|
|
// If we have a single expression on the RHS, record the LHS
|
|
|
|
// assignees so we can favor multi-return function calls with
|
|
|
|
// matching result values.
|
|
|
|
if len(node.Rhs) <= 1 {
|
|
|
|
for _, lhs := range node.Lhs {
|
|
|
|
inf.assignees = append(inf.assignees, c.pkg.GetTypesInfo().TypeOf(lhs))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Otherwse, record our single assignee, even if its type is
|
|
|
|
// not available. We use this info to downrank functions
|
|
|
|
// with the wrong number of result values.
|
|
|
|
inf.assignees = append(inf.assignees, c.pkg.GetTypesInfo().TypeOf(node.Lhs[i]))
|
|
|
|
}
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
2019-09-13 13:15:53 -06:00
|
|
|
return inf
|
2019-12-22 16:04:15 -07:00
|
|
|
case *ast.ValueSpec:
|
|
|
|
if node.Type != nil && c.pos > node.Type.End() {
|
|
|
|
inf.objType = c.pkg.GetTypesInfo().TypeOf(node.Type)
|
|
|
|
}
|
2020-01-14 10:13:30 -07:00
|
|
|
return inf
|
2018-11-07 18:57:08 -07:00
|
|
|
case *ast.CallExpr:
|
2019-05-10 10:26:15 -06:00
|
|
|
// Only consider CallExpr args if position falls between parens.
|
2020-08-01 15:18:10 -06:00
|
|
|
if node.Lparen < c.pos && c.pos <= node.Rparen {
|
2019-06-27 16:37:50 -06:00
|
|
|
// For type conversions like "int64(foo)" we can only infer our
|
|
|
|
// desired type is convertible to int64.
|
2019-09-09 18:22:42 -06:00
|
|
|
if typ := typeConversion(node, c.pkg.GetTypesInfo()); typ != nil {
|
2019-09-13 13:15:53 -06:00
|
|
|
inf.convertibleTo = typ
|
2019-06-27 16:37:50 -06:00
|
|
|
break Nodes
|
|
|
|
}
|
|
|
|
|
2019-09-09 18:22:42 -06:00
|
|
|
if tv, ok := c.pkg.GetTypesInfo().Types[node.Fun]; ok {
|
2019-05-10 10:26:15 -06:00
|
|
|
if sig, ok := tv.Type.(*types.Signature); ok {
|
2019-11-03 16:57:27 -07:00
|
|
|
numParams := sig.Params().Len()
|
|
|
|
if numParams == 0 {
|
2019-09-13 13:15:53 -06:00
|
|
|
return inf
|
2019-05-10 10:26:15 -06:00
|
|
|
}
|
2019-11-03 16:57:27 -07:00
|
|
|
|
2020-08-03 23:16:11 -06:00
|
|
|
exprIdx := exprAtPos(c.pos, node.Args)
|
2019-11-03 16:57:27 -07:00
|
|
|
|
2020-01-20 15:50:36 -07:00
|
|
|
// If we have one or zero arg expressions, we may be
|
|
|
|
// completing to a function call that returns multiple
|
|
|
|
// values, in turn getting passed in to the surrounding
|
|
|
|
// call. Record the assignees so we can favor function
|
|
|
|
// calls that return matching values.
|
2020-08-03 12:36:03 -06:00
|
|
|
if len(node.Args) <= 1 && exprIdx == 0 {
|
2020-01-20 15:50:36 -07:00
|
|
|
for i := 0; i < sig.Params().Len(); i++ {
|
|
|
|
inf.assignees = append(inf.assignees, sig.Params().At(i).Type())
|
|
|
|
}
|
2020-01-20 17:19:26 -07:00
|
|
|
|
|
|
|
// Record that we may be completing into variadic parameters.
|
|
|
|
inf.variadicAssignees = sig.Variadic()
|
2020-01-20 15:50:36 -07:00
|
|
|
}
|
|
|
|
|
2019-05-10 10:26:15 -06:00
|
|
|
// Make sure not to run past the end of expected parameters.
|
2020-08-03 23:16:11 -06:00
|
|
|
if exprIdx >= numParams {
|
2019-11-03 16:57:27 -07:00
|
|
|
inf.objType = sig.Params().At(numParams - 1).Type()
|
|
|
|
} else {
|
|
|
|
inf.objType = sig.Params().At(exprIdx).Type()
|
2019-05-10 10:26:15 -06:00
|
|
|
}
|
2020-08-03 23:16:11 -06:00
|
|
|
|
|
|
|
if sig.Variadic() && exprIdx >= (numParams-1) {
|
|
|
|
// If we are completing a variadic param, deslice the variadic type.
|
|
|
|
inf.objType = deslice(inf.objType)
|
|
|
|
// Record whether we are completing the initial variadic param.
|
|
|
|
inf.variadic = exprIdx == numParams-1 && len(node.Args) <= numParams
|
2020-08-03 17:50:08 -06:00
|
|
|
|
|
|
|
// Check if we can infer object kind from printf verb.
|
|
|
|
inf.objKind |= printfArgKind(c.pkg.GetTypesInfo(), node, exprIdx)
|
2020-08-03 23:16:11 -06:00
|
|
|
}
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
2019-05-10 10:26:15 -06:00
|
|
|
}
|
2019-11-06 12:39:30 -07:00
|
|
|
|
|
|
|
if funIdent, ok := node.Fun.(*ast.Ident); ok {
|
2019-12-30 17:49:53 -07:00
|
|
|
obj := c.pkg.GetTypesInfo().ObjectOf(funIdent)
|
|
|
|
|
|
|
|
if obj != nil && obj.Parent() == types.Universe {
|
|
|
|
// Defer call to builtinArgType so we can provide it the
|
|
|
|
// inferred type from its parent node.
|
2019-11-06 12:39:30 -07:00
|
|
|
defer func() {
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
inf = c.builtinArgType(obj, node, inf)
|
2020-04-05 23:18:15 -06:00
|
|
|
inf.objKind = c.builtinArgKind(ctx, obj, node)
|
2019-11-06 12:39:30 -07:00
|
|
|
}()
|
|
|
|
|
2019-12-30 17:49:53 -07:00
|
|
|
// The expected type of builtin arguments like append() is
|
|
|
|
// the expected type of the builtin call itself. For
|
|
|
|
// example:
|
2019-11-06 12:39:30 -07:00
|
|
|
//
|
2019-12-30 17:49:53 -07:00
|
|
|
// var foo []int = append(<>)
|
2019-11-06 12:39:30 -07:00
|
|
|
//
|
|
|
|
// To find the expected type at <> we "skip" the append()
|
|
|
|
// node and get the expected type one level up, which is
|
|
|
|
// []int.
|
|
|
|
continue Nodes
|
|
|
|
}
|
|
|
|
}
|
2020-08-01 15:18:10 -06:00
|
|
|
|
|
|
|
return inf
|
2019-05-10 10:26:15 -06:00
|
|
|
}
|
|
|
|
case *ast.ReturnStmt:
|
2019-09-18 13:26:39 -06:00
|
|
|
if c.enclosingFunc != nil {
|
|
|
|
sig := c.enclosingFunc.sig
|
2019-05-15 16:29:19 -06:00
|
|
|
// Find signature result that corresponds to our return statement.
|
2020-01-25 16:22:03 -07:00
|
|
|
if resultIdx := exprAtPos(c.pos, node.Results); resultIdx < len(node.Results) {
|
2019-05-10 10:26:15 -06:00
|
|
|
if resultIdx < sig.Results().Len() {
|
2019-09-13 13:15:53 -06:00
|
|
|
inf.objType = sig.Results().At(resultIdx).Type()
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-13 13:15:53 -06:00
|
|
|
return inf
|
2019-05-15 16:29:19 -06:00
|
|
|
case *ast.CaseClause:
|
|
|
|
if swtch, ok := findSwitchStmt(c.path[i+1:], c.pos, node).(*ast.SwitchStmt); ok {
|
2019-09-09 18:22:42 -06:00
|
|
|
if tv, ok := c.pkg.GetTypesInfo().Types[swtch.Tag]; ok {
|
2019-09-13 13:15:53 -06:00
|
|
|
inf.objType = tv.Type
|
2020-08-06 23:33:47 -06:00
|
|
|
|
|
|
|
// Record which objects have already been used in the case
|
|
|
|
// statements so we don't suggest them again.
|
|
|
|
for _, cc := range swtch.Body.List {
|
|
|
|
for _, caseExpr := range cc.(*ast.CaseClause).List {
|
|
|
|
// Don't record the expression we are currently completing.
|
|
|
|
if caseExpr.Pos() < c.pos && c.pos <= caseExpr.End() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if objs := objChain(c.pkg.GetTypesInfo(), caseExpr); len(objs) > 0 {
|
2020-08-07 23:50:58 -06:00
|
|
|
inf.penalized = append(inf.penalized, penalizedObj{objChain: objs, penalty: 0.1})
|
2020-08-06 23:33:47 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-15 16:29:19 -06:00
|
|
|
}
|
|
|
|
}
|
2019-09-13 13:15:53 -06:00
|
|
|
return inf
|
2019-05-15 16:29:19 -06:00
|
|
|
case *ast.SliceExpr:
|
|
|
|
// Make sure position falls within the brackets (e.g. "foo[a:<>]").
|
|
|
|
if node.Lbrack < c.pos && c.pos <= node.Rbrack {
|
2019-09-13 13:15:53 -06:00
|
|
|
inf.objType = types.Typ[types.Int]
|
2019-05-15 16:29:19 -06:00
|
|
|
}
|
2019-09-13 13:15:53 -06:00
|
|
|
return inf
|
2019-05-15 16:29:19 -06:00
|
|
|
case *ast.IndexExpr:
|
|
|
|
// Make sure position falls within the brackets (e.g. "foo[<>]").
|
|
|
|
if node.Lbrack < c.pos && c.pos <= node.Rbrack {
|
2019-09-09 18:22:42 -06:00
|
|
|
if tv, ok := c.pkg.GetTypesInfo().Types[node.X]; ok {
|
2019-05-15 16:29:19 -06:00
|
|
|
switch t := tv.Type.Underlying().(type) {
|
|
|
|
case *types.Map:
|
2019-09-13 13:15:53 -06:00
|
|
|
inf.objType = t.Key()
|
2019-05-15 16:29:19 -06:00
|
|
|
case *types.Slice, *types.Array:
|
2019-09-13 13:15:53 -06:00
|
|
|
inf.objType = types.Typ[types.Int]
|
2019-05-15 16:29:19 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-13 13:15:53 -06:00
|
|
|
return inf
|
2019-05-15 16:29:19 -06:00
|
|
|
case *ast.SendStmt:
|
|
|
|
// Make sure we are on right side of arrow (e.g. "foo <- <>").
|
|
|
|
if c.pos > node.Arrow+1 {
|
2019-09-09 18:22:42 -06:00
|
|
|
if tv, ok := c.pkg.GetTypesInfo().Types[node.Chan]; ok {
|
2019-05-15 16:29:19 -06:00
|
|
|
if ch, ok := tv.Type.Underlying().(*types.Chan); ok {
|
2019-09-13 13:15:53 -06:00
|
|
|
inf.objType = ch.Elem()
|
2019-05-15 16:29:19 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-13 13:15:53 -06:00
|
|
|
return inf
|
2020-01-14 10:13:30 -07:00
|
|
|
case *ast.RangeStmt:
|
|
|
|
if nodeContains(node.X, c.pos) {
|
|
|
|
inf.objKind |= kindSlice | kindArray | kindMap | kindString
|
|
|
|
if node.Value == nil {
|
|
|
|
inf.objKind |= kindChan
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return inf
|
2019-05-10 10:26:15 -06:00
|
|
|
case *ast.StarExpr:
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.modifiers = append(inf.modifiers, typeModifier{mod: dereference})
|
2019-05-10 10:26:15 -06:00
|
|
|
case *ast.UnaryExpr:
|
2019-05-15 16:29:19 -06:00
|
|
|
switch node.Op {
|
|
|
|
case token.AND:
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.modifiers = append(inf.modifiers, typeModifier{mod: reference})
|
2019-05-15 16:29:19 -06:00
|
|
|
case token.ARROW:
|
2019-09-13 13:15:53 -06:00
|
|
|
inf.modifiers = append(inf.modifiers, typeModifier{mod: chanRead})
|
2019-05-10 10:26:15 -06:00
|
|
|
}
|
2020-08-01 15:18:10 -06:00
|
|
|
case *ast.DeferStmt, *ast.GoStmt:
|
|
|
|
inf.objKind |= kindFunc
|
|
|
|
return inf
|
2019-05-10 10:26:15 -06:00
|
|
|
default:
|
2020-08-09 22:42:16 -06:00
|
|
|
if breaksExpectedTypeInference(node, c.pos) {
|
2019-09-13 13:15:53 -06:00
|
|
|
return inf
|
2019-05-10 10:26:15 -06:00
|
|
|
}
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
}
|
2019-05-10 10:26:15 -06:00
|
|
|
|
2019-09-13 13:15:53 -06:00
|
|
|
return inf
|
2019-06-19 10:12:57 -06:00
|
|
|
}
|
2019-05-10 10:26:15 -06:00
|
|
|
|
2020-08-06 23:33:47 -06:00
|
|
|
// objChain decomposes e into a chain of objects if possible. For
|
|
|
|
// example, "foo.bar().baz" will yield []types.Object{foo, bar, baz}.
|
|
|
|
// If any part can't be turned into an object, return nil.
|
|
|
|
func objChain(info *types.Info, e ast.Expr) []types.Object {
|
|
|
|
var objs []types.Object
|
|
|
|
|
|
|
|
for e != nil {
|
|
|
|
switch n := e.(type) {
|
|
|
|
case *ast.Ident:
|
|
|
|
obj := info.ObjectOf(n)
|
|
|
|
if obj == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
objs = append(objs, obj)
|
|
|
|
e = nil
|
|
|
|
case *ast.SelectorExpr:
|
|
|
|
obj := info.ObjectOf(n.Sel)
|
|
|
|
if obj == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
objs = append(objs, obj)
|
|
|
|
e = n.X
|
|
|
|
case *ast.CallExpr:
|
|
|
|
if len(n.Args) > 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
e = n.Fun
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reverse order so the layout matches the syntactic order.
|
|
|
|
for i := 0; i < len(objs)/2; i++ {
|
|
|
|
objs[i], objs[len(objs)-1-i] = objs[len(objs)-1-i], objs[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
return objs
|
|
|
|
}
|
|
|
|
|
2019-06-19 10:12:57 -06:00
|
|
|
// applyTypeModifiers applies the list of type modifiers to a type.
|
2019-12-22 10:58:14 -07:00
|
|
|
// It returns nil if the modifiers could not be applied.
|
2020-01-17 14:53:10 -07:00
|
|
|
func (ci candidateInference) applyTypeModifiers(typ types.Type, addressable bool) types.Type {
|
|
|
|
for _, mod := range ci.modifiers {
|
2019-09-13 13:15:53 -06:00
|
|
|
switch mod.mod {
|
2020-08-09 22:42:16 -06:00
|
|
|
case dereference:
|
2019-12-22 10:58:14 -07:00
|
|
|
// For every "*" indirection operator, remove a pointer layer
|
|
|
|
// from candidate type.
|
|
|
|
if ptr, ok := typ.Underlying().(*types.Pointer); ok {
|
|
|
|
typ = ptr.Elem()
|
|
|
|
} else {
|
|
|
|
return nil
|
|
|
|
}
|
2020-08-09 22:42:16 -06:00
|
|
|
case reference:
|
2019-12-22 10:58:14 -07:00
|
|
|
// For every "&" address operator, add another pointer layer to
|
|
|
|
// candidate type, if the candidate is addressable.
|
|
|
|
if addressable {
|
|
|
|
typ = types.NewPointer(typ)
|
|
|
|
} else {
|
|
|
|
return nil
|
|
|
|
}
|
2019-06-19 10:12:57 -06:00
|
|
|
case chanRead:
|
|
|
|
// For every "<-" operator, remove a layer of channelness.
|
|
|
|
if ch, ok := typ.(*types.Chan); ok {
|
|
|
|
typ = ch.Elem()
|
2019-12-22 10:58:14 -07:00
|
|
|
} else {
|
|
|
|
return nil
|
2019-06-19 10:12:57 -06:00
|
|
|
}
|
|
|
|
}
|
2019-06-17 12:11:13 -06:00
|
|
|
}
|
2019-12-22 10:58:14 -07:00
|
|
|
|
2019-06-19 10:12:57 -06:00
|
|
|
return typ
|
2019-05-10 10:26:15 -06:00
|
|
|
}
|
|
|
|
|
2019-06-26 16:30:01 -06:00
|
|
|
// applyTypeNameModifiers applies the list of type modifiers to a type name.
|
2020-01-17 14:53:10 -07:00
|
|
|
func (ci candidateInference) applyTypeNameModifiers(typ types.Type) types.Type {
|
|
|
|
for _, mod := range ci.typeName.modifiers {
|
2019-09-13 13:15:53 -06:00
|
|
|
switch mod.mod {
|
2020-08-09 22:42:16 -06:00
|
|
|
case reference:
|
2019-06-26 16:30:01 -06:00
|
|
|
typ = types.NewPointer(typ)
|
2019-09-13 13:15:53 -06:00
|
|
|
case array:
|
|
|
|
typ = types.NewArray(typ, mod.arrayLen)
|
|
|
|
case slice:
|
|
|
|
typ = types.NewSlice(typ)
|
2019-06-26 16:30:01 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return typ
|
|
|
|
}
|
|
|
|
|
2019-11-03 16:57:27 -07:00
|
|
|
// matchesVariadic returns true if we are completing a variadic
|
|
|
|
// parameter and candType is a compatible slice type.
|
2020-01-17 14:53:10 -07:00
|
|
|
func (ci candidateInference) matchesVariadic(candType types.Type) bool {
|
2020-08-03 23:16:11 -06:00
|
|
|
return ci.variadic && ci.objType != nil && types.AssignableTo(candType, types.NewSlice(ci.objType))
|
2019-11-03 16:57:27 -07:00
|
|
|
}
|
|
|
|
|
2019-05-15 16:29:19 -06:00
|
|
|
// findSwitchStmt returns an *ast.CaseClause's corresponding *ast.SwitchStmt or
|
|
|
|
// *ast.TypeSwitchStmt. path should start from the case clause's first ancestor.
|
|
|
|
func findSwitchStmt(path []ast.Node, pos token.Pos, c *ast.CaseClause) ast.Stmt {
|
|
|
|
// Make sure position falls within a "case <>:" clause.
|
2020-01-25 16:22:03 -07:00
|
|
|
if exprAtPos(pos, c.List) >= len(c.List) {
|
2019-05-15 16:29:19 -06:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// A case clause is always nested within a block statement in a switch statement.
|
|
|
|
if len(path) < 2 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if _, ok := path[0].(*ast.BlockStmt); !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
switch s := path[1].(type) {
|
|
|
|
case *ast.SwitchStmt:
|
|
|
|
return s
|
|
|
|
case *ast.TypeSwitchStmt:
|
|
|
|
return s
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-10 10:26:15 -06:00
|
|
|
// breaksExpectedTypeInference reports if an expression node's type is unrelated
|
|
|
|
// to its child expression node types. For example, "Foo{Bar: x.Baz(<>)}" should
|
|
|
|
// expect a function argument, not a composite literal value.
|
2020-08-09 22:42:16 -06:00
|
|
|
func breaksExpectedTypeInference(n ast.Node, pos token.Pos) bool {
|
|
|
|
switch n := n.(type) {
|
|
|
|
case *ast.CompositeLit:
|
|
|
|
// Doesn't break inference if pos is in type name.
|
|
|
|
// For example: "Foo<>{Bar: 123}"
|
|
|
|
return !nodeContains(n.Type, pos)
|
|
|
|
case *ast.CallExpr:
|
|
|
|
// Doesn't break inference if pos is in func name.
|
|
|
|
// For example: "Foo<>(123)"
|
|
|
|
return !nodeContains(n.Fun, pos)
|
|
|
|
case *ast.FuncLit, *ast.IndexExpr, *ast.SliceExpr:
|
2019-05-10 10:26:15 -06:00
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
|
2019-06-17 12:11:13 -06:00
|
|
|
// expectTypeName returns information about the expected type name at position.
|
2019-09-13 13:15:53 -06:00
|
|
|
func expectTypeName(c *completer) typeNameInference {
|
2020-08-09 22:42:16 -06:00
|
|
|
var inf typeNameInference
|
2019-06-17 12:11:13 -06:00
|
|
|
|
|
|
|
Nodes:
|
|
|
|
for i, p := range c.path {
|
2019-04-24 17:26:34 -06:00
|
|
|
switch n := p.(type) {
|
2019-09-17 16:52:19 -06:00
|
|
|
case *ast.FieldList:
|
|
|
|
// Expect a type name if pos is in a FieldList. This applies to
|
|
|
|
// FuncType params/results, FuncDecl receiver, StructType, and
|
|
|
|
// InterfaceType. We don't need to worry about the field name
|
|
|
|
// because completion bails out early if pos is in an *ast.Ident
|
|
|
|
// that defines an object.
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.wantTypeName = true
|
2019-09-17 16:52:19 -06:00
|
|
|
break Nodes
|
2019-05-15 16:29:19 -06:00
|
|
|
case *ast.CaseClause:
|
2019-06-17 12:11:13 -06:00
|
|
|
// Expect type names in type switch case clauses.
|
2019-06-26 16:30:01 -06:00
|
|
|
if swtch, ok := findSwitchStmt(c.path[i+1:], c.pos, n).(*ast.TypeSwitchStmt); ok {
|
|
|
|
// The case clause types must be assertable from the type switch parameter.
|
|
|
|
ast.Inspect(swtch.Assign, func(n ast.Node) bool {
|
|
|
|
if ta, ok := n.(*ast.TypeAssertExpr); ok {
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.assertableFrom = c.pkg.GetTypesInfo().TypeOf(ta.X)
|
2019-06-26 16:30:01 -06:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.wantTypeName = true
|
2020-08-05 23:17:55 -06:00
|
|
|
|
|
|
|
// Track the types that have already been used in this
|
|
|
|
// switch's case statements so we don't recommend them.
|
|
|
|
for _, e := range swtch.Body.List {
|
|
|
|
for _, typeExpr := range e.(*ast.CaseClause).List {
|
|
|
|
// Skip if type expression contains pos. We don't want to
|
|
|
|
// count it as already used if the user is completing it.
|
|
|
|
if typeExpr.Pos() < c.pos && c.pos <= typeExpr.End() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if t := c.pkg.GetTypesInfo().TypeOf(typeExpr); t != nil {
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.seenTypeSwitchCases = append(inf.seenTypeSwitchCases, t)
|
2020-08-05 23:17:55 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-17 12:11:13 -06:00
|
|
|
break Nodes
|
|
|
|
}
|
2019-09-13 13:15:53 -06:00
|
|
|
return typeNameInference{}
|
2019-05-15 16:29:19 -06:00
|
|
|
case *ast.TypeAssertExpr:
|
2019-06-17 12:11:13 -06:00
|
|
|
// Expect type names in type assert expressions.
|
|
|
|
if n.Lparen < c.pos && c.pos <= n.Rparen {
|
2019-06-26 16:30:01 -06:00
|
|
|
// The type in parens must be assertable from the expression type.
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.assertableFrom = c.pkg.GetTypesInfo().TypeOf(n.X)
|
|
|
|
inf.wantTypeName = true
|
2019-06-17 12:11:13 -06:00
|
|
|
break Nodes
|
|
|
|
}
|
2019-09-13 13:15:53 -06:00
|
|
|
return typeNameInference{}
|
2019-06-26 16:30:01 -06:00
|
|
|
case *ast.StarExpr:
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.modifiers = append(inf.modifiers, typeModifier{mod: reference})
|
2019-09-13 13:15:53 -06:00
|
|
|
case *ast.CompositeLit:
|
|
|
|
// We want a type name if position is in the "Type" part of a
|
|
|
|
// composite literal (e.g. "Foo<>{}").
|
|
|
|
if n.Type != nil && n.Type.Pos() <= c.pos && c.pos <= n.Type.End() {
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.wantTypeName = true
|
|
|
|
inf.compLitType = true
|
|
|
|
|
|
|
|
if i < len(c.path)-1 {
|
|
|
|
// Track preceding "&" operator. Technically it applies to
|
|
|
|
// the composite literal and not the type name, but if
|
|
|
|
// affects our type completion nonetheless.
|
|
|
|
if u, ok := c.path[i+1].(*ast.UnaryExpr); ok && u.Op == token.AND {
|
|
|
|
inf.modifiers = append(inf.modifiers, typeModifier{mod: reference})
|
|
|
|
}
|
|
|
|
}
|
2019-09-13 13:15:53 -06:00
|
|
|
}
|
|
|
|
break Nodes
|
|
|
|
case *ast.ArrayType:
|
|
|
|
// If we are inside the "Elt" part of an array type, we want a type name.
|
|
|
|
if n.Elt.Pos() <= c.pos && c.pos <= n.Elt.End() {
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.wantTypeName = true
|
2019-09-13 13:15:53 -06:00
|
|
|
if n.Len == nil {
|
|
|
|
// No "Len" expression means a slice type.
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.modifiers = append(inf.modifiers, typeModifier{mod: slice})
|
2019-09-13 13:15:53 -06:00
|
|
|
} else {
|
|
|
|
// Try to get the array type using the constant value of "Len".
|
|
|
|
tv, ok := c.pkg.GetTypesInfo().Types[n.Len]
|
|
|
|
if ok && tv.Value != nil && tv.Value.Kind() == constant.Int {
|
|
|
|
if arrayLen, ok := constant.Int64Val(tv.Value); ok {
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.modifiers = append(inf.modifiers, typeModifier{mod: array, arrayLen: arrayLen})
|
2019-09-13 13:15:53 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ArrayTypes can be nested, so keep going if our parent is an
|
|
|
|
// ArrayType.
|
|
|
|
if i < len(c.path)-1 {
|
|
|
|
if _, ok := c.path[i+1].(*ast.ArrayType); ok {
|
|
|
|
continue Nodes
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break Nodes
|
|
|
|
}
|
2019-09-17 15:59:27 -06:00
|
|
|
case *ast.MapType:
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.wantTypeName = true
|
2019-09-17 15:59:27 -06:00
|
|
|
if n.Key != nil {
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.wantComparable = nodeContains(n.Key, c.pos)
|
2019-09-17 15:59:27 -06:00
|
|
|
} else {
|
|
|
|
// If the key is empty, assume we are completing the key if
|
|
|
|
// pos is directly after the "map[".
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.wantComparable = c.pos == n.Pos()+token.Pos(len("map["))
|
2019-09-17 15:59:27 -06:00
|
|
|
}
|
|
|
|
break Nodes
|
2020-01-27 11:04:02 -07:00
|
|
|
case *ast.ValueSpec:
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.wantTypeName = nodeContains(n.Type, c.pos)
|
2020-01-27 11:04:02 -07:00
|
|
|
break Nodes
|
internal/lsp/source: complete keywords as types
Offer "struct", "interface", "map", "chan", and "func" keywords when
we expect a type. For example "var foo i<>" will offer "interface".
Because "struct" and "interface" are more often used when declaring
named types, they get a higher score in type declarations. Otherwise,
"map", "chan" and "func" get a higher score.
I also got rid of the special keyword scoring. Now keywords just use
stdScore and highScore. This makes the interplay with other types of
candidates more predictable. Keywords are offered in pretty limited
contexts, so I don't think they will be annoying.
Finally, keyword candidate score is now to be scaled properly based on
how well they match the prefix. Previously they weren't penalized for
not matching well, so there were probably some situations where
keywords were ranked too high.
Updates golang/go#34009.
Change-Id: I0b659c00a8503cd72da28853dfe54fcb67f734ae
Reviewed-on: https://go-review.googlesource.com/c/tools/+/220503
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-02-21 21:55:54 -07:00
|
|
|
case *ast.TypeSpec:
|
2020-08-09 22:42:16 -06:00
|
|
|
inf.wantTypeName = nodeContains(n.Type, c.pos)
|
2019-06-17 12:11:13 -06:00
|
|
|
default:
|
2020-08-09 22:42:16 -06:00
|
|
|
if breaksExpectedTypeInference(p, c.pos) {
|
2019-09-13 13:15:53 -06:00
|
|
|
return typeNameInference{}
|
2019-05-15 16:29:19 -06:00
|
|
|
}
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-09 22:42:16 -06:00
|
|
|
return inf
|
2019-06-17 12:11:13 -06:00
|
|
|
}
|
|
|
|
|
2019-12-22 10:58:14 -07:00
|
|
|
func (c *completer) fakeObj(T types.Type) *types.Var {
|
|
|
|
return types.NewVar(token.NoPos, c.pkg.GetTypes(), "", T)
|
internal/lsp: add literal completion candidates
Add support for literal completion candidates such as "[]int{}" or
"make([]int, 0)". We support both named and unnamed types. I used the
existing type matching logic, so, for example, if the expected type is
an interface, we will suggest literal candidates that implement the
interface.
The literal candidates have a lower score than normal matching
candidates, so they shouldn't be disruptive in cases where you don't
want a literal candidate.
This commit adds support for slice, array, struct, map, and channel
literal candidates since they are pretty similar. Functions will be
supported in a subsequent commit.
I also added support for setting a snippet's final tab stop. This is
useful if you want the cursor to end up somewhere other than the
character after the snippet.
Change-Id: Id3b74260fff4d61703989b422267021b00cec005
Reviewed-on: https://go-review.googlesource.com/c/tools/+/193698
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-09-04 17:12:37 -06:00
|
|
|
}
|
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
// anyCandType reports whether f returns true for any candidate type
|
|
|
|
// derivable from c. For example, from "foo" we might derive "&foo",
|
|
|
|
// and "foo()".
|
|
|
|
func (c *candidate) anyCandType(f func(t types.Type, addressable bool) bool) bool {
|
|
|
|
if c.obj == nil || c.obj.Type() == nil {
|
2020-01-17 14:20:39 -07:00
|
|
|
return false
|
2019-11-01 11:50:21 -06:00
|
|
|
}
|
2019-06-19 16:24:05 -06:00
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
objType := c.obj.Type()
|
2019-06-19 16:24:05 -06:00
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
if f(objType, c.addressable) {
|
2019-06-19 16:24:05 -06:00
|
|
|
return true
|
|
|
|
}
|
2019-06-19 10:12:57 -06:00
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
// If c is a func type with a single result, offer the result type.
|
|
|
|
if sig, ok := objType.Underlying().(*types.Signature); ok {
|
|
|
|
if sig.Results().Len() == 1 && f(sig.Results().At(0).Type(), false) {
|
|
|
|
// Mark the candidate so we know to append "()" when formatting.
|
|
|
|
c.expandFuncCall = true
|
2019-06-19 16:24:05 -06:00
|
|
|
return true
|
|
|
|
}
|
2019-06-17 12:11:13 -06:00
|
|
|
}
|
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
var (
|
|
|
|
seenPtrTypes map[types.Type]bool
|
|
|
|
ptrType = objType
|
|
|
|
ptrDepth int
|
|
|
|
)
|
|
|
|
|
|
|
|
// Check if dereferencing c would match our type inference. We loop
|
|
|
|
// since c could have arbitrary levels of pointerness.
|
|
|
|
for {
|
|
|
|
ptr, ok := ptrType.Underlying().(*types.Pointer)
|
|
|
|
if !ok {
|
|
|
|
break
|
2019-11-15 17:14:15 -07:00
|
|
|
}
|
2019-11-03 16:57:27 -07:00
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
ptrDepth++
|
2019-12-22 10:58:14 -07:00
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
// Avoid pointer type cycles.
|
|
|
|
if seenPtrTypes[ptrType] {
|
|
|
|
break
|
|
|
|
}
|
2020-02-07 14:26:29 -07:00
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
if _, named := ptrType.(*types.Named); named {
|
2020-02-07 14:26:29 -07:00
|
|
|
// Lazily allocate "seen" since it isn't used normally.
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
if seenPtrTypes == nil {
|
|
|
|
seenPtrTypes = make(map[types.Type]bool)
|
2020-02-07 14:26:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Track named pointer types we have seen to detect cycles.
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
seenPtrTypes[ptrType] = true
|
2020-02-07 14:26:29 -07:00
|
|
|
}
|
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
if f(ptr.Elem(), false) {
|
2020-01-18 14:33:26 -07:00
|
|
|
// Mark the candidate so we know to prepend "*" when formatting.
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
c.dereference = ptrDepth
|
2020-01-18 14:33:26 -07:00
|
|
|
return true
|
|
|
|
}
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
|
|
|
|
ptrType = ptr.Elem()
|
2020-01-18 14:33:26 -07:00
|
|
|
}
|
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
// Check if c is addressable and a pointer to c matches our type inference.
|
|
|
|
if c.addressable && f(types.NewPointer(objType), false) {
|
2019-12-22 10:58:14 -07:00
|
|
|
// Mark the candidate so we know to prepend "&" when formatting.
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
c.takeAddress = true
|
2019-12-22 10:58:14 -07:00
|
|
|
return true
|
2019-06-27 16:37:50 -06:00
|
|
|
}
|
|
|
|
|
2019-06-17 12:11:13 -06:00
|
|
|
return false
|
2018-11-07 18:57:08 -07:00
|
|
|
}
|
2019-06-26 16:30:01 -06:00
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
// matchingCandidate reports whether cand matches our type inferences.
|
2020-08-05 23:17:55 -06:00
|
|
|
// It mutates cand's score in certain cases.
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
func (c *completer) matchingCandidate(cand *candidate) bool {
|
|
|
|
if isTypeName(cand.obj) {
|
|
|
|
return c.matchingTypeName(cand)
|
|
|
|
} else if c.wantTypeName() {
|
|
|
|
// If we want a type, a non-type object never matches.
|
2020-01-20 15:50:36 -07:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
if c.inference.candTypeMatches(cand) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
candType := cand.obj.Type()
|
2020-01-20 15:50:36 -07:00
|
|
|
if candType == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
if sig, ok := candType.Underlying().(*types.Signature); ok {
|
|
|
|
if c.inference.assigneesMatch(cand, sig) {
|
|
|
|
// Invoke the candidate if its results are multi-assignable.
|
|
|
|
cand.expandFuncCall = true
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default to invoking *types.Func candidates. This is so function
|
|
|
|
// completions in an empty statement (or other cases with no expected type)
|
|
|
|
// are invoked by default.
|
|
|
|
cand.expandFuncCall = isFunc(cand.obj)
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-08-06 23:33:47 -06:00
|
|
|
// objChainMatches reports whether cand combined with the surrounding
|
|
|
|
// object prefix matches chain.
|
|
|
|
func (c *completer) objChainMatches(cand types.Object, chain []types.Object) bool {
|
|
|
|
// For example, when completing:
|
|
|
|
//
|
|
|
|
// foo.ba<>
|
|
|
|
//
|
|
|
|
// If we are considering the deep candidate "bar.baz", cand is baz,
|
|
|
|
// objChain is [foo] and deepChain is [bar]. We would match the
|
|
|
|
// chain [foo, bar, baz].
|
|
|
|
|
|
|
|
if len(chain) != len(c.inference.objChain)+len(c.deepState.chain)+1 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if chain[len(chain)-1] != cand {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, o := range c.inference.objChain {
|
|
|
|
if chain[i] != o {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, o := range c.deepState.chain {
|
|
|
|
if chain[i+len(c.inference.objChain)] != o {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
// candTypeMatches reports whether cand makes a good completion
|
|
|
|
// candidate given the candidate inference. cand's score may be
|
|
|
|
// mutated to downrank the candidate in certain situations.
|
|
|
|
func (ci *candidateInference) candTypeMatches(cand *candidate) bool {
|
2020-08-11 15:16:46 -06:00
|
|
|
var (
|
|
|
|
expTypes = make([]types.Type, 0, 2)
|
|
|
|
variadicType types.Type
|
|
|
|
)
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
if ci.objType != nil {
|
|
|
|
expTypes = append(expTypes, ci.objType)
|
2020-08-03 17:50:08 -06:00
|
|
|
|
2020-08-03 23:16:11 -06:00
|
|
|
if ci.variadic {
|
2020-08-11 15:16:46 -06:00
|
|
|
variadicType = types.NewSlice(ci.objType)
|
|
|
|
expTypes = append(expTypes, variadicType)
|
2020-08-03 23:16:11 -06:00
|
|
|
}
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return cand.anyCandType(func(candType types.Type, addressable bool) bool {
|
|
|
|
// Take into account any type modifiers on the expected type.
|
|
|
|
candType = ci.applyTypeModifiers(candType, addressable)
|
|
|
|
if candType == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if ci.convertibleTo != nil && types.ConvertibleTo(candType, ci.convertibleTo) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-08-03 17:50:08 -06:00
|
|
|
for _, expType := range expTypes {
|
|
|
|
if isEmptyInterface(expType) {
|
|
|
|
continue
|
2020-08-01 15:18:10 -06:00
|
|
|
}
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
|
|
|
|
matches, untyped := ci.typeMatches(expType, candType)
|
|
|
|
if !matches {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-08-11 15:16:46 -06:00
|
|
|
if expType == variadicType {
|
|
|
|
cand.variadic = true
|
|
|
|
}
|
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
// Lower candidate score for untyped conversions. This avoids
|
|
|
|
// ranking untyped constants above candidates with an exact type
|
|
|
|
// match. Don't lower score of builtin constants, e.g. "true".
|
|
|
|
if untyped && !types.Identical(candType, expType) && cand.obj.Parent() != types.Universe {
|
|
|
|
cand.score /= 2
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-08-03 17:50:08 -06:00
|
|
|
// If we don't have a specific expected type, fall back to coarser
|
|
|
|
// object kind checks.
|
|
|
|
if ci.objType == nil || isEmptyInterface(ci.objType) {
|
|
|
|
// If we were able to apply type modifiers to our candidate type,
|
|
|
|
// count that as a match. For example:
|
|
|
|
//
|
|
|
|
// var foo chan int
|
|
|
|
// <-fo<>
|
|
|
|
//
|
|
|
|
// We were able to apply the "<-" type modifier to "foo", so "foo"
|
|
|
|
// matches.
|
|
|
|
if len(ci.modifiers) > 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we didn't have an exact type match, check if our object kind
|
|
|
|
// matches.
|
|
|
|
if ci.kindMatches(candType) {
|
|
|
|
if ci.objKind == kindFunc {
|
|
|
|
cand.expandFuncCall = true
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
return false
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// typeMatches reports whether an object of candType makes a good
|
|
|
|
// completion candidate given the expected type expType. It also
|
|
|
|
// returns a second bool which is true if both types are basic types
|
|
|
|
// of the same kind, and at least one is untyped.
|
|
|
|
func (ci *candidateInference) typeMatches(expType, candType types.Type) (bool, bool) {
|
2020-01-20 15:50:36 -07:00
|
|
|
// Handle untyped values specially since AssignableTo gives false negatives
|
|
|
|
// for them (see https://golang.org/issue/32146).
|
|
|
|
if candBasic, ok := candType.Underlying().(*types.Basic); ok {
|
|
|
|
if wantBasic, ok := expType.Underlying().(*types.Basic); ok {
|
|
|
|
// Make sure at least one of them is untyped.
|
|
|
|
if isUntyped(candType) || isUntyped(expType) {
|
|
|
|
// Check that their constant kind (bool|int|float|complex|string) matches.
|
|
|
|
// This doesn't take into account the constant value, so there will be some
|
|
|
|
// false positives due to integer sign and overflow.
|
|
|
|
if candBasic.Info()&types.IsConstType == wantBasic.Info()&types.IsConstType {
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
return true, true
|
2020-01-20 15:50:36 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AssignableTo covers the case where the types are equal, but also handles
|
|
|
|
// cases like assigning a concrete type to an interface type.
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
return types.AssignableTo(candType, expType), false
|
2020-01-20 15:50:36 -07:00
|
|
|
}
|
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
// kindMatches reports whether candType's kind matches our expected
|
|
|
|
// kind (e.g. slice, map, etc.).
|
|
|
|
func (ci *candidateInference) kindMatches(candType types.Type) bool {
|
2020-08-03 17:50:08 -06:00
|
|
|
return ci.objKind > 0 && ci.objKind&candKind(candType) > 0
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
}
|
2020-01-20 15:50:36 -07:00
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
// assigneesMatch reports whether an invocation of sig matches the
|
|
|
|
// number and type of any assignees.
|
|
|
|
func (ci *candidateInference) assigneesMatch(cand *candidate, sig *types.Signature) bool {
|
2020-01-20 15:50:36 -07:00
|
|
|
if len(ci.assignees) == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
// Uniresult functions are always usable and are handled by the
|
|
|
|
// normal, non-assignees type matching logic.
|
|
|
|
if sig.Results().Len() == 1 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-01-20 17:19:26 -07:00
|
|
|
var numberOfResultsCouldMatch bool
|
|
|
|
if ci.variadicAssignees {
|
|
|
|
numberOfResultsCouldMatch = sig.Results().Len() >= len(ci.assignees)-1
|
|
|
|
} else {
|
|
|
|
numberOfResultsCouldMatch = sig.Results().Len() == len(ci.assignees)
|
|
|
|
}
|
|
|
|
|
2020-01-20 15:50:36 -07:00
|
|
|
// If our signature doesn't return the right number of values, it's
|
|
|
|
// not a match, so downrank it. For example:
|
|
|
|
//
|
|
|
|
// var foo func() (int, int)
|
|
|
|
// a, b, c := <> // downrank "foo()" since it only returns two values
|
2020-01-20 17:19:26 -07:00
|
|
|
if !numberOfResultsCouldMatch {
|
2020-01-20 15:50:36 -07:00
|
|
|
cand.score /= 2
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// If at least one assignee has a valid type, and all valid
|
|
|
|
// assignees match the corresponding sig result value, the signature
|
|
|
|
// is a match.
|
|
|
|
allMatch := false
|
2020-01-20 17:19:26 -07:00
|
|
|
for i := 0; i < sig.Results().Len(); i++ {
|
|
|
|
var assignee types.Type
|
|
|
|
|
|
|
|
// If we are completing into variadic parameters, deslice the
|
|
|
|
// expected variadic type.
|
|
|
|
if ci.variadicAssignees && i >= len(ci.assignees)-1 {
|
|
|
|
assignee = ci.assignees[len(ci.assignees)-1]
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
if elem := deslice(assignee); elem != nil {
|
|
|
|
assignee = elem
|
2020-01-20 17:19:26 -07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
assignee = ci.assignees[i]
|
2020-01-20 15:50:36 -07:00
|
|
|
}
|
2020-01-20 17:19:26 -07:00
|
|
|
|
2020-02-25 20:05:59 -07:00
|
|
|
if assignee == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
internal/lsp/source: untangle completion type comparison
The code to check if a candidate object matches our candidate
inference had become complicated, messy, and in some cases incorrect.
The main source of the complexity is the "derived" expected and
candidate types. When considering a candidate object "foo", we also
consider "&foo", "foo()", and "*foo", as appropriate. On the expected
side of things, when completing the a variadic function parameter we
expect either the variadic slice type and the scalar element type.
The code had grown organically to handle the expanding concerns, but
that resulted in confused code that didn't handle the interplay
between the various facets of candidate inference.
For example, we were inappropriately invoking func candidates when
completing variadic args:
func foo(...func())
func bar() {}
foo(bar<>) // oops - expanded to "bar()"
and we weren't type matching functions properly as builtin args:
func myMap() map[string]int { ... }
delete(myM<>) // we weren't preferring (or invoking) "myMap()"
We also had methods like "typeMatches" which took both a "candidate"
object and a "candType" type, which doesn't make sense because the
candidate contains the type already.
Now instead we explicitly iterate over all the derived candidate and
expected types so they are treated the same. There are still some
warts left but I think this is a step in the right direction.
Change-Id: If84a84b34a8fb771a32231f7ab64ca192f611b3d
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218877
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-02-08 20:59:28 -07:00
|
|
|
allMatch, _ = ci.typeMatches(assignee, sig.Results().At(i).Type())
|
2020-01-20 15:50:36 -07:00
|
|
|
if !allMatch {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return allMatch
|
|
|
|
}
|
|
|
|
|
2019-06-26 16:30:01 -06:00
|
|
|
func (c *completer) matchingTypeName(cand *candidate) bool {
|
|
|
|
if !c.wantTypeName() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-12-27 09:57:52 -07:00
|
|
|
typeMatches := func(candType types.Type) bool {
|
|
|
|
// Take into account any type name modifier prefixes.
|
2020-01-17 14:53:10 -07:00
|
|
|
candType = c.inference.applyTypeNameModifiers(candType)
|
2019-12-27 09:57:52 -07:00
|
|
|
|
2020-01-17 14:53:10 -07:00
|
|
|
if from := c.inference.typeName.assertableFrom; from != nil {
|
2019-12-27 09:57:52 -07:00
|
|
|
// Don't suggest the starting type in type assertions. For example,
|
|
|
|
// if "foo" is an io.Writer, don't suggest "foo.(io.Writer)".
|
|
|
|
if types.Identical(from, candType) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if intf, ok := from.Underlying().(*types.Interface); ok {
|
|
|
|
if !types.AssertableTo(intf, candType) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-06-26 16:30:01 -06:00
|
|
|
|
2020-01-17 14:53:10 -07:00
|
|
|
if c.inference.typeName.wantComparable && !types.Comparable(candType) {
|
2019-06-26 16:30:01 -06:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-08-05 23:17:55 -06:00
|
|
|
// Skip this type if it has already been used in another type
|
|
|
|
// switch case.
|
|
|
|
for _, seen := range c.inference.typeName.seenTypeSwitchCases {
|
|
|
|
if types.Identical(candType, seen) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-27 09:57:52 -07:00
|
|
|
// We can expect a type name and have an expected type in cases like:
|
|
|
|
//
|
|
|
|
// var foo []int
|
|
|
|
// foo = []i<>
|
|
|
|
//
|
|
|
|
// Where our expected type is "[]int", and we expect a type name.
|
2020-01-17 14:53:10 -07:00
|
|
|
if c.inference.objType != nil {
|
|
|
|
return types.AssignableTo(candType, c.inference.objType)
|
2019-06-26 16:30:01 -06:00
|
|
|
}
|
2019-12-27 09:57:52 -07:00
|
|
|
|
|
|
|
// Default to saying any type name is a match.
|
|
|
|
return true
|
2019-06-26 16:30:01 -06:00
|
|
|
}
|
|
|
|
|
2020-08-05 23:17:55 -06:00
|
|
|
t := cand.obj.Type()
|
|
|
|
|
|
|
|
if typeMatches(t) {
|
2019-12-27 09:57:52 -07:00
|
|
|
return true
|
2019-09-17 15:59:27 -06:00
|
|
|
}
|
|
|
|
|
2020-08-05 23:17:55 -06:00
|
|
|
if !isInterface(t) && typeMatches(types.NewPointer(t)) {
|
2020-08-09 22:42:16 -06:00
|
|
|
if c.inference.typeName.compLitType {
|
|
|
|
// If we are completing a composite literal type as in
|
|
|
|
// "foo<>{}", to make a pointer we must prepend "&".
|
|
|
|
cand.takeAddress = true
|
|
|
|
} else {
|
|
|
|
// If we are completing a normal type name such as "foo<>", to
|
|
|
|
// make a pointer we must prepend "*".
|
|
|
|
cand.makePointer = true
|
|
|
|
}
|
2019-12-27 09:57:52 -07:00
|
|
|
return true
|
2019-09-13 13:15:53 -06:00
|
|
|
}
|
|
|
|
|
2019-12-27 09:57:52 -07:00
|
|
|
return false
|
2019-06-26 16:30:01 -06:00
|
|
|
}
|
2019-12-30 17:49:53 -07:00
|
|
|
|
2020-08-03 17:50:08 -06:00
|
|
|
var (
|
|
|
|
// "interface { Error() string }" (i.e. error)
|
|
|
|
errorIntf = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
|
|
|
|
|
|
|
|
// "interface { String() string }" (i.e. fmt.Stringer)
|
|
|
|
stringerIntf = types.NewInterfaceType([]*types.Func{
|
|
|
|
types.NewFunc(token.NoPos, nil, "String", types.NewSignature(
|
|
|
|
nil,
|
|
|
|
nil,
|
|
|
|
types.NewTuple(types.NewParam(token.NoPos, nil, "", types.Typ[types.String])),
|
|
|
|
false,
|
|
|
|
)),
|
|
|
|
}, nil).Complete()
|
|
|
|
|
|
|
|
byteType = types.Universe.Lookup("byte").Type()
|
|
|
|
)
|
|
|
|
|
2019-12-30 17:49:53 -07:00
|
|
|
// candKind returns the objKind of candType, if any.
|
|
|
|
func candKind(candType types.Type) objKind {
|
2020-08-03 17:50:08 -06:00
|
|
|
var kind objKind
|
|
|
|
|
2019-12-30 17:49:53 -07:00
|
|
|
switch t := candType.Underlying().(type) {
|
|
|
|
case *types.Array:
|
2020-08-03 17:50:08 -06:00
|
|
|
kind |= kindArray
|
|
|
|
if t.Elem() == byteType {
|
|
|
|
kind |= kindBytes
|
|
|
|
}
|
2019-12-30 17:49:53 -07:00
|
|
|
case *types.Slice:
|
2020-08-03 17:50:08 -06:00
|
|
|
kind |= kindSlice
|
|
|
|
if t.Elem() == byteType {
|
|
|
|
kind |= kindBytes
|
|
|
|
}
|
2019-12-30 17:49:53 -07:00
|
|
|
case *types.Chan:
|
2020-08-03 17:50:08 -06:00
|
|
|
kind |= kindChan
|
2019-12-30 17:49:53 -07:00
|
|
|
case *types.Map:
|
2020-08-03 17:50:08 -06:00
|
|
|
kind |= kindMap
|
2019-12-30 17:49:53 -07:00
|
|
|
case *types.Pointer:
|
2020-08-03 17:50:08 -06:00
|
|
|
kind |= kindPtr
|
|
|
|
|
2019-12-30 17:49:53 -07:00
|
|
|
// Some builtins handle array pointers as arrays, so just report a pointer
|
|
|
|
// to an array as an array.
|
|
|
|
if _, isArray := t.Elem().Underlying().(*types.Array); isArray {
|
2020-08-03 17:50:08 -06:00
|
|
|
kind |= kindArray
|
2019-12-30 17:49:53 -07:00
|
|
|
}
|
|
|
|
case *types.Basic:
|
2020-08-03 17:50:08 -06:00
|
|
|
switch info := t.Info(); {
|
|
|
|
case info&types.IsString > 0:
|
|
|
|
kind |= kindString
|
|
|
|
case info&types.IsInteger > 0:
|
|
|
|
kind |= kindInt
|
|
|
|
case info&types.IsFloat > 0:
|
|
|
|
kind |= kindFloat
|
|
|
|
case info&types.IsComplex > 0:
|
|
|
|
kind |= kindComplex
|
|
|
|
case info&types.IsBoolean > 0:
|
|
|
|
kind |= kindBool
|
2019-12-30 17:49:53 -07:00
|
|
|
}
|
2020-08-01 15:18:10 -06:00
|
|
|
case *types.Signature:
|
|
|
|
return kindFunc
|
2019-12-30 17:49:53 -07:00
|
|
|
}
|
|
|
|
|
2020-08-03 17:50:08 -06:00
|
|
|
if types.Implements(candType, errorIntf) {
|
|
|
|
kind |= kindError
|
|
|
|
}
|
|
|
|
|
|
|
|
if types.Implements(candType, stringerIntf) {
|
|
|
|
kind |= kindStringer
|
|
|
|
}
|
|
|
|
|
|
|
|
return kind
|
2019-12-30 17:49:53 -07:00
|
|
|
}
|