aboutsummaryrefslogtreecommitdiff
path: root/gopls/internal/lsp/source/completion
diff options
context:
space:
mode:
Diffstat (limited to 'gopls/internal/lsp/source/completion')
-rw-r--r--gopls/internal/lsp/source/completion/builtin.go147
-rw-r--r--gopls/internal/lsp/source/completion/completion.go3252
-rw-r--r--gopls/internal/lsp/source/completion/deep_completion.go362
-rw-r--r--gopls/internal/lsp/source/completion/deep_completion_test.go33
-rw-r--r--gopls/internal/lsp/source/completion/definition.go160
-rw-r--r--gopls/internal/lsp/source/completion/format.go338
-rw-r--r--gopls/internal/lsp/source/completion/fuzz.go142
-rw-r--r--gopls/internal/lsp/source/completion/keywords.go154
-rw-r--r--gopls/internal/lsp/source/completion/labels.go112
-rw-r--r--gopls/internal/lsp/source/completion/literal.go592
-rw-r--r--gopls/internal/lsp/source/completion/package.go351
-rw-r--r--gopls/internal/lsp/source/completion/package_test.go81
-rw-r--r--gopls/internal/lsp/source/completion/postfix_snippets.go471
-rw-r--r--gopls/internal/lsp/source/completion/printf.go172
-rw-r--r--gopls/internal/lsp/source/completion/printf_test.go72
-rw-r--r--gopls/internal/lsp/source/completion/snippet.go116
-rw-r--r--gopls/internal/lsp/source/completion/statements.go361
-rw-r--r--gopls/internal/lsp/source/completion/util.go344
-rw-r--r--gopls/internal/lsp/source/completion/util_test.go28
19 files changed, 7288 insertions, 0 deletions
diff --git a/gopls/internal/lsp/source/completion/builtin.go b/gopls/internal/lsp/source/completion/builtin.go
new file mode 100644
index 000000000..39732d864
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/builtin.go
@@ -0,0 +1,147 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "context"
+ "go/ast"
+ "go/types"
+)
+
+// builtinArgKind determines the expected object kind for a builtin
+// argument. It attempts to use the AST hints from builtin.go where
+// possible.
+func (c *completer) builtinArgKind(ctx context.Context, obj types.Object, call *ast.CallExpr) objKind {
+ builtin, err := c.snapshot.BuiltinFile(ctx)
+ if err != nil {
+ return 0
+ }
+ exprIdx := exprAtPos(c.pos, call.Args)
+
+ builtinObj := builtin.File.Scope.Lookup(obj.Name())
+ if builtinObj == nil {
+ return 0
+ }
+ decl, ok := builtinObj.Decl.(*ast.FuncDecl)
+ if !ok || exprIdx >= len(decl.Type.Params.List) {
+ return 0
+ }
+
+ switch ptyp := decl.Type.Params.List[exprIdx].Type.(type) {
+ case *ast.ChanType:
+ return kindChan
+ case *ast.ArrayType:
+ return kindSlice
+ case *ast.MapType:
+ return kindMap
+ case *ast.Ident:
+ switch ptyp.Name {
+ case "Type":
+ switch obj.Name() {
+ case "make":
+ return kindChan | kindSlice | kindMap
+ case "len":
+ return kindSlice | kindMap | kindArray | kindString | kindChan
+ case "cap":
+ return kindSlice | kindArray | kindChan
+ }
+ }
+ }
+
+ return 0
+}
+
+// builtinArgType infers the type of an argument to a builtin
+// function. parentInf is the inferred type info for the builtin
+// call's parent node.
+func (c *completer) builtinArgType(obj types.Object, call *ast.CallExpr, parentInf candidateInference) candidateInference {
+ var (
+ exprIdx = exprAtPos(c.pos, call.Args)
+
+ // Propagate certain properties from our parent's inference.
+ inf = candidateInference{
+ typeName: parentInf.typeName,
+ modifiers: parentInf.modifiers,
+ }
+ )
+
+ switch obj.Name() {
+ case "append":
+ if exprIdx <= 0 {
+ // Infer first append() arg type as apparent return type of
+ // append().
+ inf.objType = parentInf.objType
+ if parentInf.variadic {
+ inf.objType = types.NewSlice(inf.objType)
+ }
+ break
+ }
+
+ // For non-initial append() args, infer slice type from the first
+ // append() arg, or from parent context.
+ if len(call.Args) > 0 {
+ inf.objType = c.pkg.GetTypesInfo().TypeOf(call.Args[0])
+ }
+ if inf.objType == nil {
+ inf.objType = parentInf.objType
+ }
+ if inf.objType == nil {
+ break
+ }
+
+ inf.objType = deslice(inf.objType)
+
+ // Check if we are completing the variadic append() param.
+ inf.variadic = exprIdx == 1 && len(call.Args) <= 2
+
+ // Penalize the first append() argument as a candidate. You
+ // don't normally append a slice to itself.
+ if sliceChain := objChain(c.pkg.GetTypesInfo(), call.Args[0]); len(sliceChain) > 0 {
+ inf.penalized = append(inf.penalized, penalizedObj{objChain: sliceChain, penalty: 0.9})
+ }
+ case "delete":
+ if exprIdx > 0 && len(call.Args) > 0 {
+ // Try to fill in expected type of map key.
+ firstArgType := c.pkg.GetTypesInfo().TypeOf(call.Args[0])
+ if firstArgType != nil {
+ if mt, ok := firstArgType.Underlying().(*types.Map); ok {
+ inf.objType = mt.Key()
+ }
+ }
+ }
+ case "copy":
+ var t1, t2 types.Type
+ if len(call.Args) > 0 {
+ t1 = c.pkg.GetTypesInfo().TypeOf(call.Args[0])
+ if len(call.Args) > 1 {
+ t2 = c.pkg.GetTypesInfo().TypeOf(call.Args[1])
+ }
+ }
+
+ // Fill in expected type of either arg if the other is already present.
+ if exprIdx == 1 && t1 != nil {
+ inf.objType = t1
+ } else if exprIdx == 0 && t2 != nil {
+ inf.objType = t2
+ }
+ case "new":
+ inf.typeName.wantTypeName = true
+ if parentInf.objType != nil {
+ // Expected type for "new" is the de-pointered parent type.
+ if ptr, ok := parentInf.objType.Underlying().(*types.Pointer); ok {
+ inf.objType = ptr.Elem()
+ }
+ }
+ case "make":
+ if exprIdx == 0 {
+ inf.typeName.wantTypeName = true
+ inf.objType = parentInf.objType
+ } else {
+ inf.objType = types.Typ[types.UntypedInt]
+ }
+ }
+
+ return inf
+}
diff --git a/gopls/internal/lsp/source/completion/completion.go b/gopls/internal/lsp/source/completion/completion.go
new file mode 100644
index 000000000..f8c7654f6
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/completion.go
@@ -0,0 +1,3252 @@
+// 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.
+
+// Package completion provides core functionality for code completion in Go
+// editors and tools.
+package completion
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "go/ast"
+ "go/constant"
+ "go/parser"
+ "go/scanner"
+ "go/token"
+ "go/types"
+ "math"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "time"
+ "unicode"
+
+ "golang.org/x/sync/errgroup"
+ "golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/gopls/internal/lsp/protocol"
+ "golang.org/x/tools/gopls/internal/lsp/safetoken"
+ "golang.org/x/tools/gopls/internal/lsp/snippet"
+ "golang.org/x/tools/gopls/internal/lsp/source"
+ "golang.org/x/tools/gopls/internal/span"
+ "golang.org/x/tools/internal/event"
+ "golang.org/x/tools/internal/fuzzy"
+ "golang.org/x/tools/internal/imports"
+ "golang.org/x/tools/internal/typeparams"
+)
+
+// A CompletionItem represents a possible completion suggested by the algorithm.
+type CompletionItem struct {
+
+ // Invariant: CompletionItem does not refer to syntax or types.
+
+ // Label is the primary text the user sees for this completion item.
+ Label string
+
+ // Detail is supplemental information to present to the user.
+ // This often contains the type or return type of the completion item.
+ Detail string
+
+ // 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
+
+ Kind protocol.CompletionItemKind
+ Tags []protocol.CompletionItemTag
+ Deprecated bool // Deprecated, prefer Tags if available
+
+ // 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).
+ AdditionalTextEdits []protocol.TextEdit
+
+ // 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
+
+ // Score is the internal relevance score.
+ // A higher score indicates that this completion item is more relevant.
+ Score float64
+
+ // 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:
+ //
+ // func foo(a, b, c int)
+ //
+ // would be:
+ //
+ // foo(${1:a int}, ${2: b int}, ${3: c int})
+ //
+ // If Placeholders is false in the CompletionOptions, the above
+ // snippet would instead be:
+ //
+ // foo(${1:})
+ snippet *snippet.Builder
+
+ // Documentation is the documentation for the completion item.
+ Documentation string
+
+ // isSlice reports whether the underlying type of the object
+ // from which this candidate was derived is a slice.
+ // (Used to complete append() calls.)
+ isSlice bool
+}
+
+// completionOptions holds completion specific configuration.
+type completionOptions struct {
+ unimported bool
+ documentation bool
+ fullDocumentation bool
+ placeholders bool
+ literal bool
+ snippets bool
+ postfix bool
+ matcher source.Matcher
+ budget time.Duration
+}
+
+// Snippet is a convenience returns the snippet if available, otherwise
+// the InsertText.
+// used for an item, depending on if the callee wants placeholders or not.
+func (i *CompletionItem) Snippet() string {
+ if i.snippet != nil {
+ return i.snippet.String()
+ }
+ return i.InsertText
+}
+
+// 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
+)
+
+// 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.
+type matcher interface {
+ Score(candidateLabel string) (score float32)
+}
+
+// prefixMatcher implements case sensitive prefix matching.
+type prefixMatcher string
+
+func (pm prefixMatcher) Score(candidateLabel string) float32 {
+ 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)) {
+ return 1
+ }
+ return -1
+}
+
+// completer contains the necessary information for a single completion request.
+type completer struct {
+ snapshot source.Snapshot
+ pkg source.Package
+ qf types.Qualifier // for qualifying typed expressions
+ mq source.MetadataQualifier // for syntactic qualifying
+ opts *completionOptions
+
+ // completionContext contains information about the trigger for this
+ // completion request.
+ completionContext completionContext
+
+ // fh is a handle to the file associated with this completion request.
+ fh source.FileHandle
+
+ // 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
+
+ // (tokFile, pos) is the position at which the request was triggered.
+ tokFile *token.File
+ pos token.Pos
+
+ // 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
+
+ // completionCallbacks is a list of callbacks to collect completions that
+ // require expensive operations. This includes operations where we search
+ // through the entire module cache.
+ completionCallbacks []func(opts *imports.Options) error
+
+ // surrounding describes the identifier surrounding the position.
+ surrounding *Selection
+
+ // inference contains information we've inferred about ideal
+ // candidates such as the candidate's type.
+ inference candidateInference
+
+ // enclosingFunc contains information about the function enclosing
+ // the position.
+ enclosingFunc *funcInfo
+
+ // enclosingCompositeLiteral contains information about the composite literal
+ // enclosing the position.
+ enclosingCompositeLiteral *compLitInfo
+
+ // deepState contains the current state of our deep completion search.
+ deepState deepCompletionState
+
+ // matcher matches the candidates against the surrounding prefix.
+ matcher matcher
+
+ // 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
+
+ // mapper converts the positions in the file from which the completion originated.
+ mapper *protocol.Mapper
+
+ // startTime is when we started processing this completion request. It does
+ // not include any time the request spent in the queue.
+ startTime time.Time
+
+ // scopes contains all scopes defined by nodes in our path,
+ // including nil values for nodes that don't defined a scope. It
+ // also includes our package scope and the universal scope at the
+ // end.
+ scopes []*types.Scope
+}
+
+// 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
+}
+
+type compLitInfo struct {
+ // cl is the *ast.CompositeLit enclosing the position.
+ cl *ast.CompositeLit
+
+ // clType is the type of cl.
+ clType types.Type
+
+ // kv is the *ast.KeyValueExpr enclosing the position, if any.
+ kv *ast.KeyValueExpr
+
+ // 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
+}
+
+type importInfo struct {
+ importPath string
+ name string
+}
+
+type methodSetKey struct {
+ typ types.Type
+ addressable bool
+}
+
+type completionContext struct {
+ // triggerCharacter is the character used to trigger completion at current
+ // position, if any.
+ triggerCharacter string
+
+ // triggerKind is information about how a completion was triggered.
+ triggerKind protocol.CompletionTriggerKind
+
+ // commentCompletion is true if we are completing a comment.
+ commentCompletion bool
+
+ // packageCompletion is true if we are completing a package name.
+ packageCompletion bool
+}
+
+// A Selection represents the cursor position and surrounding identifier.
+type Selection struct {
+ content string
+ tokFile *token.File
+ start, end, cursor token.Pos // relative to rng.TokFile
+ mapper *protocol.Mapper
+}
+
+func (p Selection) Content() string {
+ return p.content
+}
+
+func (p Selection) Range() (protocol.Range, error) {
+ return p.mapper.PosRange(p.tokFile, p.start, p.end)
+}
+
+func (p Selection) Prefix() string {
+ return p.content[:p.cursor-p.start]
+}
+
+func (p Selection) Suffix() string {
+ return p.content[p.cursor-p.start:]
+}
+
+func (c *completer) setSurrounding(ident *ast.Ident) {
+ if c.surrounding != nil {
+ return
+ }
+ if !(ident.Pos() <= c.pos && c.pos <= ident.End()) {
+ return
+ }
+
+ c.surrounding = &Selection{
+ content: ident.Name,
+ cursor: c.pos,
+ // Overwrite the prefix only.
+ tokFile: c.tokFile,
+ start: ident.Pos(),
+ end: ident.End(),
+ mapper: c.mapper,
+ }
+
+ c.setMatcherFromPrefix(c.surrounding.Prefix())
+}
+
+func (c *completer) setMatcherFromPrefix(prefix string) {
+ switch c.opts.matcher {
+ case source.Fuzzy:
+ c.matcher = fuzzy.NewMatcher(prefix)
+ case source.CaseSensitive:
+ c.matcher = prefixMatcher(prefix)
+ default:
+ c.matcher = insensitivePrefixMatcher(strings.ToLower(prefix))
+ }
+}
+
+func (c *completer) getSurrounding() *Selection {
+ if c.surrounding == nil {
+ c.surrounding = &Selection{
+ content: "",
+ cursor: c.pos,
+ tokFile: c.tokFile,
+ start: c.pos,
+ end: c.pos,
+ mapper: c.mapper,
+ }
+ }
+ return c.surrounding
+}
+
+// candidate represents a completion candidate.
+type candidate struct {
+ // obj is the types.Object to complete to.
+ // TODO(adonovan): eliminate dependence on go/types throughout this struct.
+ obj types.Object
+
+ // score is used to rank candidates.
+ score float64
+
+ // name is the deep object name path, e.g. "foo.bar"
+ name string
+
+ // detail is additional information about this item. If not specified,
+ // defaults to type string for the object.
+ detail string
+
+ // path holds the path from the search root (excluding the candidate
+ // itself) for a deep candidate.
+ path []types.Object
+
+ // pathInvokeMask is a bit mask tracking whether each entry in path
+ // should be formatted with "()" (i.e. whether it is a function
+ // invocation).
+ pathInvokeMask uint16
+
+ // mods contains modifications that should be applied to the
+ // candidate when inserted. For example, "foo" may be inserted as
+ // "*foo" or "foo()".
+ mods []typeModKind
+
+ // addressable is true if a pointer can be taken to the candidate.
+ addressable bool
+
+ // convertTo is a type that this candidate should be cast to. For
+ // example, if convertTo is float64, "foo" should be formatted as
+ // "float64(foo)".
+ convertTo types.Type
+
+ // 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.
+ imp *importInfo
+}
+
+func (c candidate) hasMod(mod typeModKind) bool {
+ for _, m := range c.mods {
+ if m == mod {
+ return true
+ }
+ }
+ return false
+}
+
+// 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
+}
+
+// Completion returns a list of possible candidates for completion, given a
+// a file and a position.
+//
+// The selection is computed based on the preceding identifier and can be used by
+// 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.
+func Completion(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, protoPos protocol.Position, protoContext protocol.CompletionContext) ([]CompletionItem, *Selection, error) {
+ ctx, done := event.Start(ctx, "completion.Completion")
+ defer done()
+
+ startTime := time.Now()
+
+ pkg, pgf, err := source.PackageForFile(ctx, snapshot, fh.URI(), source.NarrowestPackage)
+ 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, fmt.Errorf("getting file %s for Completion: %w (package completions: %v)", fh.URI(), err, innerErr)
+ }
+ return items, surrounding, nil
+ }
+ pos, err := pgf.PositionPos(protoPos)
+ if err != nil {
+ return nil, nil, err
+ }
+ // Completion is based on what precedes the cursor.
+ // Find the path to the position before pos.
+ path, _ := astutil.PathEnclosingInterval(pgf.File, pos-1, pos-1)
+ if path == nil {
+ return nil, nil, fmt.Errorf("cannot find node enclosing position")
+ }
+
+ // Check if completion at this position is valid. If not, return early.
+ switch n := path[0].(type) {
+ case *ast.BasicLit:
+ // Skip completion inside literals except for ImportSpec
+ if len(path) > 1 {
+ if _, ok := path[1].(*ast.ImportSpec); ok {
+ break
+ }
+ }
+ return nil, nil, nil
+ 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
+ }
+ 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.
+ } else if pgf.File.Name == n {
+ // Don't skip completions if Ident is for package name.
+ break
+ } else {
+ objStr := ""
+ if obj != nil {
+ qual := types.RelativeTo(pkg.GetTypes())
+ objStr = types.ObjectString(obj, qual)
+ }
+ ans, sel := definition(path, obj, pgf)
+ if ans != nil {
+ sort.Slice(ans, func(i, j int) bool {
+ return ans[i].Score > ans[j].Score
+ })
+ return ans, sel, nil
+ }
+ return nil, nil, ErrIsDefinition{objStr: objStr}
+ }
+ }
+ }
+
+ // Collect all surrounding scopes, innermost first.
+ scopes := source.CollectScopes(pkg.GetTypesInfo(), path, pos)
+ scopes = append(scopes, pkg.GetTypes().Scope(), types.Universe)
+
+ opts := snapshot.View().Options()
+ c := &completer{
+ pkg: pkg,
+ snapshot: snapshot,
+ qf: source.Qualifier(pgf.File, pkg.GetTypes(), pkg.GetTypesInfo()),
+ mq: source.MetadataQualifierForFile(snapshot, pgf.File, pkg.Metadata()),
+ completionContext: completionContext{
+ triggerCharacter: protoContext.TriggerCharacter,
+ triggerKind: protoContext.TriggerKind,
+ },
+ fh: fh,
+ filename: fh.URI().Filename(),
+ tokFile: pgf.Tok,
+ file: pgf.File,
+ path: path,
+ pos: pos,
+ seen: make(map[types.Object]bool),
+ enclosingFunc: enclosingFunction(path, pkg.GetTypesInfo()),
+ enclosingCompositeLiteral: enclosingCompositeLiteral(path, pos, pkg.GetTypesInfo()),
+ deepState: deepCompletionState{
+ enabled: opts.DeepCompletion,
+ },
+ opts: &completionOptions{
+ matcher: opts.Matcher,
+ unimported: opts.CompleteUnimported,
+ documentation: opts.CompletionDocumentation && opts.HoverKind != source.NoDocumentation,
+ fullDocumentation: opts.HoverKind == source.FullDocumentation,
+ placeholders: opts.UsePlaceholders,
+ literal: opts.LiteralCompletions && opts.InsertTextFormat == protocol.SnippetTextFormat,
+ budget: opts.CompletionBudget,
+ snippets: opts.InsertTextFormat == protocol.SnippetTextFormat,
+ postfix: opts.ExperimentalPostfixCompletions,
+ },
+ // default to a matcher that always matches
+ matcher: prefixMatcher(""),
+ methodSetCache: make(map[methodSetKey]*types.MethodSet),
+ mapper: pgf.Mapper,
+ startTime: startTime,
+ scopes: scopes,
+ }
+
+ var cancel context.CancelFunc
+ if c.opts.budget == 0 {
+ ctx, cancel = context.WithCancel(ctx)
+ } else {
+ // timeoutDuration is the completion budget remaining. If less than
+ // 10ms, set to 10ms
+ timeoutDuration := time.Until(c.startTime.Add(c.opts.budget))
+ if timeoutDuration < 10*time.Millisecond {
+ timeoutDuration = 10 * time.Millisecond
+ }
+ ctx, cancel = context.WithTimeout(ctx, timeoutDuration)
+ }
+ defer cancel()
+
+ if surrounding := c.containingIdent(pgf.Src); surrounding != nil {
+ c.setSurrounding(surrounding)
+ }
+
+ c.inference = expectedCandidate(ctx, c)
+
+ err = c.collectCompletions(ctx)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Deep search collected candidates and their members for more candidates.
+ c.deepSearch(ctx)
+
+ for _, callback := range c.completionCallbacks {
+ if err := c.snapshot.RunProcessEnvFunc(ctx, callback); err != nil {
+ return nil, nil, err
+ }
+ }
+
+ // Search candidates populated by expensive operations like
+ // unimportedMembers etc. for more completion items.
+ c.deepSearch(ctx)
+
+ // 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()
+
+ c.sortItems()
+ return c.items, c.getSurrounding(), nil
+}
+
+// collectCompletions adds possible completion candidates to either the deep
+// search queue or completion items directly for different completion contexts.
+func (c *completer) collectCompletions(ctx context.Context) error {
+ // Inside import blocks, return completions for unimported packages.
+ for _, importSpec := range c.file.Imports {
+ if !(importSpec.Path.Pos() <= c.pos && c.pos <= importSpec.Path.End()) {
+ continue
+ }
+ return c.populateImportCompletions(ctx, importSpec)
+ }
+
+ // Inside comments, offer completions for the name of the relevant symbol.
+ for _, comment := range c.file.Comments {
+ if comment.Pos() < c.pos && c.pos <= comment.End() {
+ c.populateCommentCompletions(ctx, comment)
+ return nil
+ }
+ }
+
+ // Struct literals are handled entirely separately.
+ if c.wantStructFieldCompletions() {
+ // If we are definitely completing a struct field name, deep completions
+ // don't make sense.
+ if c.enclosingCompositeLiteral.inKey {
+ c.deepState.enabled = false
+ }
+ return c.structLiteralFieldName(ctx)
+ }
+
+ if lt := c.wantLabelCompletion(); lt != labelNone {
+ c.labels(lt)
+ return nil
+ }
+
+ if c.emptySwitchStmt() {
+ // Empty switch statements only admit "default" and "case" keywords.
+ c.addKeywordItems(map[string]bool{}, highScore, CASE, DEFAULT)
+ return nil
+ }
+
+ switch n := c.path[0].(type) {
+ case *ast.Ident:
+ if c.file.Name == n {
+ return c.packageNameCompletions(ctx, c.fh.URI(), n)
+ } else if sel, ok := c.path[1].(*ast.SelectorExpr); ok && sel.Sel == n {
+ // Is this the Sel part of a selector?
+ return c.selector(ctx, sel)
+ }
+ return c.lexical(ctx)
+ // The function name hasn't been typed yet, but the parens are there:
+ // recv.‸(arg)
+ case *ast.TypeAssertExpr:
+ // Create a fake selector expression.
+ return c.selector(ctx, &ast.SelectorExpr{X: n.X})
+ case *ast.SelectorExpr:
+ return c.selector(ctx, n)
+ // At the file scope, only keywords are allowed.
+ case *ast.BadDecl, *ast.File:
+ c.addKeywordCompletions()
+ default:
+ // fallback to lexical completions
+ return c.lexical(ctx)
+ }
+
+ return nil
+}
+
+// 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 identifier 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
+}
+
+// scanToken scans pgh's contents for the token containing pos.
+func (c *completer) scanToken(contents []byte) (token.Pos, token.Token, string) {
+ tok := c.pkg.FileSet().File(c.pos)
+
+ var s scanner.Scanner
+ s.Init(tok, contents, nil, 0)
+ for {
+ tknPos, tkn, lit := s.Scan()
+ if tkn == token.EOF || tknPos >= c.pos {
+ return token.NoPos, token.ILLEGAL, ""
+ }
+
+ if len(lit) > 0 && tknPos <= c.pos && c.pos <= tknPos+token.Pos(len(lit)) {
+ return tknPos, tkn, lit
+ }
+ }
+}
+
+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 preferring shorter candidates.
+ return c.items[i].Label < c.items[j].Label
+ })
+}
+
+// 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
+ }
+}
+
+// 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 {
+ if !strings.HasPrefix(searchImport.Path.Value, `"`) {
+ return nil
+ }
+
+ // deepSearch is not valuable for import completions.
+ c.deepState.enabled = false
+
+ importPath := searchImport.Path.Value
+
+ // Extract the text between the quotes (if any) in an import spec.
+ // prefix is the part of import path before the cursor.
+ prefixEnd := c.pos - searchImport.Path.Pos()
+ prefix := strings.Trim(importPath[:prefixEnd], `"`)
+
+ // The number of directories in the import path gives us the depth at
+ // which to search.
+ depth := len(strings.Split(prefix, "/")) - 1
+
+ content := importPath
+ start, end := searchImport.Path.Pos(), searchImport.Path.End()
+ namePrefix, nameSuffix := `"`, `"`
+ // If a starting quote is present, adjust surrounding to either after the
+ // cursor or after the first slash (/), except if cursor is at the starting
+ // quote. Otherwise we provide a completion including the starting quote.
+ if strings.HasPrefix(importPath, `"`) && c.pos > searchImport.Path.Pos() {
+ content = content[1:]
+ start++
+ if depth > 0 {
+ // Adjust textEdit start to replacement range. For ex: if current
+ // path was "golang.or/x/to<>ols/internal/", where <> is the cursor
+ // position, start of the replacement range would be after
+ // "golang.org/x/".
+ path := strings.SplitAfter(prefix, "/")
+ numChars := len(strings.Join(path[:len(path)-1], ""))
+ content = content[numChars:]
+ start += token.Pos(numChars)
+ }
+ namePrefix = ""
+ }
+
+ // We won't provide an ending quote if one is already present, except if
+ // cursor is after the ending quote but still in import spec. This is
+ // because cursor has to be in our textEdit range.
+ if strings.HasSuffix(importPath, `"`) && c.pos < searchImport.Path.End() {
+ end--
+ content = content[:len(content)-1]
+ nameSuffix = ""
+ }
+
+ c.surrounding = &Selection{
+ content: content,
+ cursor: c.pos,
+ tokFile: c.tokFile,
+ start: start,
+ end: end,
+ mapper: c.mapper,
+ }
+
+ seenImports := make(map[string]struct{})
+ for _, importSpec := range c.file.Imports {
+ if importSpec.Path.Value == importPath {
+ continue
+ }
+ seenImportPath, err := strconv.Unquote(importSpec.Path.Value)
+ if err != nil {
+ return err
+ }
+ seenImports[seenImportPath] = struct{}{}
+ }
+
+ 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], "/")
+
+ name := pkgDirList[depth]
+ // if we're adding an opening quote to completion too, set name to full
+ // package path since we'll need to overwrite that range.
+ if namePrefix == `"` {
+ name = pkgToConsider
+ }
+
+ score := 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.
+ name += "/"
+ pkgToConsider += "/"
+ }
+
+ if _, ok := seen[pkgToConsider]; ok {
+ return
+ }
+ seen[pkgToConsider] = struct{}{}
+
+ mu.Lock()
+ defer mu.Unlock()
+
+ name = namePrefix + name + nameSuffix
+ obj := types.NewPkgName(0, nil, name, types.NewPackage(pkgToConsider, name))
+ c.deepState.enqueue(candidate{
+ obj: obj,
+ detail: fmt.Sprintf("%q", pkgToConsider),
+ score: score,
+ })
+ }
+
+ c.completionCallbacks = append(c.completionCallbacks, func(opts *imports.Options) error {
+ return imports.GetImportPaths(ctx, searchImports, prefix, c.filename, c.pkg.GetTypes().Name(), opts.Env)
+ })
+ return nil
+}
+
+// populateCommentCompletions yields completions for comments preceding or in declarations.
+func (c *completer) populateCommentCompletions(ctx context.Context, comment *ast.CommentGroup) {
+ // If the completion was triggered by a period, ignore it. These types of
+ // completions will not be useful in comments.
+ if c.completionContext.triggerCharacter == "." {
+ return
+ }
+
+ // Using the comment position find the line after
+ file := c.pkg.FileSet().File(comment.End())
+ if file == nil {
+ return
+ }
+
+ // Deep completion doesn't work properly in comments since we don't
+ // have a type object to complete further.
+ c.deepState.enabled = false
+ c.completionContext.commentCompletion = true
+
+ // Documentation isn't useful in comments, since it might end up being the
+ // comment itself.
+ c.opts.documentation = false
+
+ commentLine := file.Line(comment.End())
+
+ // comment is valid, set surrounding as word boundaries around cursor
+ c.setSurroundingForComment(comment)
+
+ // Using the next line pos, grab and parse the exported symbol on that line
+ for _, n := range c.file.Decls {
+ 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()) {
+ continue
+ }
+ switch node := n.(type) {
+ // handle const, vars, and types
+ case *ast.GenDecl:
+ for _, spec := range node.Specs {
+ switch spec := spec.(type) {
+ case *ast.ValueSpec:
+ for _, name := range spec.Names {
+ if name.String() == "_" {
+ continue
+ }
+ obj := c.pkg.GetTypesInfo().ObjectOf(name)
+ c.deepState.enqueue(candidate{obj: obj, score: stdScore})
+ }
+ case *ast.TypeSpec:
+ // 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)
+ }
+
+ if spec.Name.String() == "_" {
+ continue
+ }
+
+ obj := c.pkg.GetTypesInfo().ObjectOf(spec.Name)
+ // 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
+ }
+
+ c.deepState.enqueue(candidate{obj: obj, score: score})
+ }
+ }
+ // handle functions
+ case *ast.FuncDecl:
+ 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)
+ c.deepState.enqueue(candidate{obj: field, score: lowScore})
+ }
+ }
+ }
+ }
+
+ if node.Name.String() == "_" {
+ continue
+ }
+
+ obj := c.pkg.GetTypesInfo().ObjectOf(node.Name)
+ if obj == nil || obj.Pkg() != nil && obj.Pkg() != c.pkg.GetTypes() {
+ continue
+ }
+
+ c.deepState.enqueue(candidate{obj: obj, score: highScore})
+ }
+ }
+}
+
+// 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,
+ tokFile: c.tokFile,
+ start: token.Pos(int(cursorComment.Slash) + start),
+ end: token.Pos(int(cursorComment.Slash) + end),
+ mapper: c.mapper,
+ }
+ c.setMatcherFromPrefix(c.surrounding.Prefix())
+}
+
+// isValidIdentifierChar returns true if a byte is a valid go identifier
+// character, i.e. unicode letter or digit or underscore.
+func isValidIdentifierChar(char byte) bool {
+ charRune := rune(char)
+ return unicode.In(charRune, unicode.Letter, unicode.Digit) || char == '_'
+}
+
+// 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 {
+ if name.String() == "_" {
+ continue
+ }
+ obj := c.pkg.GetTypesInfo().ObjectOf(name)
+ if obj == nil {
+ continue
+ }
+
+ // 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
+ }
+
+ c.deepState.enqueue(candidate{obj: obj, score: score})
+ }
+ }
+}
+
+func (c *completer) wantStructFieldCompletions() bool {
+ clInfo := c.enclosingCompositeLiteral
+ if clInfo == nil {
+ return false
+ }
+
+ return clInfo.isStruct() && (clInfo.inKey || clInfo.maybeInFieldName)
+}
+
+func (c *completer) wantTypeName() bool {
+ return !c.completionContext.commentCompletion && c.inference.typeName.wantTypeName
+}
+
+// See https://golang.org/issue/36001. Unimported completions are expensive.
+const (
+ maxUnimportedPackageNames = 5
+ unimportedMemberTarget = 100
+)
+
+// selector finds completions for the specified selector expression.
+func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error {
+ c.inference.objChain = objChain(c.pkg.GetTypesInfo(), sel.X)
+
+ // True selector?
+ if tv, ok := c.pkg.GetTypesInfo().Types[sel.X]; ok {
+ c.methodsAndFields(tv.Type, tv.Addressable(), nil, c.deepState.enqueue)
+ c.addPostfixSnippetCandidates(ctx, sel)
+ return nil
+ }
+
+ id, ok := sel.X.(*ast.Ident)
+ if !ok {
+ return nil
+ }
+
+ // Treat sel as a qualified identifier.
+ var filter func(*source.Metadata) bool
+ needImport := false
+ if pkgName, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok {
+ // Qualified identifier with import declaration.
+ imp := pkgName.Imported()
+
+ // Known direct dependency? Expand using type information.
+ if _, ok := c.pkg.Metadata().DepsByPkgPath[source.PackagePath(imp.Path())]; ok {
+ c.packageMembers(imp, stdScore, nil, c.deepState.enqueue)
+ return nil
+ }
+
+ // Imported declaration with missing type information.
+ // Fall through to shallow completion of unimported package members.
+ // Match candidate packages by path.
+ // TODO(adonovan): simplify by merging with else case and matching on name only?
+ filter = func(m *source.Metadata) bool {
+ return strings.TrimPrefix(string(m.PkgPath), "vendor/") == imp.Path()
+ }
+ } else {
+ // Qualified identifier without import declaration.
+ // Match candidate packages by name.
+ filter = func(m *source.Metadata) bool {
+ return string(m.Name) == id.Name
+ }
+ needImport = true
+ }
+
+ // Search unimported packages.
+ if !c.opts.unimported {
+ return nil // feature disabled
+ }
+
+ // The deep completion algorithm is exceedingly complex and
+ // deeply coupled to the now obsolete notions that all
+ // token.Pos values can be interpreted by as a single FileSet
+ // belonging to the Snapshot and that all types.Object values
+ // are canonicalized by a single types.Importer mapping.
+ // These invariants are no longer true now that gopls uses
+ // an incremental approach, parsing and type-checking each
+ // package separately.
+ //
+ // Consequently, completion of symbols defined in packages that
+ // are not currently imported by the query file cannot use the
+ // deep completion machinery which is based on type information.
+ // Instead it must use only syntax information from a quick
+ // parse of top-level declarations (but not function bodies).
+ //
+ // TODO(adonovan): rewrite the deep completion machinery to
+ // not assume global Pos/Object realms and then use export
+ // data instead of the quick parse approach taken here.
+
+ // First, we search among packages in the workspace.
+ // We'll use a fast parse to extract package members
+ // from those that match the name/path criterion.
+ all, err := c.snapshot.AllMetadata(ctx)
+ if err != nil {
+ return err
+ }
+ var paths []string
+ known := make(map[source.PackagePath][]*source.Metadata) // may include test variant
+ for _, m := range all {
+ if m.IsIntermediateTestVariant() || m.Name == "main" || !filter(m) {
+ continue
+ }
+ known[m.PkgPath] = append(known[m.PkgPath], m)
+ paths = append(paths, string(m.PkgPath))
+ }
+
+ // Rank import paths as goimports would.
+ var relevances map[string]float64
+ if len(paths) > 0 {
+ if err := c.snapshot.RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
+ var err error
+ relevances, err = imports.ScoreImportPaths(ctx, opts.Env, paths)
+ return err
+ }); err != nil {
+ return err
+ }
+ sort.Slice(paths, func(i, j int) bool {
+ return relevances[paths[i]] > relevances[paths[j]]
+ })
+ }
+
+ // quickParse does a quick parse of a single file of package m,
+ // extracts exported package members and adds candidates to c.items.
+ var itemsMu sync.Mutex // guards c.items
+ var enough int32 // atomic bool
+ quickParse := func(uri span.URI, m *source.Metadata) error {
+ if atomic.LoadInt32(&enough) != 0 {
+ return nil
+ }
+
+ fh, err := c.snapshot.GetFile(ctx, uri)
+ if err != nil {
+ return err
+ }
+ content, err := fh.Read()
+ if err != nil {
+ return err
+ }
+ path := string(m.PkgPath)
+ forEachPackageMember(content, func(tok token.Token, id *ast.Ident, fn *ast.FuncDecl) {
+ if atomic.LoadInt32(&enough) != 0 {
+ return
+ }
+
+ if !id.IsExported() ||
+ sel.Sel.Name != "_" && !strings.HasPrefix(id.Name, sel.Sel.Name) {
+ return // not a match
+ }
+
+ // The only detail is the kind and package: `var (from "example.com/foo")`
+ // TODO(adonovan): pretty-print FuncDecl.FuncType or TypeSpec.Type?
+ item := CompletionItem{
+ Label: id.Name,
+ Detail: fmt.Sprintf("%s (from %q)", strings.ToLower(tok.String()), m.PkgPath),
+ InsertText: id.Name,
+ Score: unimportedScore(relevances[path]),
+ }
+ switch tok {
+ case token.FUNC:
+ item.Kind = protocol.FunctionCompletion
+ case token.VAR:
+ item.Kind = protocol.VariableCompletion
+ case token.CONST:
+ item.Kind = protocol.ConstantCompletion
+ case token.TYPE:
+ // Without types, we can't distinguish Class from Interface.
+ item.Kind = protocol.ClassCompletion
+ }
+
+ if needImport {
+ imp := &importInfo{importPath: path}
+ if imports.ImportPathToAssumedName(path) != string(m.Name) {
+ imp.name = string(m.Name)
+ }
+ item.AdditionalTextEdits, _ = c.importEdits(imp)
+ }
+
+ // For functions, add a parameter snippet.
+ if fn != nil {
+ var sn snippet.Builder
+ sn.WriteText(id.Name)
+ sn.WriteText("(")
+ var nparams int
+ for _, field := range fn.Type.Params.List {
+ if field.Names != nil {
+ nparams += len(field.Names)
+ } else {
+ nparams++
+ }
+ }
+ for i := 0; i < nparams; i++ {
+ if i > 0 {
+ sn.WriteText(", ")
+ }
+ sn.WritePlaceholder(nil)
+ }
+ sn.WriteText(")")
+ item.snippet = &sn
+ }
+
+ itemsMu.Lock()
+ c.items = append(c.items, item)
+ if len(c.items) >= unimportedMemberTarget {
+ atomic.StoreInt32(&enough, 1)
+ }
+ itemsMu.Unlock()
+ })
+ return nil
+ }
+
+ // Extract the package-level candidates using a quick parse.
+ var g errgroup.Group
+ for _, path := range paths {
+ for _, m := range known[source.PackagePath(path)] {
+ m := m
+ for _, uri := range m.CompiledGoFiles {
+ uri := uri
+ g.Go(func() error {
+ return quickParse(uri, m)
+ })
+ }
+ }
+ }
+ if err := g.Wait(); err != nil {
+ return err
+ }
+
+ // In addition, we search in the module cache using goimports.
+ ctx, cancel := context.WithCancel(ctx)
+ var mu sync.Mutex
+ add := func(pkgExport imports.PackageExport) {
+ mu.Lock()
+ defer mu.Unlock()
+ // TODO(adonovan): what if the actual package has a vendor/ prefix?
+ if _, ok := known[source.PackagePath(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 {
+ score := unimportedScore(pkgExport.Fix.Relevance)
+ c.deepState.enqueue(candidate{
+ obj: types.NewVar(0, pkg, export, nil),
+ score: score,
+ imp: &importInfo{
+ importPath: pkgExport.Fix.StmtInfo.ImportPath,
+ name: pkgExport.Fix.StmtInfo.Name,
+ },
+ })
+ }
+ if len(c.items) >= unimportedMemberTarget {
+ cancel()
+ }
+ }
+
+ c.completionCallbacks = append(c.completionCallbacks, func(opts *imports.Options) error {
+ defer cancel()
+ return imports.GetPackageExports(ctx, add, id.Name, c.filename, c.pkg.GetTypes().Name(), opts.Env)
+ })
+ return nil
+}
+
+// unimportedScore returns a score for an unimported package that is generally
+// lower than other candidates.
+func unimportedScore(relevance float64) float64 {
+ return (stdScore + .1*relevance) / 2
+}
+
+func (c *completer) packageMembers(pkg *types.Package, score float64, imp *importInfo, cb func(candidate)) {
+ scope := pkg.Scope()
+ for _, name := range scope.Names() {
+ obj := scope.Lookup(name)
+ cb(candidate{
+ obj: obj,
+ score: score,
+ imp: imp,
+ addressable: isVar(obj),
+ })
+ }
+}
+
+func (c *completer) methodsAndFields(typ types.Type, addressable bool, imp *importInfo, cb func(candidate)) {
+ 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
+ }
+
+ if isStarTestingDotF(typ) && addressable {
+ // is that a sufficient test? (or is more care needed?)
+ if c.fuzz(typ, mset, imp, cb, c.pkg.FileSet()) {
+ return
+ }
+ }
+
+ for i := 0; i < mset.Len(); i++ {
+ cb(candidate{
+ obj: mset.At(i).Obj(),
+ score: stdScore,
+ imp: imp,
+ addressable: addressable || isPointer(typ),
+ })
+ }
+
+ // Add fields of T.
+ eachField(typ, func(v *types.Var) {
+ cb(candidate{
+ obj: v,
+ score: stdScore - 0.01,
+ imp: imp,
+ addressable: addressable || isPointer(typ),
+ })
+ })
+}
+
+// isStarTestingDotF reports whether typ is *testing.F.
+func isStarTestingDotF(typ types.Type) bool {
+ ptr, _ := typ.(*types.Pointer)
+ if ptr == nil {
+ return false
+ }
+ named, _ := ptr.Elem().(*types.Named)
+ if named == nil {
+ return false
+ }
+ obj := named.Obj()
+ // obj.Pkg is nil for the error type.
+ return obj != nil && obj.Pkg() != nil && obj.Pkg().Path() == "testing" && obj.Name() == "F"
+}
+
+// lexical finds completions in the lexical environment.
+func (c *completer) lexical(ctx context.Context) error {
+ var (
+ builtinIota = types.Universe.Lookup("iota")
+ builtinNil = types.Universe.Lookup("nil")
+
+ // TODO(rfindley): only allow "comparable" where it is valid (in constraint
+ // position or embedded in interface declarations).
+ // builtinComparable = types.Universe.Lookup("comparable")
+ )
+
+ // 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{})
+
+ // Process scopes innermost first.
+ for i, scope := range c.scopes {
+ if scope == nil {
+ continue
+ }
+
+ Names:
+ for _, name := range scope.Names() {
+ declScope, obj := scope.LookupParent(name, c.pos)
+ if declScope != scope {
+ continue // Name was declared in some enclosing scope, or not at all.
+ }
+
+ // 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.
+ if !isPkgName(obj) && !typeIsValid(obj.Type()) {
+ // 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
+ 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]
+ }
+ if node != nil {
+ if resolved := resolveInvalid(c.pkg.FileSet(), obj, node, c.pkg.GetTypesInfo()); resolved != nil {
+ obj = resolved
+ }
+ }
+ }
+
+ // Don't use LHS of decl in RHS.
+ for _, ident := range enclosingDeclLHS(c.path) {
+ if obj.Pos() == ident.Pos() {
+ continue Names
+ }
+ }
+
+ // Don't suggest "iota" outside of const decls.
+ if obj == builtinIota && !c.inConstDecl() {
+ continue
+ }
+
+ // Rank outer scopes lower than inner.
+ score := stdScore * math.Pow(.99, float64(i))
+
+ // Dowrank "nil" a bit so it is ranked below more interesting candidates.
+ if obj == builtinNil {
+ score /= 2
+ }
+
+ // If we haven't already added a candidate for an object with this name.
+ if _, ok := seen[obj.Name()]; !ok {
+ seen[obj.Name()] = struct{}{}
+ c.deepState.enqueue(candidate{
+ obj: obj,
+ score: score,
+ addressable: isVar(obj),
+ })
+ }
+ }
+ }
+
+ if c.inference.objType != nil {
+ if named, _ := source.Deref(c.inference.objType).(*types.Named); named != nil {
+ // 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.
+ // TODO(adonovan): what if pkg.Path has vendor/ prefix?
+ if _, ok := seen[pkg.Name()]; !ok && pkg != c.pkg.GetTypes() && !alreadyImports(c.file, source.ImportPath(pkg.Path())) {
+ seen[pkg.Name()] = struct{}{}
+ obj := types.NewPkgName(0, nil, pkg.Name(), pkg)
+ imp := &importInfo{
+ importPath: pkg.Path(),
+ }
+ if imports.ImportPathToAssumedName(pkg.Path()) != pkg.Name() {
+ imp.name = pkg.Name()
+ }
+ c.deepState.enqueue(candidate{
+ obj: obj,
+ score: stdScore,
+ imp: imp,
+ })
+ }
+ }
+ }
+ }
+
+ if c.opts.unimported {
+ if err := c.unimportedPackages(ctx, seen); err != nil {
+ return err
+ }
+ }
+
+ if c.inference.typeName.isTypeParam {
+ // If we are completing a type param, offer each structural type.
+ // This ensures we suggest "[]int" and "[]float64" for a constraint
+ // with type union "[]int | []float64".
+ if t, _ := c.inference.objType.(*types.Interface); t != nil {
+ terms, _ := typeparams.InterfaceTermSet(t)
+ for _, term := range terms {
+ c.injectType(ctx, term.Type())
+ }
+ }
+ } else {
+ c.injectType(ctx, c.inference.objType)
+ }
+
+ // Add keyword completion items appropriate in the current context.
+ c.addKeywordCompletions()
+
+ return nil
+}
+
+// injectType manufactures candidates based on the given type. This is
+// intended for types not discoverable via lexical search, such as
+// composite and/or generic types. For example, if the type is "[]int",
+// this method makes sure you get candidates "[]int{}" and "[]int"
+// (the latter applies when completing a type name).
+func (c *completer) injectType(ctx context.Context, t types.Type) {
+ if t == nil {
+ return
+ }
+
+ t = source.Deref(t)
+
+ // 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. Also allow generic types since lexical search does not
+ // infer instantiated versions of them.
+ if named, _ := t.(*types.Named); named == nil || typeparams.ForNamed(named).Len() > 0 {
+ // If our expected type is "[]int", this will add a literal
+ // candidate of "[]int{}".
+ c.literal(ctx, t, nil)
+
+ 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.deepState.enqueue(fakeNamedType)
+ }
+ }
+ }
+}
+
+func (c *completer) unimportedPackages(ctx context.Context, seen map[string]struct{}) error {
+ var prefix string
+ if c.surrounding != nil {
+ prefix = c.surrounding.Prefix()
+ }
+
+ // Don't suggest unimported packages if we have absolutely nothing
+ // to go on.
+ if prefix == "" {
+ return nil
+ }
+
+ count := 0
+
+ // Search packages across the entire workspace.
+ all, err := c.snapshot.AllMetadata(ctx)
+ if err != nil {
+ return err
+ }
+ pkgNameByPath := make(map[source.PackagePath]string)
+ var paths []string // actually PackagePaths
+ for _, m := range all {
+ if m.ForTest != "" {
+ continue // skip all test variants
+ }
+ if m.Name == "main" {
+ continue // main is non-importable
+ }
+ if !strings.HasPrefix(string(m.Name), prefix) {
+ continue // not a match
+ }
+ paths = append(paths, string(m.PkgPath))
+ pkgNameByPath[m.PkgPath] = string(m.Name)
+ }
+
+ // Rank candidates using goimports' algorithm.
+ var relevances map[string]float64
+ if len(paths) != 0 {
+ if err := c.snapshot.RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
+ var err error
+ relevances, err = imports.ScoreImportPaths(ctx, opts.Env, paths)
+ return err
+ }); err != nil {
+ return err
+ }
+ }
+ sort.Slice(paths, func(i, j int) bool {
+ if relevances[paths[i]] != relevances[paths[j]] {
+ return relevances[paths[i]] > relevances[paths[j]]
+ }
+
+ // Fall back to lexical sort to keep truncated set of candidates
+ // in a consistent order.
+ return paths[i] < paths[j]
+ })
+
+ for _, path := range paths {
+ name := pkgNameByPath[source.PackagePath(path)]
+ if _, ok := seen[name]; ok {
+ continue
+ }
+ imp := &importInfo{
+ importPath: path,
+ }
+ if imports.ImportPathToAssumedName(path) != name {
+ imp.name = name
+ }
+ if count >= maxUnimportedPackageNames {
+ return nil
+ }
+ c.deepState.enqueue(candidate{
+ // Pass an empty *types.Package to disable deep completions.
+ obj: types.NewPkgName(0, nil, name, types.NewPackage(path, name)),
+ score: unimportedScore(relevances[path]),
+ imp: imp,
+ })
+ count++
+ }
+
+ ctx, cancel := context.WithCancel(ctx)
+
+ var mu sync.Mutex
+ add := func(pkg imports.ImportFix) {
+ mu.Lock()
+ defer mu.Unlock()
+ if _, ok := seen[pkg.IdentName]; ok {
+ return
+ }
+ if _, ok := relevances[pkg.StmtInfo.ImportPath]; ok {
+ return
+ }
+
+ if count >= maxUnimportedPackageNames {
+ 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))
+ c.deepState.enqueue(candidate{
+ obj: obj,
+ score: unimportedScore(pkg.Relevance),
+ imp: &importInfo{
+ importPath: pkg.StmtInfo.ImportPath,
+ name: pkg.StmtInfo.Name,
+ },
+ })
+ count++
+ }
+ c.completionCallbacks = append(c.completionCallbacks, func(opts *imports.Options) error {
+ defer cancel()
+ return imports.GetAllCandidates(ctx, add, prefix, c.filename, c.pkg.GetTypes().Name(), opts.Env)
+ })
+ return nil
+}
+
+// alreadyImports reports whether f has an import with the specified path.
+func alreadyImports(f *ast.File, path source.ImportPath) bool {
+ for _, s := range f.Imports {
+ if source.UnquoteImportPath(s) == path {
+ return true
+ }
+ }
+ return false
+}
+
+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
+}
+
+// structLiteralFieldName finds completions for struct field names inside a struct literal.
+func (c *completer) structLiteralFieldName(ctx context.Context) error {
+ clInfo := c.enclosingCompositeLiteral
+
+ // Mark fields of the composite literal that have already been set,
+ // except for the current field.
+ addedFields := make(map[*types.Var]bool)
+ for _, el := range clInfo.cl.Elts {
+ if kvExpr, ok := el.(*ast.KeyValueExpr); ok {
+ if clInfo.kv == kvExpr {
+ continue
+ }
+
+ if key, ok := kvExpr.Key.(*ast.Ident); ok {
+ if used, ok := c.pkg.GetTypesInfo().Uses[key]; ok {
+ if usedVar, ok := used.(*types.Var); ok {
+ addedFields[usedVar] = true
+ }
+ }
+ }
+ }
+ }
+
+ deltaScore := 0.0001
+ switch t := clInfo.clType.(type) {
+ case *types.Struct:
+ for i := 0; i < t.NumFields(); i++ {
+ field := t.Field(i)
+ if !addedFields[field] {
+ c.deepState.enqueue(candidate{
+ obj: field,
+ score: highScore - float64(i)*deltaScore,
+ })
+ }
+ }
+
+ // Add lexical completions if we aren't certain we are in the key part of a
+ // key-value pair.
+ if clInfo.maybeInFieldName {
+ return c.lexical(ctx)
+ }
+ default:
+ return c.lexical(ctx)
+ }
+
+ return nil
+}
+
+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 {
+ for _, n := range path {
+ switch n := n.(type) {
+ case *ast.CompositeLit:
+ // 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{}").
+ if !(n.Lbrace < pos && pos <= n.Rbrace) {
+ // Keep searching since we may yet be inside a composite literal.
+ // For example "Foo{B: Ba<>{}}".
+ break
+ }
+
+ tv, ok := info.Types[n]
+ if !ok {
+ return nil
+ }
+
+ clInfo := compLitInfo{
+ cl: n,
+ clType: source.Deref(tv.Type).Underlying(),
+ }
+
+ 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
+ }
+ }
+ }
+
+ 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
+ }
+ }
+
+ return &clInfo
+ default:
+ if breaksExpectedTypeInference(n, pos) {
+ return nil
+ }
+ }
+ }
+
+ return nil
+}
+
+// enclosingFunction returns the signature and body of the function
+// enclosing the given position.
+func enclosingFunction(path []ast.Node, info *types.Info) *funcInfo {
+ for _, node := range path {
+ switch t := node.(type) {
+ case *ast.FuncDecl:
+ if obj, ok := info.Defs[t.Name]; ok {
+ return &funcInfo{
+ sig: obj.Type().(*types.Signature),
+ body: t.Body,
+ }
+ }
+ case *ast.FuncLit:
+ if typ, ok := info.Types[t]; ok {
+ if sig, _ := typ.Type.(*types.Signature); sig == nil {
+ // golang/go#49397: it should not be possible, but we somehow arrived
+ // here with a non-signature type, most likely due to AST mangling
+ // such that node.Type is not a FuncType.
+ return nil
+ }
+ return &funcInfo{
+ sig: typ.Type.(*types.Signature),
+ body: t.Body,
+ }
+ }
+ }
+ }
+ return nil
+}
+
+func (c *completer) expectedCompositeLiteralType() types.Type {
+ clInfo := c.enclosingCompositeLiteral
+ switch t := clInfo.clType.(type) {
+ case *types.Slice:
+ if clInfo.inKey {
+ return types.Typ[types.UntypedInt]
+ }
+ return t.Elem()
+ case *types.Array:
+ if clInfo.inKey {
+ return types.Typ[types.UntypedInt]
+ }
+ return t.Elem()
+ case *types.Map:
+ if clInfo.inKey {
+ return t.Key()
+ }
+ return t.Elem()
+ case *types.Struct:
+ // 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 {
+ for i := 0; i < t.NumFields(); i++ {
+ if field := t.Field(i); field.Name() == key.Name {
+ return field.Type()
+ }
+ }
+ }
+ } 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.
+ if i := exprAtPos(c.pos, clInfo.cl.Elts); i < t.NumFields() {
+ return t.Field(i).Type()
+ }
+ }
+ }
+ return nil
+}
+
+// typeMod represents an operator that changes the expected type.
+type typeMod struct {
+ mod typeModKind
+ arrayLen int64
+}
+
+type typeModKind int
+
+const (
+ dereference typeModKind = iota // pointer indirection: "*"
+ reference // adds level of pointer: "&" for values, "*" for type names
+ chanRead // channel read operator: "<-"
+ sliceType // make a slice type: "[]" in "[]int"
+ arrayType // make an array type: "[2]" in "[2]int"
+ invoke // make a function call: "()" in "foo()"
+ takeSlice // take slice of array: "[:]" in "foo[:]"
+ takeDotDotDot // turn slice into variadic args: "..." in "foo..."
+ index // index into slice/array: "[0]" in "foo[0]"
+)
+
+type objKind int
+
+const (
+ kindAny objKind = 0
+ kindArray objKind = 1 << iota
+ kindSlice
+ kindChan
+ kindMap
+ kindStruct
+ kindString
+ kindInt
+ kindBool
+ kindBytes
+ kindPtr
+ kindFloat
+ kindComplex
+ kindError
+ kindStringer
+ kindFunc
+)
+
+// 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
+}
+
+// candidateInference holds information we have inferred about a type that can be
+// used at the current position.
+type candidateInference struct {
+ // objType is the desired type of an object used at the query position.
+ objType types.Type
+
+ // objKind is a mask of expected kinds of types such as "map", "slice", etc.
+ objKind objKind
+
+ // 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
+
+ // modifiers are prefixes such as "*", "&" or "<-" that influence how
+ // a candidate type relates to the expected type.
+ modifiers []typeMod
+
+ // 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
+
+ // 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
+
+ // 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
+
+ // 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
+
+ // 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
+}
+
+// typeNameInference holds information about the expected type name at
+// position.
+type typeNameInference struct {
+ // wantTypeName is true if we expect the name of a type.
+ wantTypeName bool
+
+ // modifiers are prefixes such as "*", "&" or "<-" that influence how
+ // a candidate type relates to the expected type.
+ modifiers []typeMod
+
+ // assertableFrom is a type that must be assertable to our candidate type.
+ assertableFrom types.Type
+
+ // wantComparable is true if we want a comparable type.
+ wantComparable bool
+
+ // seenTypeSwitchCases tracks types that have already been used by
+ // the containing type switch.
+ seenTypeSwitchCases []types.Type
+
+ // compLitType is true if we are completing a composite literal type
+ // name, e.g "foo<>{}".
+ compLitType bool
+
+ // isTypeParam is true if we are completing a type instantiation parameter
+ isTypeParam bool
+}
+
+// expectedCandidate returns information about the expected candidate
+// for an expression at the query position.
+func expectedCandidate(ctx context.Context, c *completer) (inf candidateInference) {
+ inf.typeName = expectTypeName(c)
+
+ if c.enclosingCompositeLiteral != nil {
+ inf.objType = c.expectedCompositeLiteralType()
+ }
+
+Nodes:
+ for i, node := range c.path {
+ switch node := node.(type) {
+ case *ast.BinaryExpr:
+ // Determine if query position comes from left or right of op.
+ e := node.X
+ if c.pos < node.OpPos {
+ e = node.Y
+ }
+ if tv, ok := c.pkg.GetTypesInfo().Types[e]; ok {
+ 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
+ }
+ break Nodes
+ }
+ case *ast.AssignStmt:
+ // Only rank completions if you are on the right side of the token.
+ if c.pos > node.TokPos {
+ i := exprAtPos(c.pos, node.Rhs)
+ if i >= len(node.Lhs) {
+ i = len(node.Lhs) - 1
+ }
+ if tv, ok := c.pkg.GetTypesInfo().Types[node.Lhs[i]]; ok {
+ inf.objType = tv.Type
+ }
+
+ // 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 {
+ // Otherwise, 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]))
+ }
+ }
+ return inf
+ case *ast.ValueSpec:
+ if node.Type != nil && c.pos > node.Type.End() {
+ inf.objType = c.pkg.GetTypesInfo().TypeOf(node.Type)
+ }
+ return inf
+ case *ast.CallExpr:
+ // Only consider CallExpr args if position falls between parens.
+ if node.Lparen < c.pos && c.pos <= node.Rparen {
+ // For type conversions like "int64(foo)" we can only infer our
+ // desired type is convertible to int64.
+ if typ := typeConversion(node, c.pkg.GetTypesInfo()); typ != nil {
+ inf.convertibleTo = typ
+ break Nodes
+ }
+
+ sig, _ := c.pkg.GetTypesInfo().Types[node.Fun].Type.(*types.Signature)
+
+ if sig != nil && typeparams.ForSignature(sig).Len() > 0 {
+ // If we are completing a generic func call, re-check the call expression.
+ // This allows type param inference to work in cases like:
+ //
+ // func foo[T any](T) {}
+ // foo[int](<>) // <- get "int" completions instead of "T"
+ //
+ // TODO: remove this after https://go.dev/issue/52503
+ info := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
+ types.CheckExpr(c.pkg.FileSet(), c.pkg.GetTypes(), node.Fun.Pos(), node.Fun, info)
+ sig, _ = info.Types[node.Fun].Type.(*types.Signature)
+ }
+
+ if sig != nil {
+ inf = c.expectedCallParamType(inf, node, sig)
+ }
+
+ if funIdent, ok := node.Fun.(*ast.Ident); ok {
+ 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.
+ defer func() {
+ inf = c.builtinArgType(obj, node, inf)
+ inf.objKind = c.builtinArgKind(ctx, obj, node)
+ }()
+
+ // The expected type of builtin arguments like append() is
+ // the expected type of the builtin call itself. For
+ // example:
+ //
+ // var foo []int = append(<>)
+ //
+ // To find the expected type at <> we "skip" the append()
+ // node and get the expected type one level up, which is
+ // []int.
+ continue Nodes
+ }
+ }
+
+ return inf
+ }
+ case *ast.ReturnStmt:
+ if c.enclosingFunc != nil {
+ sig := c.enclosingFunc.sig
+ // Find signature result that corresponds to our return statement.
+ if resultIdx := exprAtPos(c.pos, node.Results); resultIdx < len(node.Results) {
+ if resultIdx < sig.Results().Len() {
+ inf.objType = sig.Results().At(resultIdx).Type()
+ }
+ }
+ }
+ return inf
+ case *ast.CaseClause:
+ if swtch, ok := findSwitchStmt(c.path[i+1:], c.pos, node).(*ast.SwitchStmt); ok {
+ if tv, ok := c.pkg.GetTypesInfo().Types[swtch.Tag]; ok {
+ inf.objType = tv.Type
+
+ // 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 {
+ inf.penalized = append(inf.penalized, penalizedObj{objChain: objs, penalty: 0.1})
+ }
+ }
+ }
+ }
+ }
+ return inf
+ case *ast.SliceExpr:
+ // Make sure position falls within the brackets (e.g. "foo[a:<>]").
+ if node.Lbrack < c.pos && c.pos <= node.Rbrack {
+ inf.objType = types.Typ[types.UntypedInt]
+ }
+ return inf
+ case *ast.IndexExpr:
+ // Make sure position falls within the brackets (e.g. "foo[<>]").
+ if node.Lbrack < c.pos && c.pos <= node.Rbrack {
+ if tv, ok := c.pkg.GetTypesInfo().Types[node.X]; ok {
+ switch t := tv.Type.Underlying().(type) {
+ case *types.Map:
+ inf.objType = t.Key()
+ case *types.Slice, *types.Array:
+ inf.objType = types.Typ[types.UntypedInt]
+ }
+
+ if ct := expectedConstraint(tv.Type, 0); ct != nil {
+ inf.objType = ct
+ inf.typeName.wantTypeName = true
+ inf.typeName.isTypeParam = true
+ }
+ }
+ }
+ return inf
+ case *typeparams.IndexListExpr:
+ if node.Lbrack < c.pos && c.pos <= node.Rbrack {
+ if tv, ok := c.pkg.GetTypesInfo().Types[node.X]; ok {
+ if ct := expectedConstraint(tv.Type, exprAtPos(c.pos, node.Indices)); ct != nil {
+ inf.objType = ct
+ inf.typeName.wantTypeName = true
+ inf.typeName.isTypeParam = true
+ }
+ }
+ }
+ return inf
+ case *ast.SendStmt:
+ // Make sure we are on right side of arrow (e.g. "foo <- <>").
+ if c.pos > node.Arrow+1 {
+ if tv, ok := c.pkg.GetTypesInfo().Types[node.Chan]; ok {
+ if ch, ok := tv.Type.Underlying().(*types.Chan); ok {
+ inf.objType = ch.Elem()
+ }
+ }
+ }
+ return inf
+ case *ast.RangeStmt:
+ if source.NodeContains(node.X, c.pos) {
+ inf.objKind |= kindSlice | kindArray | kindMap | kindString
+ if node.Value == nil {
+ inf.objKind |= kindChan
+ }
+ }
+ return inf
+ case *ast.StarExpr:
+ inf.modifiers = append(inf.modifiers, typeMod{mod: dereference})
+ case *ast.UnaryExpr:
+ switch node.Op {
+ case token.AND:
+ inf.modifiers = append(inf.modifiers, typeMod{mod: reference})
+ case token.ARROW:
+ inf.modifiers = append(inf.modifiers, typeMod{mod: chanRead})
+ }
+ case *ast.DeferStmt, *ast.GoStmt:
+ inf.objKind |= kindFunc
+ return inf
+ default:
+ if breaksExpectedTypeInference(node, c.pos) {
+ return inf
+ }
+ }
+ }
+
+ return inf
+}
+
+func (c *completer) expectedCallParamType(inf candidateInference, node *ast.CallExpr, sig *types.Signature) candidateInference {
+ numParams := sig.Params().Len()
+ if numParams == 0 {
+ return inf
+ }
+
+ exprIdx := exprAtPos(c.pos, node.Args)
+
+ // 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.
+ if len(node.Args) <= 1 && exprIdx == 0 {
+ for i := 0; i < sig.Params().Len(); i++ {
+ inf.assignees = append(inf.assignees, sig.Params().At(i).Type())
+ }
+
+ // Record that we may be completing into variadic parameters.
+ inf.variadicAssignees = sig.Variadic()
+ }
+
+ // Make sure not to run past the end of expected parameters.
+ if exprIdx >= numParams {
+ inf.objType = sig.Params().At(numParams - 1).Type()
+ } else {
+ inf.objType = sig.Params().At(exprIdx).Type()
+ }
+
+ 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
+
+ // Check if we can infer object kind from printf verb.
+ inf.objKind |= printfArgKind(c.pkg.GetTypesInfo(), node, exprIdx)
+ }
+
+ // If our expected type is an uninstantiated generic type param,
+ // swap to the constraint which will do a decent job filtering
+ // candidates.
+ if tp, _ := inf.objType.(*typeparams.TypeParam); tp != nil {
+ inf.objType = tp.Constraint()
+ }
+
+ return inf
+}
+
+func expectedConstraint(t types.Type, idx int) types.Type {
+ var tp *typeparams.TypeParamList
+ if named, _ := t.(*types.Named); named != nil {
+ tp = typeparams.ForNamed(named)
+ } else if sig, _ := t.Underlying().(*types.Signature); sig != nil {
+ tp = typeparams.ForSignature(sig)
+ }
+ if tp == nil || idx >= tp.Len() {
+ return nil
+ }
+ return tp.At(idx).Constraint()
+}
+
+// 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
+}
+
+// applyTypeModifiers applies the list of type modifiers to a type.
+// It returns nil if the modifiers could not be applied.
+func (ci candidateInference) applyTypeModifiers(typ types.Type, addressable bool) types.Type {
+ for _, mod := range ci.modifiers {
+ switch mod.mod {
+ case dereference:
+ // 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
+ }
+ case reference:
+ // 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
+ }
+ case chanRead:
+ // For every "<-" operator, remove a layer of channelness.
+ if ch, ok := typ.(*types.Chan); ok {
+ typ = ch.Elem()
+ } else {
+ return nil
+ }
+ }
+ }
+
+ return typ
+}
+
+// applyTypeNameModifiers applies the list of type modifiers to a type name.
+func (ci candidateInference) applyTypeNameModifiers(typ types.Type) types.Type {
+ for _, mod := range ci.typeName.modifiers {
+ switch mod.mod {
+ case reference:
+ typ = types.NewPointer(typ)
+ case arrayType:
+ typ = types.NewArray(typ, mod.arrayLen)
+ case sliceType:
+ typ = types.NewSlice(typ)
+ }
+ }
+ return typ
+}
+
+// matchesVariadic returns true if we are completing a variadic
+// parameter and candType is a compatible slice type.
+func (ci candidateInference) matchesVariadic(candType types.Type) bool {
+ return ci.variadic && ci.objType != nil && assignableTo(candType, types.NewSlice(ci.objType))
+}
+
+// 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.
+ if exprAtPos(pos, c.List) >= len(c.List) {
+ 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
+ }
+}
+
+// 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.
+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 !source.NodeContains(n.Type, pos)
+ case *ast.CallExpr:
+ // Doesn't break inference if pos is in func name.
+ // For example: "Foo<>(123)"
+ return !source.NodeContains(n.Fun, pos)
+ case *ast.FuncLit, *ast.IndexExpr, *ast.SliceExpr:
+ return true
+ default:
+ return false
+ }
+}
+
+// expectTypeName returns information about the expected type name at position.
+func expectTypeName(c *completer) typeNameInference {
+ var inf typeNameInference
+
+Nodes:
+ for i, p := range c.path {
+ switch n := p.(type) {
+ 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.
+ inf.wantTypeName = true
+ break Nodes
+ case *ast.CaseClause:
+ // Expect type names in type switch case clauses.
+ 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 {
+ inf.assertableFrom = c.pkg.GetTypesInfo().TypeOf(ta.X)
+ return false
+ }
+ return true
+ })
+ inf.wantTypeName = true
+
+ // 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 {
+ inf.seenTypeSwitchCases = append(inf.seenTypeSwitchCases, t)
+ }
+ }
+ }
+
+ break Nodes
+ }
+ return typeNameInference{}
+ case *ast.TypeAssertExpr:
+ // Expect type names in type assert expressions.
+ if n.Lparen < c.pos && c.pos <= n.Rparen {
+ // The type in parens must be assertable from the expression type.
+ inf.assertableFrom = c.pkg.GetTypesInfo().TypeOf(n.X)
+ inf.wantTypeName = true
+ break Nodes
+ }
+ return typeNameInference{}
+ case *ast.StarExpr:
+ inf.modifiers = append(inf.modifiers, typeMod{mod: reference})
+ 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() {
+ 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, typeMod{mod: reference})
+ }
+ }
+ }
+ 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() {
+ inf.wantTypeName = true
+ if n.Len == nil {
+ // No "Len" expression means a slice type.
+ inf.modifiers = append(inf.modifiers, typeMod{mod: sliceType})
+ } 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 {
+ inf.modifiers = append(inf.modifiers, typeMod{mod: arrayType, arrayLen: arrayLen})
+ }
+ }
+ }
+
+ // 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
+ }
+ case *ast.MapType:
+ inf.wantTypeName = true
+ if n.Key != nil {
+ inf.wantComparable = source.NodeContains(n.Key, c.pos)
+ } else {
+ // If the key is empty, assume we are completing the key if
+ // pos is directly after the "map[".
+ inf.wantComparable = c.pos == n.Pos()+token.Pos(len("map["))
+ }
+ break Nodes
+ case *ast.ValueSpec:
+ inf.wantTypeName = source.NodeContains(n.Type, c.pos)
+ break Nodes
+ case *ast.TypeSpec:
+ inf.wantTypeName = source.NodeContains(n.Type, c.pos)
+ default:
+ if breaksExpectedTypeInference(p, c.pos) {
+ return typeNameInference{}
+ }
+ }
+ }
+
+ return inf
+}
+
+func (c *completer) fakeObj(T types.Type) *types.Var {
+ return types.NewVar(token.NoPos, c.pkg.GetTypes(), "", T)
+}
+
+// derivableTypes iterates types you can derive from t. For example,
+// from "foo" we might derive "&foo", and "foo()".
+func derivableTypes(t types.Type, addressable bool, f func(t types.Type, addressable bool, mod typeModKind) bool) bool {
+ switch t := t.Underlying().(type) {
+ case *types.Signature:
+ // If t is a func type with a single result, offer the result type.
+ if t.Results().Len() == 1 && f(t.Results().At(0).Type(), false, invoke) {
+ return true
+ }
+ case *types.Array:
+ if f(t.Elem(), true, index) {
+ return true
+ }
+ // Try converting array to slice.
+ if f(types.NewSlice(t.Elem()), false, takeSlice) {
+ return true
+ }
+ case *types.Pointer:
+ if f(t.Elem(), false, dereference) {
+ return true
+ }
+ case *types.Slice:
+ if f(t.Elem(), true, index) {
+ return true
+ }
+ case *types.Map:
+ if f(t.Elem(), false, index) {
+ return true
+ }
+ case *types.Chan:
+ if f(t.Elem(), false, chanRead) {
+ return true
+ }
+ }
+
+ // Check if c is addressable and a pointer to c matches our type inference.
+ if addressable && f(types.NewPointer(t), false, reference) {
+ return true
+ }
+
+ return false
+}
+
+// anyCandType reports whether f returns true for any candidate type
+// derivable from c. It searches up to three levels of type
+// modification. For example, given "foo" we could discover "***foo"
+// or "*foo()".
+func (c *candidate) anyCandType(f func(t types.Type, addressable bool) bool) bool {
+ if c.obj == nil || c.obj.Type() == nil {
+ return false
+ }
+
+ const maxDepth = 3
+
+ var searchTypes func(t types.Type, addressable bool, mods []typeModKind) bool
+ searchTypes = func(t types.Type, addressable bool, mods []typeModKind) bool {
+ if f(t, addressable) {
+ if len(mods) > 0 {
+ newMods := make([]typeModKind, len(mods)+len(c.mods))
+ copy(newMods, mods)
+ copy(newMods[len(mods):], c.mods)
+ c.mods = newMods
+ }
+ return true
+ }
+
+ if len(mods) == maxDepth {
+ return false
+ }
+
+ return derivableTypes(t, addressable, func(t types.Type, addressable bool, mod typeModKind) bool {
+ return searchTypes(t, addressable, append(mods, mod))
+ })
+ }
+
+ return searchTypes(c.obj.Type(), c.addressable, make([]typeModKind, 0, maxDepth))
+}
+
+// matchingCandidate reports whether cand matches our type inferences.
+// It mutates cand's score in certain cases.
+func (c *completer) matchingCandidate(cand *candidate) bool {
+ if c.completionContext.commentCompletion {
+ return false
+ }
+
+ // Bail out early if we are completing a field name in a composite literal.
+ if v, ok := cand.obj.(*types.Var); ok && v.IsField() && c.wantStructFieldCompletions() {
+ return true
+ }
+
+ if isTypeName(cand.obj) {
+ return c.matchingTypeName(cand)
+ } else if c.wantTypeName() {
+ // If we want a type, a non-type object never matches.
+ return false
+ }
+
+ if c.inference.candTypeMatches(cand) {
+ return true
+ }
+
+ candType := cand.obj.Type()
+ if candType == nil {
+ return false
+ }
+
+ if sig, ok := candType.Underlying().(*types.Signature); ok {
+ if c.inference.assigneesMatch(cand, sig) {
+ // Invoke the candidate if its results are multi-assignable.
+ cand.mods = append(cand.mods, invoke)
+ 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.
+ if isFunc(cand.obj) {
+ cand.mods = append(cand.mods, invoke)
+ }
+
+ return false
+}
+
+// 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 {
+ var (
+ expTypes = make([]types.Type, 0, 2)
+ variadicType types.Type
+ )
+ if ci.objType != nil {
+ expTypes = append(expTypes, ci.objType)
+
+ if ci.variadic {
+ variadicType = types.NewSlice(ci.objType)
+ expTypes = append(expTypes, variadicType)
+ }
+ }
+
+ 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 && convertibleTo(candType, ci.convertibleTo) {
+ return true
+ }
+
+ for _, expType := range expTypes {
+ if isEmptyInterface(expType) {
+ continue
+ }
+
+ matches := ci.typeMatches(expType, candType)
+ if !matches {
+ // If candType doesn't otherwise match, consider if we can
+ // convert candType directly to expType.
+ if considerTypeConversion(candType, expType, cand.path) {
+ cand.convertTo = expType
+ // Give a major score penalty so we always prefer directly
+ // assignable candidates, all else equal.
+ cand.score *= 0.5
+ return true
+ }
+
+ continue
+ }
+
+ if expType == variadicType {
+ cand.mods = append(cand.mods, takeDotDotDot)
+ }
+
+ // 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 isUntyped(candType) && !types.Identical(candType, expType) && cand.obj.Parent() != types.Universe {
+ // Bigger penalty for deep completions into other packages to
+ // avoid random constants from other packages popping up all
+ // the time.
+ if len(cand.path) > 0 && isPkgName(cand.path[0]) {
+ cand.score *= 0.5
+ } else {
+ cand.score *= 0.75
+ }
+ }
+
+ return true
+ }
+
+ // 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.mods = append(cand.mods, invoke)
+ }
+ return true
+ }
+ }
+
+ return false
+ })
+}
+
+// considerTypeConversion returns true if we should offer a completion
+// automatically converting "from" to "to".
+func considerTypeConversion(from, to types.Type, path []types.Object) bool {
+ // Don't offer to convert deep completions from other packages.
+ // Otherwise there are many random package level consts/vars that
+ // pop up as candidates all the time.
+ if len(path) > 0 && isPkgName(path[0]) {
+ return false
+ }
+
+ if _, ok := from.(*typeparams.TypeParam); ok {
+ return false
+ }
+
+ if !convertibleTo(from, to) {
+ return false
+ }
+
+ // Don't offer to convert ints to strings since that probably
+ // doesn't do what the user wants.
+ if isBasicKind(from, types.IsInteger) && isBasicKind(to, types.IsString) {
+ return false
+ }
+
+ return true
+}
+
+// typeMatches reports whether an object of candType makes a good
+// completion candidate given the expected type expType.
+func (ci *candidateInference) typeMatches(expType, candType types.Type) bool {
+ // 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 expBasic, ok := expType.Underlying().(*types.Basic); ok {
+ // Note that the candidate and/or the expected can be untyped.
+ // In "fo<> == 100" the expected type is untyped, and the
+ // candidate could also be an untyped constant.
+
+ // Sort by is_untyped and then by is_int to simplify below logic.
+ a, b := candBasic.Info(), expBasic.Info()
+ if a&types.IsUntyped == 0 || (b&types.IsInteger > 0 && b&types.IsUntyped > 0) {
+ a, b = b, a
+ }
+
+ // If at least one is untyped...
+ if a&types.IsUntyped > 0 {
+ switch {
+ // Untyped integers are compatible with floats.
+ case a&types.IsInteger > 0 && b&types.IsFloat > 0:
+ return true
+
+ // Check if 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.
+ case a&types.IsConstType == b&types.IsConstType:
+ return true
+ }
+ }
+ }
+ }
+
+ // AssignableTo covers the case where the types are equal, but also handles
+ // cases like assigning a concrete type to an interface type.
+ return assignableTo(candType, expType)
+}
+
+// kindMatches reports whether candType's kind matches our expected
+// kind (e.g. slice, map, etc.).
+func (ci *candidateInference) kindMatches(candType types.Type) bool {
+ return ci.objKind > 0 && ci.objKind&candKind(candType) > 0
+}
+
+// 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 {
+ if len(ci.assignees) == 0 {
+ return false
+ }
+
+ // Uniresult functions are always usable and are handled by the
+ // normal, non-assignees type matching logic.
+ if sig.Results().Len() == 1 {
+ return false
+ }
+
+ // Don't prefer completing into func(...interface{}) calls since all
+ // functions would match.
+ if ci.variadicAssignees && len(ci.assignees) == 1 && isEmptyInterface(deslice(ci.assignees[0])) {
+ return false
+ }
+
+ var numberOfResultsCouldMatch bool
+ if ci.variadicAssignees {
+ numberOfResultsCouldMatch = sig.Results().Len() >= len(ci.assignees)-1
+ } else {
+ numberOfResultsCouldMatch = sig.Results().Len() == len(ci.assignees)
+ }
+
+ // 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
+ if !numberOfResultsCouldMatch {
+ 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
+ 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]
+ if elem := deslice(assignee); elem != nil {
+ assignee = elem
+ }
+ } else {
+ assignee = ci.assignees[i]
+ }
+
+ if assignee == nil || assignee == types.Typ[types.Invalid] {
+ continue
+ }
+
+ allMatch = ci.typeMatches(assignee, sig.Results().At(i).Type())
+ if !allMatch {
+ break
+ }
+ }
+ return allMatch
+}
+
+func (c *completer) matchingTypeName(cand *candidate) bool {
+ if !c.wantTypeName() {
+ return false
+ }
+
+ typeMatches := func(candType types.Type) bool {
+ // Take into account any type name modifier prefixes.
+ candType = c.inference.applyTypeNameModifiers(candType)
+
+ if from := c.inference.typeName.assertableFrom; from != nil {
+ // 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
+ }
+ }
+ }
+
+ if c.inference.typeName.wantComparable && !types.Comparable(candType) {
+ return false
+ }
+
+ // 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
+ }
+ }
+
+ // 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.
+ if c.inference.objType != nil {
+ return assignableTo(candType, c.inference.objType)
+ }
+
+ // Default to saying any type name is a match.
+ return true
+ }
+
+ t := cand.obj.Type()
+
+ if typeMatches(t) {
+ return true
+ }
+
+ if !types.IsInterface(t) && typeMatches(types.NewPointer(t)) {
+ if c.inference.typeName.compLitType {
+ // If we are completing a composite literal type as in
+ // "foo<>{}", to make a pointer we must prepend "&".
+ cand.mods = append(cand.mods, reference)
+ } else {
+ // If we are completing a normal type name such as "foo<>", to
+ // make a pointer we must prepend "*".
+ cand.mods = append(cand.mods, dereference)
+ }
+ return true
+ }
+
+ return false
+}
+
+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()
+)
+
+// candKind returns the objKind of candType, if any.
+func candKind(candType types.Type) objKind {
+ var kind objKind
+
+ switch t := candType.Underlying().(type) {
+ case *types.Array:
+ kind |= kindArray
+ if t.Elem() == byteType {
+ kind |= kindBytes
+ }
+ case *types.Slice:
+ kind |= kindSlice
+ if t.Elem() == byteType {
+ kind |= kindBytes
+ }
+ case *types.Chan:
+ kind |= kindChan
+ case *types.Map:
+ kind |= kindMap
+ case *types.Pointer:
+ kind |= kindPtr
+
+ // 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 {
+ kind |= kindArray
+ }
+ case *types.Basic:
+ 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
+ }
+ case *types.Signature:
+ return kindFunc
+ }
+
+ if types.Implements(candType, errorIntf) {
+ kind |= kindError
+ }
+
+ if types.Implements(candType, stringerIntf) {
+ kind |= kindStringer
+ }
+
+ return kind
+}
+
+// innermostScope returns the innermost scope for c.pos.
+func (c *completer) innermostScope() *types.Scope {
+ for _, s := range c.scopes {
+ if s != nil {
+ return s
+ }
+ }
+ return nil
+}
+
+// isSlice reports whether the object's underlying type is a slice.
+func isSlice(obj types.Object) bool {
+ if obj != nil && obj.Type() != nil {
+ if _, ok := obj.Type().Underlying().(*types.Slice); ok {
+ return true
+ }
+ }
+ return false
+}
+
+// forEachPackageMember calls f(tok, id, fn) for each package-level
+// TYPE/VAR/CONST/FUNC declaration in the Go source file, based on a
+// quick partial parse. fn is non-nil only for function declarations.
+// The AST position information is garbage.
+func forEachPackageMember(content []byte, f func(tok token.Token, id *ast.Ident, fn *ast.FuncDecl)) {
+ purged := purgeFuncBodies(content)
+ file, _ := parser.ParseFile(token.NewFileSet(), "", purged, 0)
+ for _, decl := range file.Decls {
+ switch decl := decl.(type) {
+ case *ast.GenDecl:
+ for _, spec := range decl.Specs {
+ switch spec := spec.(type) {
+ case *ast.ValueSpec: // var/const
+ for _, id := range spec.Names {
+ f(decl.Tok, id, nil)
+ }
+ case *ast.TypeSpec:
+ f(decl.Tok, spec.Name, nil)
+ }
+ }
+ case *ast.FuncDecl:
+ if decl.Recv == nil {
+ f(token.FUNC, decl.Name, decl)
+ }
+ }
+ }
+}
+
+// purgeFuncBodies returns a copy of src in which the contents of each
+// outermost {...} region except struct and interface types have been
+// deleted. It does not preserve newlines. This reduces the amount of
+// work required to parse the top-level declarations.
+func purgeFuncBodies(src []byte) []byte {
+ // Destroy the content of any {...}-bracketed regions that are
+ // not immediately preceded by a "struct" or "interface"
+ // token. That includes function bodies, composite literals,
+ // switch/select bodies, and all blocks of statements.
+ // This will lead to non-void functions that don't have return
+ // statements, which of course is a type error, but that's ok.
+
+ var out bytes.Buffer
+ file := token.NewFileSet().AddFile("", -1, len(src))
+ var sc scanner.Scanner
+ sc.Init(file, src, nil, 0)
+ var prev token.Token
+ var cursor int // last consumed src offset
+ var braces []token.Pos // stack of unclosed braces or -1 for struct/interface type
+ for {
+ pos, tok, _ := sc.Scan()
+ if tok == token.EOF {
+ break
+ }
+ switch tok {
+ case token.COMMENT:
+ // TODO(adonovan): opt: skip, to save an estimated 20% of time.
+
+ case token.LBRACE:
+ if prev == token.STRUCT || prev == token.INTERFACE {
+ pos = -1
+ }
+ braces = append(braces, pos)
+
+ case token.RBRACE:
+ if last := len(braces) - 1; last >= 0 {
+ top := braces[last]
+ braces = braces[:last]
+ if top < 0 {
+ // struct/interface type: leave alone
+ } else if len(braces) == 0 { // toplevel only
+ // Delete {...} body.
+ start, _ := safetoken.Offset(file, top)
+ end, _ := safetoken.Offset(file, pos)
+ out.Write(src[cursor : start+len("{")])
+ cursor = end
+ }
+ }
+ }
+ prev = tok
+ }
+ out.Write(src[cursor:])
+ return out.Bytes()
+}
diff --git a/gopls/internal/lsp/source/completion/deep_completion.go b/gopls/internal/lsp/source/completion/deep_completion.go
new file mode 100644
index 000000000..a72d56191
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/deep_completion.go
@@ -0,0 +1,362 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "context"
+ "go/types"
+ "strings"
+ "time"
+)
+
+// MaxDeepCompletions limits deep completion results because in most cases
+// there are too many to be useful.
+const MaxDeepCompletions = 3
+
+// deepCompletionState stores our state as we search for deep completions.
+// "deep completion" refers to searching into objects' fields and methods to
+// find more completion candidates.
+type deepCompletionState struct {
+ // enabled indicates whether deep completion is permitted.
+ enabled bool
+
+ // queueClosed is used to disable adding new sub-fields to search queue
+ // once we're running out of our time budget.
+ queueClosed bool
+
+ // thisQueue holds the current breadth first search queue.
+ thisQueue []candidate
+
+ // nextQueue holds the next breadth first search iteration's queue.
+ nextQueue []candidate
+
+ // highScores tracks the highest deep candidate scores we have found
+ // so far. This is used to avoid work for low scoring deep candidates.
+ highScores [MaxDeepCompletions]float64
+
+ // candidateCount is the count of unique deep candidates encountered
+ // so far.
+ candidateCount int
+}
+
+// enqueue adds a candidate to the search queue.
+func (s *deepCompletionState) enqueue(cand candidate) {
+ s.nextQueue = append(s.nextQueue, cand)
+}
+
+// dequeue removes and returns the leftmost element from the search queue.
+func (s *deepCompletionState) dequeue() *candidate {
+ var cand *candidate
+ cand, s.thisQueue = &s.thisQueue[len(s.thisQueue)-1], s.thisQueue[:len(s.thisQueue)-1]
+ return cand
+}
+
+// scorePenalty computes a deep candidate score penalty. A candidate is
+// penalized based on depth to favor shallower candidates. We also give a
+// slight bonus to unexported objects and a slight additional penalty to
+// function objects.
+func (s *deepCompletionState) scorePenalty(cand *candidate) float64 {
+ var deepPenalty float64
+ for _, dc := range cand.path {
+ deepPenalty++
+
+ if !dc.Exported() {
+ deepPenalty -= 0.1
+ }
+
+ if _, isSig := dc.Type().Underlying().(*types.Signature); isSig {
+ deepPenalty += 0.1
+ }
+ }
+
+ // Normalize penalty to a max depth of 10.
+ return deepPenalty / 10
+}
+
+// isHighScore returns whether score is among the top MaxDeepCompletions deep
+// candidate scores encountered so far. If so, it adds score to highScores,
+// possibly displacing an existing high score.
+func (s *deepCompletionState) isHighScore(score float64) bool {
+ // Invariant: s.highScores is sorted with highest score first. Unclaimed
+ // positions are trailing zeros.
+
+ // If we beat an existing score then take its spot.
+ for i, deepScore := range s.highScores {
+ if score <= deepScore {
+ continue
+ }
+
+ if deepScore != 0 && i != len(s.highScores)-1 {
+ // If this wasn't an empty slot then we need to scooch everyone
+ // down one spot.
+ copy(s.highScores[i+1:], s.highScores[i:])
+ }
+ s.highScores[i] = score
+ return true
+ }
+
+ return false
+}
+
+// newPath returns path from search root for an object following a given
+// candidate.
+func (s *deepCompletionState) newPath(cand candidate, obj types.Object) []types.Object {
+ path := make([]types.Object, len(cand.path)+1)
+ copy(path, cand.path)
+ path[len(path)-1] = obj
+
+ return path
+}
+
+// deepSearch searches a candidate and its subordinate objects for completion
+// items if deep completion is enabled and adds the valid candidates to
+// completion items.
+func (c *completer) deepSearch(ctx context.Context) {
+ defer func() {
+ // We can return early before completing the search, so be sure to
+ // clear out our queues to not impact any further invocations.
+ c.deepState.thisQueue = c.deepState.thisQueue[:0]
+ c.deepState.nextQueue = c.deepState.nextQueue[:0]
+ }()
+
+ for len(c.deepState.nextQueue) > 0 {
+ c.deepState.thisQueue, c.deepState.nextQueue = c.deepState.nextQueue, c.deepState.thisQueue[:0]
+
+ outer:
+ for _, cand := range c.deepState.thisQueue {
+ obj := cand.obj
+
+ if obj == nil {
+ continue
+ }
+
+ // At the top level, dedupe by object.
+ if len(cand.path) == 0 {
+ if c.seen[obj] {
+ continue
+ }
+ c.seen[obj] = true
+ }
+
+ // If obj is not accessible because it lives in another package and is
+ // not exported, don't treat it as a completion candidate unless it's
+ // a package completion candidate.
+ if !c.completionContext.packageCompletion &&
+ obj.Pkg() != nil && obj.Pkg() != c.pkg.GetTypes() && !obj.Exported() {
+ continue
+ }
+
+ // If 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) {
+ continue
+ }
+
+ // When searching deep, 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 cand.path {
+ if seenObj == obj {
+ continue outer
+ }
+ }
+
+ c.addCandidate(ctx, &cand)
+
+ c.deepState.candidateCount++
+ if c.opts.budget > 0 && c.deepState.candidateCount%100 == 0 {
+ spent := float64(time.Since(c.startTime)) / float64(c.opts.budget)
+ select {
+ case <-ctx.Done():
+ return
+ default:
+ // If we are almost out of budgeted time, no further elements
+ // should be added to the queue. This ensures remaining time is
+ // used for processing current queue.
+ if !c.deepState.queueClosed && spent >= 0.85 {
+ c.deepState.queueClosed = true
+ }
+ }
+ }
+
+ // if deep search is disabled, don't add any more candidates.
+ if !c.deepState.enabled || c.deepState.queueClosed {
+ continue
+ }
+
+ // Searching members for a type name doesn't make sense.
+ if isTypeName(obj) {
+ continue
+ }
+ if obj.Type() == nil {
+ continue
+ }
+
+ // Don't search embedded fields because they were already included in their
+ // parent's fields.
+ if v, ok := obj.(*types.Var); ok && v.Embedded() {
+ continue
+ }
+
+ if sig, ok := obj.Type().Underlying().(*types.Signature); ok {
+ // If obj is a function that takes no arguments and returns one
+ // value, keep searching across the function call.
+ if sig.Params().Len() == 0 && sig.Results().Len() == 1 {
+ path := c.deepState.newPath(cand, obj)
+ // The result of a function call is not addressable.
+ c.methodsAndFields(sig.Results().At(0).Type(), false, cand.imp, func(newCand candidate) {
+ newCand.pathInvokeMask = cand.pathInvokeMask | (1 << uint64(len(cand.path)))
+ newCand.path = path
+ c.deepState.enqueue(newCand)
+ })
+ }
+ }
+
+ path := c.deepState.newPath(cand, obj)
+ switch obj := obj.(type) {
+ case *types.PkgName:
+ c.packageMembers(obj.Imported(), stdScore, cand.imp, func(newCand candidate) {
+ newCand.pathInvokeMask = cand.pathInvokeMask
+ newCand.path = path
+ c.deepState.enqueue(newCand)
+ })
+ default:
+ c.methodsAndFields(obj.Type(), cand.addressable, cand.imp, func(newCand candidate) {
+ newCand.pathInvokeMask = cand.pathInvokeMask
+ newCand.path = path
+ c.deepState.enqueue(newCand)
+ })
+ }
+ }
+ }
+}
+
+// addCandidate adds a completion candidate to suggestions, without searching
+// its members for more candidates.
+func (c *completer) addCandidate(ctx context.Context, cand *candidate) {
+ obj := cand.obj
+ if c.matchingCandidate(cand) {
+ cand.score *= highScore
+
+ if p := c.penalty(cand); p > 0 {
+ cand.score *= (1 - p)
+ }
+ } 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.
+
+ // We only care about named types (i.e. don't want builtin types).
+ if _, isNamed := obj.Type().(*types.Named); isNamed {
+ c.literal(ctx, obj.Type(), cand.imp)
+ }
+ }
+
+ // Lower score of method calls so we prefer fields and vars over calls.
+ if cand.hasMod(invoke) {
+ if sig, ok := obj.Type().Underlying().(*types.Signature); ok && sig.Recv() != nil {
+ cand.score *= 0.9
+ }
+ }
+
+ // Prefer private objects over public ones.
+ if !obj.Exported() && obj.Parent() != types.Universe {
+ cand.score *= 1.1
+ }
+
+ // Slight penalty for index modifier (e.g. changing "foo" to
+ // "foo[]") to curb false positives.
+ if cand.hasMod(index) {
+ cand.score *= 0.9
+ }
+
+ // Favor shallow matches by lowering score according to depth.
+ cand.score -= cand.score * c.deepState.scorePenalty(cand)
+
+ if cand.score < 0 {
+ cand.score = 0
+ }
+
+ cand.name = deepCandName(cand)
+ if item, err := c.item(ctx, *cand); err == nil {
+ c.items = append(c.items, item)
+ }
+}
+
+// deepCandName produces the full candidate name including any
+// ancestor objects. For example, "foo.bar().baz" for candidate "baz".
+func deepCandName(cand *candidate) string {
+ totalLen := len(cand.obj.Name())
+ for i, obj := range cand.path {
+ totalLen += len(obj.Name()) + 1
+ if cand.pathInvokeMask&(1<<uint16(i)) > 0 {
+ totalLen += 2
+ }
+ }
+
+ var buf strings.Builder
+ buf.Grow(totalLen)
+
+ for i, obj := range cand.path {
+ buf.WriteString(obj.Name())
+ if cand.pathInvokeMask&(1<<uint16(i)) > 0 {
+ buf.WriteByte('(')
+ buf.WriteByte(')')
+ }
+ buf.WriteByte('.')
+ }
+
+ buf.WriteString(cand.obj.Name())
+
+ return buf.String()
+}
+
+// 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, p.objChain) {
+ return p.penalty
+ }
+ }
+
+ return 0
+}
+
+// objChainMatches reports whether cand combined with the surrounding
+// object prefix matches chain.
+func (c *completer) objChainMatches(cand *candidate, 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(cand.path)+1 {
+ return false
+ }
+
+ if chain[len(chain)-1] != cand.obj {
+ return false
+ }
+
+ for i, o := range c.inference.objChain {
+ if chain[i] != o {
+ return false
+ }
+ }
+
+ for i, o := range cand.path {
+ if chain[i+len(c.inference.objChain)] != o {
+ return false
+ }
+ }
+
+ return true
+}
diff --git a/gopls/internal/lsp/source/completion/deep_completion_test.go b/gopls/internal/lsp/source/completion/deep_completion_test.go
new file mode 100644
index 000000000..27009af1b
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/deep_completion_test.go
@@ -0,0 +1,33 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "testing"
+)
+
+func TestDeepCompletionIsHighScore(t *testing.T) {
+ // Test that deepCompletionState.isHighScore properly tracks the top
+ // N=MaxDeepCompletions scores.
+
+ var s deepCompletionState
+
+ if !s.isHighScore(1) {
+ // No other scores yet, anything is a winner.
+ t.Error("1 should be high score")
+ }
+
+ // Fill up with higher scores.
+ for i := 0; i < MaxDeepCompletions; i++ {
+ if !s.isHighScore(10) {
+ t.Error("10 should be high score")
+ }
+ }
+
+ // High scores should be filled with 10s so 2 is not a high score.
+ if s.isHighScore(2) {
+ t.Error("2 shouldn't be high score")
+ }
+}
diff --git a/gopls/internal/lsp/source/completion/definition.go b/gopls/internal/lsp/source/completion/definition.go
new file mode 100644
index 000000000..d7f51f002
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/definition.go
@@ -0,0 +1,160 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "go/ast"
+ "go/types"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+
+ "golang.org/x/tools/gopls/internal/lsp/protocol"
+ "golang.org/x/tools/gopls/internal/lsp/snippet"
+ "golang.org/x/tools/gopls/internal/lsp/source"
+)
+
+// some function definitions in test files can be completed
+// So far, TestFoo(t *testing.T), TestMain(m *testing.M)
+// BenchmarkFoo(b *testing.B), FuzzFoo(f *testing.F)
+
+// path[0] is known to be *ast.Ident
+func definition(path []ast.Node, obj types.Object, pgf *source.ParsedGoFile) ([]CompletionItem, *Selection) {
+ if _, ok := obj.(*types.Func); !ok {
+ return nil, nil // not a function at all
+ }
+ if !strings.HasSuffix(pgf.URI.Filename(), "_test.go") {
+ return nil, nil // not a test file
+ }
+
+ name := path[0].(*ast.Ident).Name
+ if len(name) == 0 {
+ // can't happen
+ return nil, nil
+ }
+ start := path[0].Pos()
+ end := path[0].End()
+ sel := &Selection{
+ content: "",
+ cursor: start,
+ tokFile: pgf.Tok,
+ start: start,
+ end: end,
+ mapper: pgf.Mapper,
+ }
+ var ans []CompletionItem
+ var hasParens bool
+ n, ok := path[1].(*ast.FuncDecl)
+ if !ok {
+ return nil, nil // can't happen
+ }
+ if n.Recv != nil {
+ return nil, nil // a method, not a function
+ }
+ t := n.Type.Params
+ if t.Closing != t.Opening {
+ hasParens = true
+ }
+
+ // Always suggest TestMain, if possible
+ if strings.HasPrefix("TestMain", name) {
+ if hasParens {
+ ans = append(ans, defItem("TestMain", obj))
+ } else {
+ ans = append(ans, defItem("TestMain(m *testing.M)", obj))
+ }
+ }
+
+ // If a snippet is possible, suggest it
+ if strings.HasPrefix("Test", name) {
+ if hasParens {
+ ans = append(ans, defItem("Test", obj))
+ } else {
+ ans = append(ans, defSnippet("Test", "(t *testing.T)", obj))
+ }
+ return ans, sel
+ } else if strings.HasPrefix("Benchmark", name) {
+ if hasParens {
+ ans = append(ans, defItem("Benchmark", obj))
+ } else {
+ ans = append(ans, defSnippet("Benchmark", "(b *testing.B)", obj))
+ }
+ return ans, sel
+ } else if strings.HasPrefix("Fuzz", name) {
+ if hasParens {
+ ans = append(ans, defItem("Fuzz", obj))
+ } else {
+ ans = append(ans, defSnippet("Fuzz", "(f *testing.F)", obj))
+ }
+ return ans, sel
+ }
+
+ // Fill in the argument for what the user has already typed
+ if got := defMatches(name, "Test", path, "(t *testing.T)"); got != "" {
+ ans = append(ans, defItem(got, obj))
+ } else if got := defMatches(name, "Benchmark", path, "(b *testing.B)"); got != "" {
+ ans = append(ans, defItem(got, obj))
+ } else if got := defMatches(name, "Fuzz", path, "(f *testing.F)"); got != "" {
+ ans = append(ans, defItem(got, obj))
+ }
+ return ans, sel
+}
+
+// defMatches returns text for defItem, never for defSnippet
+func defMatches(name, pat string, path []ast.Node, arg string) string {
+ if !strings.HasPrefix(name, pat) {
+ return ""
+ }
+ c, _ := utf8.DecodeRuneInString(name[len(pat):])
+ if unicode.IsLower(c) {
+ return ""
+ }
+ fd, ok := path[1].(*ast.FuncDecl)
+ if !ok {
+ // we don't know what's going on
+ return ""
+ }
+ fp := fd.Type.Params
+ if len(fp.List) > 0 {
+ // signature already there, nothing to suggest
+ return ""
+ }
+ if fp.Opening != fp.Closing {
+ // nothing: completion works on words, not easy to insert arg
+ return ""
+ }
+ // suggesting signature too
+ return name + arg
+}
+
+func defSnippet(prefix, suffix string, obj types.Object) CompletionItem {
+ var sn snippet.Builder
+ sn.WriteText(prefix)
+ sn.WritePlaceholder(func(b *snippet.Builder) { b.WriteText("Xxx") })
+ sn.WriteText(suffix + " {\n\t")
+ sn.WriteFinalTabstop()
+ sn.WriteText("\n}")
+ return CompletionItem{
+ Label: prefix + "Xxx" + suffix,
+ Detail: "tab, type the rest of the name, then tab",
+ Kind: protocol.FunctionCompletion,
+ Depth: 0,
+ Score: 10,
+ snippet: &sn,
+ Documentation: prefix + " test function",
+ isSlice: isSlice(obj),
+ }
+}
+func defItem(val string, obj types.Object) CompletionItem {
+ return CompletionItem{
+ Label: val,
+ InsertText: val,
+ Kind: protocol.FunctionCompletion,
+ Depth: 0,
+ Score: 9, // prefer the snippets when available
+ Documentation: "complete the function name",
+ isSlice: isSlice(obj),
+ }
+}
diff --git a/gopls/internal/lsp/source/completion/format.go b/gopls/internal/lsp/source/completion/format.go
new file mode 100644
index 000000000..c2d2c0bc0
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/format.go
@@ -0,0 +1,338 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/doc"
+ "go/types"
+ "strings"
+
+ "golang.org/x/tools/gopls/internal/lsp/protocol"
+ "golang.org/x/tools/gopls/internal/lsp/safetoken"
+ "golang.org/x/tools/gopls/internal/lsp/snippet"
+ "golang.org/x/tools/gopls/internal/lsp/source"
+ "golang.org/x/tools/gopls/internal/span"
+ "golang.org/x/tools/internal/event"
+ "golang.org/x/tools/internal/imports"
+ "golang.org/x/tools/internal/typeparams"
+)
+
+var (
+ errNoMatch = errors.New("not a surrounding match")
+ errLowScore = errors.New("not a high scoring candidate")
+)
+
+// item formats a candidate to a CompletionItem.
+func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, error) {
+ obj := cand.obj
+
+ // if the object isn't a valid match against the surrounding, return early.
+ matchScore := c.matcher.Score(cand.name)
+ if matchScore <= 0 {
+ return CompletionItem{}, errNoMatch
+ }
+ cand.score *= float64(matchScore)
+
+ // Ignore deep candidates that won't be in the MaxDeepCompletions anyway.
+ if len(cand.path) != 0 && !c.deepState.isHighScore(cand.score) {
+ return CompletionItem{}, errLowScore
+ }
+
+ // Handle builtin types separately.
+ if obj.Parent() == types.Universe {
+ return c.formatBuiltin(ctx, cand)
+ }
+
+ var (
+ label = cand.name
+ detail = types.TypeString(obj.Type(), c.qf)
+ insert = label
+ kind = protocol.TextCompletion
+ snip snippet.Builder
+ protocolEdits []protocol.TextEdit
+ )
+ if obj.Type() == nil {
+ detail = ""
+ }
+ if isTypeName(obj) && c.wantTypeParams() {
+ x := cand.obj.(*types.TypeName)
+ if named, ok := x.Type().(*types.Named); ok {
+ tp := typeparams.ForNamed(named)
+ label += source.FormatTypeParams(tp)
+ insert = label // maintain invariant above (label == insert)
+ }
+ }
+
+ snip.WriteText(insert)
+
+ switch obj := obj.(type) {
+ case *types.TypeName:
+ detail, kind = source.FormatType(obj.Type(), c.qf)
+ case *types.Const:
+ kind = protocol.ConstantCompletion
+ case *types.Var:
+ if _, ok := obj.Type().(*types.Struct); ok {
+ detail = "struct{...}" // for anonymous structs
+ } else if obj.IsField() {
+ var err error
+ detail, err = source.FormatVarType(ctx, c.snapshot, c.pkg, obj, c.qf, c.mq)
+ if err != nil {
+ return CompletionItem{}, err
+ }
+ }
+ if obj.IsField() {
+ kind = protocol.FieldCompletion
+ c.structFieldSnippet(cand, detail, &snip)
+ } else {
+ kind = protocol.VariableCompletion
+ }
+ if obj.Type() == nil {
+ break
+ }
+ case *types.Func:
+ sig, ok := obj.Type().Underlying().(*types.Signature)
+ if !ok {
+ break
+ }
+ kind = protocol.FunctionCompletion
+ if sig != nil && sig.Recv() != nil {
+ kind = protocol.MethodCompletion
+ }
+ case *types.PkgName:
+ kind = protocol.ModuleCompletion
+ detail = fmt.Sprintf("%q", obj.Imported().Path())
+ case *types.Label:
+ kind = protocol.ConstantCompletion
+ detail = "label"
+ }
+
+ var prefix string
+ for _, mod := range cand.mods {
+ switch mod {
+ case reference:
+ prefix = "&" + prefix
+ case dereference:
+ prefix = "*" + prefix
+ case chanRead:
+ prefix = "<-" + prefix
+ }
+ }
+
+ var (
+ suffix string
+ funcType = obj.Type()
+ )
+Suffixes:
+ for _, mod := range cand.mods {
+ switch mod {
+ case invoke:
+ if sig, ok := funcType.Underlying().(*types.Signature); ok {
+ s, err := source.NewSignature(ctx, c.snapshot, c.pkg, sig, nil, c.qf, c.mq)
+ if err != nil {
+ return CompletionItem{}, err
+ }
+ c.functionCallSnippet("", s.TypeParams(), s.Params(), &snip)
+ if sig.Results().Len() == 1 {
+ funcType = sig.Results().At(0).Type()
+ }
+ detail = "func" + s.Format()
+ }
+
+ if !c.opts.snippets {
+ // Without snippets the candidate will not include "()". Don't
+ // add further suffixes since they will be invalid. For
+ // example, with snippets "foo()..." would become "foo..."
+ // without snippets if we added the dotDotDot.
+ break Suffixes
+ }
+ case takeSlice:
+ suffix += "[:]"
+ case takeDotDotDot:
+ suffix += "..."
+ case index:
+ snip.WriteText("[")
+ snip.WritePlaceholder(nil)
+ snip.WriteText("]")
+ }
+ }
+
+ // If this candidate needs an additional import statement,
+ // add the additional text edits needed.
+ if cand.imp != nil {
+ addlEdits, err := c.importEdits(cand.imp)
+
+ if err != nil {
+ return CompletionItem{}, err
+ }
+
+ protocolEdits = append(protocolEdits, addlEdits...)
+ if kind != protocol.ModuleCompletion {
+ if detail != "" {
+ detail += " "
+ }
+ detail += fmt.Sprintf("(from %q)", cand.imp.importPath)
+ }
+ }
+
+ if cand.convertTo != nil {
+ typeName := types.TypeString(cand.convertTo, c.qf)
+
+ switch cand.convertTo.(type) {
+ // We need extra parens when casting to these types. For example,
+ // we need "(*int)(foo)", not "*int(foo)".
+ case *types.Pointer, *types.Signature:
+ typeName = "(" + typeName + ")"
+ }
+
+ prefix = typeName + "(" + prefix
+ suffix = ")"
+ }
+
+ if prefix != "" {
+ // If we are in a selector, add an edit to place prefix before selector.
+ if sel := enclosingSelector(c.path, c.pos); sel != nil {
+ edits, err := c.editText(sel.Pos(), sel.Pos(), prefix)
+ if err != nil {
+ return CompletionItem{}, err
+ }
+ protocolEdits = append(protocolEdits, edits...)
+ } else {
+ // If there is no selector, just stick the prefix at the start.
+ insert = prefix + insert
+ snip.PrependText(prefix)
+ }
+ }
+
+ if suffix != "" {
+ insert += suffix
+ snip.WriteText(suffix)
+ }
+
+ detail = strings.TrimPrefix(detail, "untyped ")
+ // override computed detail with provided detail, if something is provided.
+ if cand.detail != "" {
+ detail = cand.detail
+ }
+ item := CompletionItem{
+ Label: label,
+ InsertText: insert,
+ AdditionalTextEdits: protocolEdits,
+ Detail: detail,
+ Kind: kind,
+ Score: cand.score,
+ Depth: len(cand.path),
+ snippet: &snip,
+ isSlice: isSlice(obj),
+ }
+ // If the user doesn't want documentation for completion items.
+ if !c.opts.documentation {
+ return item, nil
+ }
+ pos := safetoken.StartPosition(c.pkg.FileSet(), obj.Pos())
+
+ // We ignore errors here, because some types, like "unsafe" or "error",
+ // may not have valid positions that we can use to get documentation.
+ if !pos.IsValid() {
+ return item, nil
+ }
+
+ comment, err := source.HoverDocForObject(ctx, c.snapshot, c.pkg.FileSet(), obj)
+ if err != nil {
+ event.Error(ctx, fmt.Sprintf("failed to find Hover for %q", obj.Name()), err)
+ return item, nil
+ }
+ if c.opts.fullDocumentation {
+ item.Documentation = comment.Text()
+ } else {
+ item.Documentation = doc.Synopsis(comment.Text())
+ }
+ // The desired pattern is `^// Deprecated`, but the prefix has been removed
+ // TODO(rfindley): It doesn't look like this does the right thing for
+ // multi-line comments.
+ if strings.HasPrefix(comment.Text(), "Deprecated") {
+ if c.snapshot.View().Options().CompletionTags {
+ item.Tags = []protocol.CompletionItemTag{protocol.ComplDeprecated}
+ } else if c.snapshot.View().Options().CompletionDeprecated {
+ item.Deprecated = true
+ }
+ }
+
+ return item, nil
+}
+
+// importEdits produces the text edits necessary to add the given import to the current file.
+func (c *completer) importEdits(imp *importInfo) ([]protocol.TextEdit, error) {
+ if imp == nil {
+ return nil, nil
+ }
+
+ pgf, err := c.pkg.File(span.URIFromPath(c.filename))
+ if err != nil {
+ return nil, err
+ }
+
+ return source.ComputeOneImportFixEdits(c.snapshot, pgf, &imports.ImportFix{
+ StmtInfo: imports.ImportInfo{
+ ImportPath: imp.importPath,
+ Name: imp.name,
+ },
+ // IdentName is unused on this path and is difficult to get.
+ FixType: imports.AddImport,
+ })
+}
+
+func (c *completer) formatBuiltin(ctx context.Context, cand candidate) (CompletionItem, error) {
+ obj := cand.obj
+ item := CompletionItem{
+ Label: obj.Name(),
+ InsertText: obj.Name(),
+ Score: cand.score,
+ }
+ switch obj.(type) {
+ case *types.Const:
+ item.Kind = protocol.ConstantCompletion
+ case *types.Builtin:
+ item.Kind = protocol.FunctionCompletion
+ sig, err := source.NewBuiltinSignature(ctx, c.snapshot, obj.Name())
+ if err != nil {
+ return CompletionItem{}, err
+ }
+ item.Detail = "func" + sig.Format()
+ item.snippet = &snippet.Builder{}
+ c.functionCallSnippet(obj.Name(), sig.TypeParams(), sig.Params(), item.snippet)
+ case *types.TypeName:
+ if types.IsInterface(obj.Type()) {
+ item.Kind = protocol.InterfaceCompletion
+ } else {
+ item.Kind = protocol.ClassCompletion
+ }
+ case *types.Nil:
+ item.Kind = protocol.VariableCompletion
+ }
+ return item, nil
+}
+
+// decide if the type params (if any) should be part of the completion
+// which only possible for types.Named and types.Signature
+// (so far, only in receivers, e.g.; func (s *GENERIC[K, V])..., which is a types.Named)
+func (c *completer) wantTypeParams() bool {
+ // Need to be lexically in a receiver, and a child of an IndexListExpr
+ // (but IndexListExpr only exists with go1.18)
+ start := c.path[0].Pos()
+ for i, nd := range c.path {
+ if fd, ok := nd.(*ast.FuncDecl); ok {
+ if i > 0 && fd.Recv != nil && start < fd.Recv.End() {
+ return true
+ } else {
+ return false
+ }
+ }
+ }
+ return false
+}
diff --git a/gopls/internal/lsp/source/completion/fuzz.go b/gopls/internal/lsp/source/completion/fuzz.go
new file mode 100644
index 000000000..08e7654c7
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/fuzz.go
@@ -0,0 +1,142 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "strings"
+
+ "golang.org/x/tools/gopls/internal/lsp/protocol"
+)
+
+// golang/go#51089
+// *testing.F deserves special treatment as member use is constrained:
+// The arguments to f.Fuzz are determined by the arguments to a previous f.Add
+// Inside f.Fuzz only f.Failed and f.Name are allowed.
+// PJW: are there other packages where we can deduce usage constraints?
+
+// if we find fuzz completions, then return true, as those are the only completions to offer
+func (c *completer) fuzz(typ types.Type, mset *types.MethodSet, imp *importInfo, cb func(candidate), fset *token.FileSet) bool {
+ // 1. inside f.Fuzz? (only f.Failed and f.Name)
+ // 2. possible completing f.Fuzz?
+ // [Ident,SelectorExpr,Callexpr,ExprStmt,BlockiStmt,FuncDecl(Fuzz...)]
+ // 3. before f.Fuzz, same (for 2., offer choice when looking at an F)
+
+ // does the path contain FuncLit as arg to f.Fuzz CallExpr?
+ inside := false
+Loop:
+ for i, n := range c.path {
+ switch v := n.(type) {
+ case *ast.CallExpr:
+ if len(v.Args) != 1 {
+ continue Loop
+ }
+ if _, ok := v.Args[0].(*ast.FuncLit); !ok {
+ continue
+ }
+ if s, ok := v.Fun.(*ast.SelectorExpr); !ok || s.Sel.Name != "Fuzz" {
+ continue
+ }
+ if i > 2 { // avoid t.Fuzz itself in tests
+ inside = true
+ break Loop
+ }
+ }
+ }
+ if inside {
+ for i := 0; i < mset.Len(); i++ {
+ o := mset.At(i).Obj()
+ if o.Name() == "Failed" || o.Name() == "Name" {
+ cb(candidate{
+ obj: o,
+ score: stdScore,
+ imp: imp,
+ addressable: true,
+ })
+ }
+ }
+ return true
+ }
+ // if it could be t.Fuzz, look for the preceding t.Add
+ id, ok := c.path[0].(*ast.Ident)
+ if ok && strings.HasPrefix("Fuzz", id.Name) {
+ var add *ast.CallExpr
+ f := func(n ast.Node) bool {
+ if n == nil {
+ return true
+ }
+ call, ok := n.(*ast.CallExpr)
+ if !ok {
+ return true
+ }
+ s, ok := call.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return true
+ }
+ if s.Sel.Name != "Add" {
+ return true
+ }
+ // Sel.X should be of type *testing.F
+ got := c.pkg.GetTypesInfo().Types[s.X]
+ if got.Type.String() == "*testing.F" {
+ add = call
+ }
+ return false // because we're done...
+ }
+ // look at the enclosing FuzzFoo functions
+ if len(c.path) < 2 {
+ return false
+ }
+ n := c.path[len(c.path)-2]
+ if _, ok := n.(*ast.FuncDecl); !ok {
+ // the path should start with ast.File, ast.FuncDecl, ...
+ // but it didn't, so give up
+ return false
+ }
+ ast.Inspect(n, f)
+ if add == nil {
+ // looks like f.Fuzz without a preceding f.Add.
+ // let the regular completion handle it.
+ return false
+ }
+
+ lbl := "Fuzz(func(t *testing.T"
+ for i, a := range add.Args {
+ info := c.pkg.GetTypesInfo().TypeOf(a)
+ if info == nil {
+ return false // How could this happen, but better safe than panic.
+ }
+ lbl += fmt.Sprintf(", %c %s", 'a'+i, info)
+ }
+ lbl += ")"
+ xx := CompletionItem{
+ Label: lbl,
+ InsertText: lbl,
+ Kind: protocol.FunctionCompletion,
+ Depth: 0,
+ Score: 10, // pretty confident the user should see this
+ Documentation: "argument types from f.Add",
+ isSlice: false,
+ }
+ c.items = append(c.items, xx)
+ for i := 0; i < mset.Len(); i++ {
+ o := mset.At(i).Obj()
+ if o.Name() != "Fuzz" {
+ cb(candidate{
+ obj: o,
+ score: stdScore,
+ imp: imp,
+ addressable: true,
+ })
+ }
+ }
+ return true // done
+ }
+ // let the standard processing take care of it instead
+ return false
+}
diff --git a/gopls/internal/lsp/source/completion/keywords.go b/gopls/internal/lsp/source/completion/keywords.go
new file mode 100644
index 000000000..a068ca2d5
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/keywords.go
@@ -0,0 +1,154 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "go/ast"
+
+ "golang.org/x/tools/gopls/internal/lsp/protocol"
+ "golang.org/x/tools/gopls/internal/lsp/source"
+)
+
+const (
+ BREAK = "break"
+ CASE = "case"
+ CHAN = "chan"
+ CONST = "const"
+ CONTINUE = "continue"
+ DEFAULT = "default"
+ DEFER = "defer"
+ ELSE = "else"
+ FALLTHROUGH = "fallthrough"
+ FOR = "for"
+ FUNC = "func"
+ GO = "go"
+ GOTO = "goto"
+ IF = "if"
+ IMPORT = "import"
+ INTERFACE = "interface"
+ MAP = "map"
+ PACKAGE = "package"
+ RANGE = "range"
+ RETURN = "return"
+ SELECT = "select"
+ STRUCT = "struct"
+ SWITCH = "switch"
+ TYPE = "type"
+ VAR = "var"
+)
+
+// addKeywordCompletions offers keyword candidates appropriate at the position.
+func (c *completer) addKeywordCompletions() {
+ seen := make(map[string]bool)
+
+ if c.wantTypeName() && c.inference.objType == nil {
+ // If we want a type name but don't have an expected obj type,
+ // include "interface", "struct", "func", "chan", and "map".
+
+ // "interface" and "struct" are more common declaring named types.
+ // Give them a higher score if we are in a type declaration.
+ structIntf, funcChanMap := stdScore, highScore
+ if len(c.path) > 1 {
+ if _, namedDecl := c.path[1].(*ast.TypeSpec); namedDecl {
+ structIntf, funcChanMap = highScore, stdScore
+ }
+ }
+
+ c.addKeywordItems(seen, structIntf, STRUCT, INTERFACE)
+ c.addKeywordItems(seen, funcChanMap, FUNC, CHAN, MAP)
+ }
+
+ // If we are at the file scope, only offer decl keywords. We don't
+ // get *ast.Idents at the file scope because non-keyword identifiers
+ // turn into *ast.BadDecl, not *ast.Ident.
+ if len(c.path) == 1 || isASTFile(c.path[1]) {
+ c.addKeywordItems(seen, stdScore, TYPE, CONST, VAR, FUNC, IMPORT)
+ return
+ } else if _, ok := c.path[0].(*ast.Ident); !ok {
+ // Otherwise only offer keywords if the client is completing an identifier.
+ return
+ }
+
+ if len(c.path) > 2 {
+ // Offer "range" if we are in ast.ForStmt.Init. This is what the
+ // AST looks like before "range" is typed, e.g. "for i := r<>".
+ if loop, ok := c.path[2].(*ast.ForStmt); ok && source.NodeContains(loop.Init, c.pos) {
+ c.addKeywordItems(seen, stdScore, RANGE)
+ }
+ }
+
+ // Only suggest keywords if we are beginning a statement.
+ switch n := c.path[1].(type) {
+ case *ast.BlockStmt, *ast.ExprStmt:
+ // OK - our ident must be at beginning of statement.
+ case *ast.CommClause:
+ // Make sure we aren't in the Comm statement.
+ if !n.Colon.IsValid() || c.pos <= n.Colon {
+ return
+ }
+ case *ast.CaseClause:
+ // Make sure we aren't in the case List.
+ if !n.Colon.IsValid() || c.pos <= n.Colon {
+ return
+ }
+ default:
+ return
+ }
+
+ // Filter out keywords depending on scope
+ // Skip the first one because we want to look at the enclosing scopes
+ path := c.path[1:]
+ for i, n := range path {
+ switch node := n.(type) {
+ case *ast.CaseClause:
+ // only recommend "fallthrough" and "break" within the bodies of a case clause
+ if c.pos > node.Colon {
+ c.addKeywordItems(seen, stdScore, BREAK)
+ // "fallthrough" is only valid in switch statements.
+ // A case clause is always nested within a block statement in a switch statement,
+ // that block statement is nested within either a TypeSwitchStmt or a SwitchStmt.
+ if i+2 >= len(path) {
+ continue
+ }
+ if _, ok := path[i+2].(*ast.SwitchStmt); ok {
+ c.addKeywordItems(seen, stdScore, FALLTHROUGH)
+ }
+ }
+ case *ast.CommClause:
+ if c.pos > node.Colon {
+ c.addKeywordItems(seen, stdScore, BREAK)
+ }
+ case *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.SwitchStmt:
+ c.addKeywordItems(seen, stdScore, CASE, DEFAULT)
+ case *ast.ForStmt, *ast.RangeStmt:
+ c.addKeywordItems(seen, stdScore, BREAK, CONTINUE)
+ // This is a bit weak, functions allow for many keywords
+ case *ast.FuncDecl:
+ if node.Body != nil && c.pos > node.Body.Lbrace {
+ c.addKeywordItems(seen, stdScore, DEFER, RETURN, FOR, GO, SWITCH, SELECT, IF, ELSE, VAR, CONST, GOTO, TYPE)
+ }
+ }
+ }
+}
+
+// addKeywordItems dedupes and adds completion items for the specified
+// keywords with the specified score.
+func (c *completer) addKeywordItems(seen map[string]bool, score float64, kws ...string) {
+ for _, kw := range kws {
+ if seen[kw] {
+ continue
+ }
+ seen[kw] = true
+
+ if matchScore := c.matcher.Score(kw); matchScore > 0 {
+ c.items = append(c.items, CompletionItem{
+ Label: kw,
+ Kind: protocol.KeywordCompletion,
+ InsertText: kw,
+ Score: score * float64(matchScore),
+ })
+ }
+ }
+}
diff --git a/gopls/internal/lsp/source/completion/labels.go b/gopls/internal/lsp/source/completion/labels.go
new file mode 100644
index 000000000..e4fd961e3
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/labels.go
@@ -0,0 +1,112 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "go/ast"
+ "go/token"
+ "math"
+)
+
+type labelType int
+
+const (
+ labelNone labelType = iota
+ labelBreak
+ labelContinue
+ labelGoto
+)
+
+// wantLabelCompletion returns true if we want (only) label
+// completions at the position.
+func (c *completer) wantLabelCompletion() labelType {
+ if _, ok := c.path[0].(*ast.Ident); ok && len(c.path) > 1 {
+ // We want a label if we are an *ast.Ident child of a statement
+ // that accepts a label, e.g. "break Lo<>".
+ return takesLabel(c.path[1])
+ }
+
+ return labelNone
+}
+
+// takesLabel returns the corresponding labelType if n is a statement
+// that accepts a label, otherwise labelNone.
+func takesLabel(n ast.Node) labelType {
+ if bs, ok := n.(*ast.BranchStmt); ok {
+ switch bs.Tok {
+ case token.BREAK:
+ return labelBreak
+ case token.CONTINUE:
+ return labelContinue
+ case token.GOTO:
+ return labelGoto
+ }
+ }
+ return labelNone
+}
+
+// labels adds completion items for labels defined in the enclosing
+// function.
+func (c *completer) labels(lt labelType) {
+ if c.enclosingFunc == nil {
+ return
+ }
+
+ addLabel := func(score float64, l *ast.LabeledStmt) {
+ labelObj := c.pkg.GetTypesInfo().ObjectOf(l.Label)
+ if labelObj != nil {
+ c.deepState.enqueue(candidate{obj: labelObj, score: score})
+ }
+ }
+
+ switch lt {
+ case labelBreak, labelContinue:
+ // "break" and "continue" only accept labels from enclosing statements.
+
+ for i, p := range c.path {
+ switch p := p.(type) {
+ case *ast.FuncLit:
+ // Labels are function scoped, so don't continue out of functions.
+ return
+ case *ast.LabeledStmt:
+ switch p.Stmt.(type) {
+ case *ast.ForStmt, *ast.RangeStmt:
+ // Loop labels can be used for "break" or "continue".
+ addLabel(highScore*math.Pow(.99, float64(i)), p)
+ case *ast.SwitchStmt, *ast.SelectStmt, *ast.TypeSwitchStmt:
+ // Switch and select labels can be used only for "break".
+ if lt == labelBreak {
+ addLabel(highScore*math.Pow(.99, float64(i)), p)
+ }
+ }
+ }
+ }
+ case labelGoto:
+ // Goto accepts any label in the same function not in a nested
+ // block. It also doesn't take labels that would jump across
+ // variable definitions, but ignore that case for now.
+ ast.Inspect(c.enclosingFunc.body, func(n ast.Node) bool {
+ if n == nil {
+ return false
+ }
+
+ switch n := n.(type) {
+ // Only search into block-like nodes enclosing our "goto".
+ // This prevents us from finding labels in nested blocks.
+ case *ast.BlockStmt, *ast.CommClause, *ast.CaseClause:
+ for _, p := range c.path {
+ if n == p {
+ return true
+ }
+ }
+ return false
+ case *ast.LabeledStmt:
+ addLabel(highScore, n)
+ }
+
+ return true
+ })
+ }
+}
diff --git a/gopls/internal/lsp/source/completion/literal.go b/gopls/internal/lsp/source/completion/literal.go
new file mode 100644
index 000000000..06ed559be
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/literal.go
@@ -0,0 +1,592 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "context"
+ "fmt"
+ "go/types"
+ "strings"
+ "unicode"
+
+ "golang.org/x/tools/gopls/internal/lsp/protocol"
+ "golang.org/x/tools/gopls/internal/lsp/snippet"
+ "golang.org/x/tools/gopls/internal/lsp/source"
+ "golang.org/x/tools/internal/event"
+ "golang.org/x/tools/internal/typeparams"
+)
+
+// literal generates composite literal, function literal, and make()
+// completion items.
+func (c *completer) literal(ctx context.Context, literalType types.Type, imp *importInfo) {
+ if !c.opts.literal {
+ return
+ }
+
+ expType := c.inference.objType
+
+ if c.inference.matchesVariadic(literalType) {
+ // Don't offer literal slice candidates for variadic arguments.
+ // For example, don't offer "[]interface{}{}" in "fmt.Print(<>)".
+ return
+ }
+
+ // Avoid literal candidates if the expected type is an empty
+ // interface. It isn't very useful to suggest a literal candidate of
+ // every possible type.
+ if expType != nil && isEmptyInterface(expType) {
+ return
+ }
+
+ // We handle unnamed literal completions explicitly before searching
+ // for candidates. Avoid named-type literal completions for
+ // unnamed-type expected type since that results in duplicate
+ // candidates. For example, in
+ //
+ // type mySlice []int
+ // var []int = <>
+ //
+ // don't offer "mySlice{}" since we have already added a candidate
+ // of "[]int{}".
+ if _, named := literalType.(*types.Named); named && expType != nil {
+ if _, named := source.Deref(expType).(*types.Named); !named {
+ return
+ }
+ }
+
+ // Check if an object of type literalType would match our expected type.
+ cand := candidate{
+ obj: c.fakeObj(literalType),
+ }
+
+ switch literalType.Underlying().(type) {
+ // These literal types are addressable (e.g. "&[]int{}"), others are
+ // not (e.g. can't do "&(func(){})").
+ case *types.Struct, *types.Array, *types.Slice, *types.Map:
+ cand.addressable = true
+ }
+
+ if !c.matchingCandidate(&cand) || cand.convertTo != nil {
+ return
+ }
+
+ var (
+ qf = c.qf
+ sel = enclosingSelector(c.path, c.pos)
+ )
+
+ // Don't qualify the type name if we are in a selector expression
+ // since the package name is already present.
+ if sel != nil {
+ qf = func(_ *types.Package) string { return "" }
+ }
+
+ snip, typeName := c.typeNameSnippet(literalType, qf)
+
+ // A type name of "[]int" doesn't work very will with the matcher
+ // since "[" isn't a valid identifier prefix. Here we strip off the
+ // slice (and array) prefix yielding just "int".
+ matchName := typeName
+ switch t := literalType.(type) {
+ case *types.Slice:
+ matchName = types.TypeString(t.Elem(), qf)
+ case *types.Array:
+ matchName = types.TypeString(t.Elem(), qf)
+ }
+
+ addlEdits, err := c.importEdits(imp)
+ if err != nil {
+ event.Error(ctx, "error adding import for literal candidate", err)
+ return
+ }
+
+ // If prefix matches the type name, client may want a composite literal.
+ if score := c.matcher.Score(matchName); score > 0 {
+ if cand.hasMod(reference) {
+ if sel != nil {
+ // If we are in a selector we must place the "&" before the selector.
+ // For example, "foo.B<>" must complete to "&foo.Bar{}", not
+ // "foo.&Bar{}".
+ edits, err := c.editText(sel.Pos(), sel.Pos(), "&")
+ if err != nil {
+ event.Error(ctx, "error making edit for literal pointer completion", err)
+ return
+ }
+ addlEdits = append(addlEdits, edits...)
+ } else {
+ // Otherwise we can stick the "&" directly before the type name.
+ typeName = "&" + typeName
+ snip.PrependText("&")
+ }
+ }
+
+ switch t := literalType.Underlying().(type) {
+ case *types.Struct, *types.Array, *types.Slice, *types.Map:
+ c.compositeLiteral(t, snip.Clone(), typeName, float64(score), addlEdits)
+ case *types.Signature:
+ // Add a literal completion for a signature type that implements
+ // an interface. For example, offer "http.HandlerFunc()" when
+ // expected type is "http.Handler".
+ if expType != nil && types.IsInterface(expType) {
+ c.basicLiteral(t, snip.Clone(), typeName, float64(score), addlEdits)
+ }
+ case *types.Basic:
+ // Add a literal completion for basic types that implement our
+ // expected interface (e.g. named string type http.Dir
+ // implements http.FileSystem), or are identical to our expected
+ // type (i.e. yielding a type conversion such as "float64()").
+ if expType != nil && (types.IsInterface(expType) || types.Identical(expType, literalType)) {
+ c.basicLiteral(t, snip.Clone(), typeName, float64(score), addlEdits)
+ }
+ }
+ }
+
+ // If prefix matches "make", client may want a "make()"
+ // invocation. We also include the type name to allow for more
+ // flexible fuzzy matching.
+ if score := c.matcher.Score("make." + matchName); !cand.hasMod(reference) && score > 0 {
+ switch literalType.Underlying().(type) {
+ case *types.Slice:
+ // The second argument to "make()" for slices is required, so default to "0".
+ c.makeCall(snip.Clone(), typeName, "0", float64(score), addlEdits)
+ case *types.Map, *types.Chan:
+ // Maps and channels don't require the second argument, so omit
+ // to keep things simple for now.
+ c.makeCall(snip.Clone(), typeName, "", float64(score), addlEdits)
+ }
+ }
+
+ // If prefix matches "func", client may want a function literal.
+ if score := c.matcher.Score("func"); !cand.hasMod(reference) && score > 0 && (expType == nil || !types.IsInterface(expType)) {
+ switch t := literalType.Underlying().(type) {
+ case *types.Signature:
+ c.functionLiteral(ctx, t, float64(score))
+ }
+ }
+}
+
+// literalCandidateScore is the base score for literal candidates.
+// Literal candidates match the expected type so they should be high
+// scoring, but we want them ranked below lexical objects of the
+// correct type, so scale down highScore.
+const literalCandidateScore = highScore / 2
+
+// functionLiteral adds a function literal completion item for the
+// given signature.
+func (c *completer) functionLiteral(ctx context.Context, sig *types.Signature, matchScore float64) {
+ snip := &snippet.Builder{}
+ snip.WriteText("func(")
+
+ // First we generate names for each param and keep a seen count so
+ // we know if we need to uniquify param names. For example,
+ // "func(int)" will become "func(i int)", but "func(int, int64)"
+ // will become "func(i1 int, i2 int64)".
+ var (
+ paramNames = make([]string, sig.Params().Len())
+ paramNameCount = make(map[string]int)
+ hasTypeParams bool
+ )
+ for i := 0; i < sig.Params().Len(); i++ {
+ var (
+ p = sig.Params().At(i)
+ name = p.Name()
+ )
+
+ if tp, _ := p.Type().(*typeparams.TypeParam); tp != nil && !c.typeParamInScope(tp) {
+ hasTypeParams = true
+ }
+
+ if name == "" {
+ // If the param has no name in the signature, guess a name based
+ // on the type. Use an empty qualifier to ignore the package.
+ // For example, we want to name "http.Request" "r", not "hr".
+ typeName, err := source.FormatVarType(ctx, c.snapshot, c.pkg, p,
+ func(p *types.Package) string { return "" },
+ func(source.PackageName, source.ImportPath, source.PackagePath) string { return "" })
+ if err != nil {
+ // In general, the only error we should encounter while formatting is
+ // context cancellation.
+ if ctx.Err() == nil {
+ event.Error(ctx, "formatting var type", err)
+ }
+ return
+ }
+ name = abbreviateTypeName(typeName)
+ }
+ paramNames[i] = name
+ if name != "_" {
+ paramNameCount[name]++
+ }
+ }
+
+ for n, c := range paramNameCount {
+ // Any names we saw more than once will need a unique suffix added
+ // on. Reset the count to 1 to act as the suffix for the first
+ // name.
+ if c >= 2 {
+ paramNameCount[n] = 1
+ } else {
+ delete(paramNameCount, n)
+ }
+ }
+
+ for i := 0; i < sig.Params().Len(); i++ {
+ if hasTypeParams && !c.opts.placeholders {
+ // If there are type params in the args then the user must
+ // choose the concrete types. If placeholders are disabled just
+ // drop them between the parens and let them fill things in.
+ snip.WritePlaceholder(nil)
+ break
+ }
+
+ if i > 0 {
+ snip.WriteText(", ")
+ }
+
+ var (
+ p = sig.Params().At(i)
+ name = paramNames[i]
+ )
+
+ // Uniquify names by adding on an incrementing numeric suffix.
+ if idx, found := paramNameCount[name]; found {
+ paramNameCount[name]++
+ name = fmt.Sprintf("%s%d", name, idx)
+ }
+
+ if name != p.Name() && c.opts.placeholders {
+ // If we didn't use the signature's param name verbatim then we
+ // may have chosen a poor name. Give the user a placeholder so
+ // they can easily fix the name.
+ snip.WritePlaceholder(func(b *snippet.Builder) {
+ b.WriteText(name)
+ })
+ } else {
+ snip.WriteText(name)
+ }
+
+ // If the following param's type is identical to this one, omit
+ // this param's type string. For example, emit "i, j int" instead
+ // of "i int, j int".
+ if i == sig.Params().Len()-1 || !types.Identical(p.Type(), sig.Params().At(i+1).Type()) {
+ snip.WriteText(" ")
+ typeStr, err := source.FormatVarType(ctx, c.snapshot, c.pkg, p, c.qf, c.mq)
+ if err != nil {
+ // In general, the only error we should encounter while formatting is
+ // context cancellation.
+ if ctx.Err() == nil {
+ event.Error(ctx, "formatting var type", err)
+ }
+ return
+ }
+ if sig.Variadic() && i == sig.Params().Len()-1 {
+ typeStr = strings.Replace(typeStr, "[]", "...", 1)
+ }
+
+ if tp, _ := p.Type().(*typeparams.TypeParam); tp != nil && !c.typeParamInScope(tp) {
+ snip.WritePlaceholder(func(snip *snippet.Builder) {
+ snip.WriteText(typeStr)
+ })
+ } else {
+ snip.WriteText(typeStr)
+ }
+ }
+ }
+ snip.WriteText(")")
+
+ results := sig.Results()
+ if results.Len() > 0 {
+ snip.WriteText(" ")
+ }
+
+ resultsNeedParens := results.Len() > 1 ||
+ results.Len() == 1 && results.At(0).Name() != ""
+
+ var resultHasTypeParams bool
+ for i := 0; i < results.Len(); i++ {
+ if tp, _ := results.At(i).Type().(*typeparams.TypeParam); tp != nil && !c.typeParamInScope(tp) {
+ resultHasTypeParams = true
+ }
+ }
+
+ if resultsNeedParens {
+ snip.WriteText("(")
+ }
+ for i := 0; i < results.Len(); i++ {
+ if resultHasTypeParams && !c.opts.placeholders {
+ // Leave an empty tabstop if placeholders are disabled and there
+ // are type args that need specificying.
+ snip.WritePlaceholder(nil)
+ break
+ }
+
+ if i > 0 {
+ snip.WriteText(", ")
+ }
+ r := results.At(i)
+ if name := r.Name(); name != "" {
+ snip.WriteText(name + " ")
+ }
+
+ text, err := source.FormatVarType(ctx, c.snapshot, c.pkg, r, c.qf, c.mq)
+ if err != nil {
+ // In general, the only error we should encounter while formatting is
+ // context cancellation.
+ if ctx.Err() == nil {
+ event.Error(ctx, "formatting var type", err)
+ }
+ return
+ }
+ if tp, _ := r.Type().(*typeparams.TypeParam); tp != nil && !c.typeParamInScope(tp) {
+ snip.WritePlaceholder(func(snip *snippet.Builder) {
+ snip.WriteText(text)
+ })
+ } else {
+ snip.WriteText(text)
+ }
+ }
+ if resultsNeedParens {
+ snip.WriteText(")")
+ }
+
+ snip.WriteText(" {")
+ snip.WriteFinalTabstop()
+ snip.WriteText("}")
+
+ c.items = append(c.items, CompletionItem{
+ Label: "func(...) {}",
+ Score: matchScore * literalCandidateScore,
+ Kind: protocol.VariableCompletion,
+ snippet: snip,
+ })
+}
+
+// conventionalAcronyms contains conventional acronyms for type names
+// in lower case. For example, "ctx" for "context" and "err" for "error".
+var conventionalAcronyms = map[string]string{
+ "context": "ctx",
+ "error": "err",
+ "tx": "tx",
+ "responsewriter": "w",
+}
+
+// abbreviateTypeName abbreviates type names into acronyms. For
+// example, "fooBar" is abbreviated "fb". Care is taken to ignore
+// non-identifier runes. For example, "[]int" becomes "i", and
+// "struct { i int }" becomes "s".
+func abbreviateTypeName(s string) string {
+ var (
+ b strings.Builder
+ useNextUpper bool
+ )
+
+ // Trim off leading non-letters. We trim everything between "[" and
+ // "]" to handle array types like "[someConst]int".
+ var inBracket bool
+ s = strings.TrimFunc(s, func(r rune) bool {
+ if inBracket {
+ inBracket = r != ']'
+ return true
+ }
+
+ if r == '[' {
+ inBracket = true
+ }
+
+ return !unicode.IsLetter(r)
+ })
+
+ if acr, ok := conventionalAcronyms[strings.ToLower(s)]; ok {
+ return acr
+ }
+
+ for i, r := range s {
+ // Stop if we encounter a non-identifier rune.
+ if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
+ break
+ }
+
+ if i == 0 {
+ b.WriteRune(unicode.ToLower(r))
+ }
+
+ if unicode.IsUpper(r) {
+ if useNextUpper {
+ b.WriteRune(unicode.ToLower(r))
+ useNextUpper = false
+ }
+ } else {
+ useNextUpper = true
+ }
+ }
+
+ return b.String()
+}
+
+// compositeLiteral adds a composite literal completion item for the given typeName.
+func (c *completer) compositeLiteral(T types.Type, snip *snippet.Builder, typeName string, matchScore float64, edits []protocol.TextEdit) {
+ snip.WriteText("{")
+ // Don't put the tab stop inside the composite literal curlies "{}"
+ // for structs that have no accessible fields.
+ if strct, ok := T.(*types.Struct); !ok || fieldsAccessible(strct, c.pkg.GetTypes()) {
+ snip.WriteFinalTabstop()
+ }
+ snip.WriteText("}")
+
+ nonSnippet := typeName + "{}"
+
+ c.items = append(c.items, CompletionItem{
+ Label: nonSnippet,
+ InsertText: nonSnippet,
+ Score: matchScore * literalCandidateScore,
+ Kind: protocol.VariableCompletion,
+ AdditionalTextEdits: edits,
+ snippet: snip,
+ })
+}
+
+// basicLiteral adds a literal completion item for the given basic
+// type name typeName.
+func (c *completer) basicLiteral(T types.Type, snip *snippet.Builder, typeName string, matchScore float64, edits []protocol.TextEdit) {
+ // Never give type conversions like "untyped int()".
+ if isUntyped(T) {
+ return
+ }
+
+ snip.WriteText("(")
+ snip.WriteFinalTabstop()
+ snip.WriteText(")")
+
+ nonSnippet := typeName + "()"
+
+ c.items = append(c.items, CompletionItem{
+ Label: nonSnippet,
+ InsertText: nonSnippet,
+ Detail: T.String(),
+ Score: matchScore * literalCandidateScore,
+ Kind: protocol.VariableCompletion,
+ AdditionalTextEdits: edits,
+ snippet: snip,
+ })
+}
+
+// makeCall adds a completion item for a "make()" call given a specific type.
+func (c *completer) makeCall(snip *snippet.Builder, typeName string, secondArg string, matchScore float64, edits []protocol.TextEdit) {
+ // Keep it simple and don't add any placeholders for optional "make()" arguments.
+
+ snip.PrependText("make(")
+ if secondArg != "" {
+ snip.WriteText(", ")
+ snip.WritePlaceholder(func(b *snippet.Builder) {
+ if c.opts.placeholders {
+ b.WriteText(secondArg)
+ }
+ })
+ }
+ snip.WriteText(")")
+
+ var nonSnippet strings.Builder
+ nonSnippet.WriteString("make(" + typeName)
+ if secondArg != "" {
+ nonSnippet.WriteString(", ")
+ nonSnippet.WriteString(secondArg)
+ }
+ nonSnippet.WriteByte(')')
+
+ c.items = append(c.items, CompletionItem{
+ Label: nonSnippet.String(),
+ InsertText: nonSnippet.String(),
+ Score: matchScore * literalCandidateScore,
+ Kind: protocol.FunctionCompletion,
+ AdditionalTextEdits: edits,
+ snippet: snip,
+ })
+}
+
+// Create a snippet for a type name where type params become placeholders.
+func (c *completer) typeNameSnippet(literalType types.Type, qf types.Qualifier) (*snippet.Builder, string) {
+ var (
+ snip snippet.Builder
+ typeName string
+ named, _ = literalType.(*types.Named)
+ )
+
+ if named != nil && named.Obj() != nil && typeparams.ForNamed(named).Len() > 0 && !c.fullyInstantiated(named) {
+ // We are not "fully instantiated" meaning we have type params that must be specified.
+ if pkg := qf(named.Obj().Pkg()); pkg != "" {
+ typeName = pkg + "."
+ }
+
+ // We do this to get "someType" instead of "someType[T]".
+ typeName += named.Obj().Name()
+ snip.WriteText(typeName + "[")
+
+ if c.opts.placeholders {
+ for i := 0; i < typeparams.ForNamed(named).Len(); i++ {
+ if i > 0 {
+ snip.WriteText(", ")
+ }
+ snip.WritePlaceholder(func(snip *snippet.Builder) {
+ snip.WriteText(types.TypeString(typeparams.ForNamed(named).At(i), qf))
+ })
+ }
+ } else {
+ snip.WritePlaceholder(nil)
+ }
+ snip.WriteText("]")
+ typeName += "[...]"
+ } else {
+ // We don't have unspecified type params so use default type formatting.
+ typeName = types.TypeString(literalType, qf)
+ snip.WriteText(typeName)
+ }
+
+ return &snip, typeName
+}
+
+// fullyInstantiated reports whether all of t's type params have
+// specified type args.
+func (c *completer) fullyInstantiated(t *types.Named) bool {
+ tps := typeparams.ForNamed(t)
+ tas := typeparams.NamedTypeArgs(t)
+
+ if tps.Len() != tas.Len() {
+ return false
+ }
+
+ for i := 0; i < tas.Len(); i++ {
+ switch ta := tas.At(i).(type) {
+ case *typeparams.TypeParam:
+ // A *TypeParam only counts as specified if it is currently in
+ // scope (i.e. we are in a generic definition).
+ if !c.typeParamInScope(ta) {
+ return false
+ }
+ case *types.Named:
+ if !c.fullyInstantiated(ta) {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+// typeParamInScope returns whether tp's object is in scope at c.pos.
+// This tells you whether you are in a generic definition and can
+// assume tp has been specified.
+func (c *completer) typeParamInScope(tp *typeparams.TypeParam) bool {
+ obj := tp.Obj()
+ if obj == nil {
+ return false
+ }
+
+ scope := c.innermostScope()
+ if scope == nil {
+ return false
+ }
+
+ _, foundObj := scope.LookupParent(obj.Name(), c.pos)
+ return obj == foundObj
+}
diff --git a/gopls/internal/lsp/source/completion/package.go b/gopls/internal/lsp/source/completion/package.go
new file mode 100644
index 000000000..f3bc30688
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/package.go
@@ -0,0 +1,351 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/scanner"
+ "go/token"
+ "go/types"
+ "path/filepath"
+ "strings"
+ "unicode"
+
+ "golang.org/x/tools/gopls/internal/lsp/protocol"
+ "golang.org/x/tools/gopls/internal/lsp/safetoken"
+ "golang.org/x/tools/gopls/internal/lsp/source"
+ "golang.org/x/tools/gopls/internal/span"
+ "golang.org/x/tools/internal/fuzzy"
+)
+
+// packageClauseCompletions offers completions for a package declaration when
+// one is not present in the given file.
+func packageClauseCompletions(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, position protocol.Position) ([]CompletionItem, *Selection, error) {
+ // We know that the AST for this file will be empty due to the missing
+ // package declaration, but parse it anyway to get a mapper.
+ // TODO(adonovan): opt: there's no need to parse just to get a mapper.
+ pgf, err := snapshot.ParseGo(ctx, fh, source.ParseFull)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ offset, err := pgf.Mapper.PositionOffset(position)
+ if err != nil {
+ return nil, nil, err
+ }
+ surrounding, err := packageCompletionSurrounding(pgf, offset)
+ if err != nil {
+ return nil, nil, fmt.Errorf("invalid position for package completion: %w", err)
+ }
+
+ packageSuggestions, err := packageSuggestions(ctx, snapshot, fh.URI(), "")
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var items []CompletionItem
+ for _, pkg := range packageSuggestions {
+ insertText := fmt.Sprintf("package %s", pkg.name)
+ items = append(items, CompletionItem{
+ Label: insertText,
+ Kind: protocol.ModuleCompletion,
+ InsertText: insertText,
+ Score: pkg.score,
+ })
+ }
+
+ return items, surrounding, nil
+}
+
+// packageCompletionSurrounding returns surrounding for package completion if a
+// package completions can be suggested at a given cursor offset. A valid location
+// for package completion is above any declarations or import statements.
+func packageCompletionSurrounding(pgf *source.ParsedGoFile, offset int) (*Selection, error) {
+ m := pgf.Mapper
+ // If the file lacks a package declaration, the parser will return an empty
+ // AST. As a work-around, try to parse an expression from the file contents.
+ fset := token.NewFileSet()
+ expr, _ := parser.ParseExprFrom(fset, m.URI.Filename(), pgf.Src, parser.Mode(0))
+ if expr == nil {
+ return nil, fmt.Errorf("unparseable file (%s)", m.URI)
+ }
+ tok := fset.File(expr.Pos())
+ cursor := tok.Pos(offset)
+
+ // If we were able to parse out an identifier as the first expression from
+ // the file, it may be the beginning of a package declaration ("pack ").
+ // We can offer package completions if the cursor is in the identifier.
+ if name, ok := expr.(*ast.Ident); ok {
+ if cursor >= name.Pos() && cursor <= name.End() {
+ if !strings.HasPrefix(PACKAGE, name.Name) {
+ return nil, fmt.Errorf("cursor in non-matching ident")
+ }
+ return &Selection{
+ content: name.Name,
+ cursor: cursor,
+ tokFile: tok,
+ start: name.Pos(),
+ end: name.End(),
+ mapper: m,
+ }, nil
+ }
+ }
+
+ // The file is invalid, but it contains an expression that we were able to
+ // parse. We will use this expression to construct the cursor's
+ // "surrounding".
+
+ // First, consider the possibility that we have a valid "package" keyword
+ // with an empty package name ("package "). "package" is parsed as an
+ // *ast.BadDecl since it is a keyword. This logic would allow "package" to
+ // appear on any line of the file as long as it's the first code expression
+ // in the file.
+ lines := strings.Split(string(pgf.Src), "\n")
+ cursorLine := tok.Line(cursor)
+ if cursorLine <= 0 || cursorLine > len(lines) {
+ return nil, fmt.Errorf("invalid line number")
+ }
+ if safetoken.StartPosition(fset, expr.Pos()).Line == cursorLine {
+ words := strings.Fields(lines[cursorLine-1])
+ if len(words) > 0 && words[0] == PACKAGE {
+ content := PACKAGE
+ // Account for spaces if there are any.
+ if len(words) > 1 {
+ content += " "
+ }
+
+ start := expr.Pos()
+ end := token.Pos(int(expr.Pos()) + len(content) + 1)
+ // We have verified that we have a valid 'package' keyword as our
+ // first expression. Ensure that cursor is in this keyword or
+ // otherwise fallback to the general case.
+ if cursor >= start && cursor <= end {
+ return &Selection{
+ content: content,
+ cursor: cursor,
+ tokFile: tok,
+ start: start,
+ end: end,
+ mapper: m,
+ }, nil
+ }
+ }
+ }
+
+ // If the cursor is after the start of the expression, no package
+ // declaration will be valid.
+ if cursor > expr.Pos() {
+ return nil, fmt.Errorf("cursor after expression")
+ }
+
+ // If the cursor is in a comment, don't offer any completions.
+ if cursorInComment(tok, cursor, m.Content) {
+ return nil, fmt.Errorf("cursor in comment")
+ }
+
+ // The surrounding range in this case is the cursor.
+ return &Selection{
+ content: "",
+ tokFile: tok,
+ start: cursor,
+ end: cursor,
+ cursor: cursor,
+ mapper: m,
+ }, nil
+}
+
+func cursorInComment(file *token.File, cursor token.Pos, src []byte) bool {
+ var s scanner.Scanner
+ s.Init(file, src, func(_ token.Position, _ string) {}, scanner.ScanComments)
+ for {
+ pos, tok, lit := s.Scan()
+ if pos <= cursor && cursor <= token.Pos(int(pos)+len(lit)) {
+ return tok == token.COMMENT
+ }
+ if tok == token.EOF {
+ break
+ }
+ }
+ return false
+}
+
+// packageNameCompletions returns name completions for a package clause using
+// the current name as prefix.
+func (c *completer) packageNameCompletions(ctx context.Context, fileURI span.URI, name *ast.Ident) error {
+ cursor := int(c.pos - name.NamePos)
+ if cursor < 0 || cursor > len(name.Name) {
+ return errors.New("cursor is not in package name identifier")
+ }
+
+ c.completionContext.packageCompletion = true
+
+ prefix := name.Name[:cursor]
+ packageSuggestions, err := packageSuggestions(ctx, c.snapshot, fileURI, prefix)
+ if err != nil {
+ return err
+ }
+
+ for _, pkg := range packageSuggestions {
+ c.deepState.enqueue(pkg)
+ }
+ return nil
+}
+
+// packageSuggestions returns a list of packages from workspace packages that
+// have the given prefix and are used in the same directory as the given
+// file. This also includes test packages for these packages (<pkg>_test) and
+// the directory name itself.
+func packageSuggestions(ctx context.Context, snapshot source.Snapshot, fileURI span.URI, prefix string) (packages []candidate, err error) {
+ active, err := snapshot.ActiveMetadata(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ toCandidate := func(name string, score float64) candidate {
+ obj := types.NewPkgName(0, nil, name, types.NewPackage("", name))
+ return candidate{obj: obj, name: name, detail: name, score: score}
+ }
+
+ matcher := fuzzy.NewMatcher(prefix)
+
+ // Always try to suggest a main package
+ defer func() {
+ if score := float64(matcher.Score("main")); score > 0 {
+ packages = append(packages, toCandidate("main", score*lowScore))
+ }
+ }()
+
+ dirPath := filepath.Dir(fileURI.Filename())
+ dirName := filepath.Base(dirPath)
+ if !isValidDirName(dirName) {
+ return packages, nil
+ }
+ pkgName := convertDirNameToPkgName(dirName)
+
+ seenPkgs := make(map[source.PackageName]struct{})
+
+ // The `go` command by default only allows one package per directory but we
+ // support multiple package suggestions since gopls is build system agnostic.
+ for _, m := range active {
+ if m.Name == "main" || m.Name == "" {
+ continue
+ }
+ if _, ok := seenPkgs[m.Name]; ok {
+ continue
+ }
+
+ // Only add packages that are previously used in the current directory.
+ var relevantPkg bool
+ for _, uri := range m.CompiledGoFiles {
+ if filepath.Dir(uri.Filename()) == dirPath {
+ relevantPkg = true
+ break
+ }
+ }
+ if !relevantPkg {
+ continue
+ }
+
+ // Add a found package used in current directory as a high relevance
+ // suggestion and the test package for it as a medium relevance
+ // suggestion.
+ if score := float64(matcher.Score(string(m.Name))); score > 0 {
+ packages = append(packages, toCandidate(string(m.Name), score*highScore))
+ }
+ seenPkgs[m.Name] = struct{}{}
+
+ testPkgName := m.Name + "_test"
+ if _, ok := seenPkgs[testPkgName]; ok || strings.HasSuffix(string(m.Name), "_test") {
+ continue
+ }
+ if score := float64(matcher.Score(string(testPkgName))); score > 0 {
+ packages = append(packages, toCandidate(string(testPkgName), score*stdScore))
+ }
+ seenPkgs[testPkgName] = struct{}{}
+ }
+
+ // Add current directory name as a low relevance suggestion.
+ if _, ok := seenPkgs[pkgName]; !ok {
+ if score := float64(matcher.Score(string(pkgName))); score > 0 {
+ packages = append(packages, toCandidate(string(pkgName), score*lowScore))
+ }
+
+ testPkgName := pkgName + "_test"
+ if score := float64(matcher.Score(string(testPkgName))); score > 0 {
+ packages = append(packages, toCandidate(string(testPkgName), score*lowScore))
+ }
+ }
+
+ return packages, nil
+}
+
+// isValidDirName checks whether the passed directory name can be used in
+// a package path. Requirements for a package path can be found here:
+// https://golang.org/ref/mod#go-mod-file-ident.
+func isValidDirName(dirName string) bool {
+ if dirName == "" {
+ return false
+ }
+
+ for i, ch := range dirName {
+ if isLetter(ch) || isDigit(ch) {
+ continue
+ }
+ if i == 0 {
+ // Directory name can start only with '_'. '.' is not allowed in module paths.
+ // '-' and '~' are not allowed because elements of package paths must be
+ // safe command-line arguments.
+ if ch == '_' {
+ continue
+ }
+ } else {
+ // Modules path elements can't end with '.'
+ if isAllowedPunctuation(ch) && (i != len(dirName)-1 || ch != '.') {
+ continue
+ }
+ }
+
+ return false
+ }
+ return true
+}
+
+// convertDirNameToPkgName converts a valid directory name to a valid package name.
+// It leaves only letters and digits. All letters are mapped to lower case.
+func convertDirNameToPkgName(dirName string) source.PackageName {
+ var buf bytes.Buffer
+ for _, ch := range dirName {
+ switch {
+ case isLetter(ch):
+ buf.WriteRune(unicode.ToLower(ch))
+
+ case buf.Len() != 0 && isDigit(ch):
+ buf.WriteRune(ch)
+ }
+ }
+ return source.PackageName(buf.String())
+}
+
+// isLetter and isDigit allow only ASCII characters because
+// "Each path element is a non-empty string made of up ASCII letters,
+// ASCII digits, and limited ASCII punctuation"
+// (see https://golang.org/ref/mod#go-mod-file-ident).
+
+func isLetter(ch rune) bool {
+ return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z'
+}
+
+func isDigit(ch rune) bool {
+ return '0' <= ch && ch <= '9'
+}
+
+func isAllowedPunctuation(ch rune) bool {
+ return ch == '_' || ch == '-' || ch == '~' || ch == '.'
+}
diff --git a/gopls/internal/lsp/source/completion/package_test.go b/gopls/internal/lsp/source/completion/package_test.go
new file mode 100644
index 000000000..614359fa5
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/package_test.go
@@ -0,0 +1,81 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "testing"
+
+ "golang.org/x/tools/gopls/internal/lsp/source"
+)
+
+func TestIsValidDirName(t *testing.T) {
+ tests := []struct {
+ dirName string
+ valid bool
+ }{
+ {dirName: "", valid: false},
+ //
+ {dirName: "a", valid: true},
+ {dirName: "abcdef", valid: true},
+ {dirName: "AbCdEf", valid: true},
+ //
+ {dirName: "1a35", valid: true},
+ {dirName: "a16", valid: true},
+ //
+ {dirName: "_a", valid: true},
+ {dirName: "a_", valid: true},
+ //
+ {dirName: "~a", valid: false},
+ {dirName: "a~", valid: true},
+ //
+ {dirName: "-a", valid: false},
+ {dirName: "a-", valid: true},
+ //
+ {dirName: ".a", valid: false},
+ {dirName: "a.", valid: false},
+ //
+ {dirName: "a~_b--c.-e", valid: true},
+ {dirName: "~a~_b--c.-e", valid: false},
+ {dirName: "a~_b--c.-e--~", valid: true},
+ {dirName: "a~_b--2134dc42.-e6--~", valid: true},
+ {dirName: "abc`def", valid: false},
+ {dirName: "тест", valid: false},
+ {dirName: "你好", valid: false},
+ }
+ for _, tt := range tests {
+ valid := isValidDirName(tt.dirName)
+ if tt.valid != valid {
+ t.Errorf("%s: expected %v, got %v", tt.dirName, tt.valid, valid)
+ }
+ }
+}
+
+func TestConvertDirNameToPkgName(t *testing.T) {
+ tests := []struct {
+ dirName string
+ pkgName source.PackageName
+ }{
+ {dirName: "a", pkgName: "a"},
+ {dirName: "abcdef", pkgName: "abcdef"},
+ {dirName: "AbCdEf", pkgName: "abcdef"},
+ {dirName: "1a35", pkgName: "a35"},
+ {dirName: "14a35", pkgName: "a35"},
+ {dirName: "a16", pkgName: "a16"},
+ {dirName: "_a", pkgName: "a"},
+ {dirName: "a_", pkgName: "a"},
+ {dirName: "a~", pkgName: "a"},
+ {dirName: "a-", pkgName: "a"},
+ {dirName: "a~_b--c.-e", pkgName: "abce"},
+ {dirName: "a~_b--c.-e--~", pkgName: "abce"},
+ {dirName: "a~_b--2134dc42.-e6--~", pkgName: "ab2134dc42e6"},
+ }
+ for _, tt := range tests {
+ pkgName := convertDirNameToPkgName(tt.dirName)
+ if tt.pkgName != pkgName {
+ t.Errorf("%s: expected %v, got %v", tt.dirName, tt.pkgName, pkgName)
+ continue
+ }
+ }
+}
diff --git a/gopls/internal/lsp/source/completion/postfix_snippets.go b/gopls/internal/lsp/source/completion/postfix_snippets.go
new file mode 100644
index 000000000..0737ec246
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/postfix_snippets.go
@@ -0,0 +1,471 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "context"
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "log"
+ "reflect"
+ "strings"
+ "sync"
+ "text/template"
+
+ "golang.org/x/tools/gopls/internal/lsp/protocol"
+ "golang.org/x/tools/gopls/internal/lsp/snippet"
+ "golang.org/x/tools/gopls/internal/lsp/source"
+ "golang.org/x/tools/internal/event"
+ "golang.org/x/tools/internal/imports"
+)
+
+// Postfix snippets are artificial methods that allow the user to
+// compose common operations in an "argument oriented" fashion. For
+// example, instead of "sort.Slice(someSlice, ...)" a user can expand
+// "someSlice.sort!".
+
+// postfixTmpl represents a postfix snippet completion candidate.
+type postfixTmpl struct {
+ // label is the completion candidate's label presented to the user.
+ label string
+
+ // details is passed along to the client as the candidate's details.
+ details string
+
+ // body is the template text. See postfixTmplArgs for details on the
+ // facilities available to the template.
+ body string
+
+ tmpl *template.Template
+}
+
+// postfixTmplArgs are the template execution arguments available to
+// the postfix snippet templates.
+type postfixTmplArgs struct {
+ // StmtOK is true if it is valid to replace the selector with a
+ // statement. For example:
+ //
+ // func foo() {
+ // bar.sort! // statement okay
+ //
+ // someMethod(bar.sort!) // statement not okay
+ // }
+ StmtOK bool
+
+ // X is the textual SelectorExpr.X. For example, when completing
+ // "foo.bar.print!", "X" is "foo.bar".
+ X string
+
+ // Obj is the types.Object of SelectorExpr.X, if any.
+ Obj types.Object
+
+ // Type is the type of "foo.bar" in "foo.bar.print!".
+ Type types.Type
+
+ scope *types.Scope
+ snip snippet.Builder
+ importIfNeeded func(pkgPath string, scope *types.Scope) (name string, edits []protocol.TextEdit, err error)
+ edits []protocol.TextEdit
+ qf types.Qualifier
+ varNames map[string]bool
+}
+
+var postfixTmpls = []postfixTmpl{{
+ label: "sort",
+ details: "sort.Slice()",
+ body: `{{if and (eq .Kind "slice") .StmtOK -}}
+{{.Import "sort"}}.Slice({{.X}}, func({{.VarName nil "i"}}, {{.VarName nil "j"}} int) bool {
+ {{.Cursor}}
+})
+{{- end}}`,
+}, {
+ label: "last",
+ details: "s[len(s)-1]",
+ body: `{{if and (eq .Kind "slice") .Obj -}}
+{{.X}}[len({{.X}})-1]
+{{- end}}`,
+}, {
+ label: "reverse",
+ details: "reverse slice",
+ body: `{{if and (eq .Kind "slice") .StmtOK -}}
+{{$i := .VarName nil "i"}}{{$j := .VarName nil "j" -}}
+for {{$i}}, {{$j}} := 0, len({{.X}})-1; {{$i}} < {{$j}}; {{$i}}, {{$j}} = {{$i}}+1, {{$j}}-1 {
+ {{.X}}[{{$i}}], {{.X}}[{{$j}}] = {{.X}}[{{$j}}], {{.X}}[{{$i}}]
+}
+{{end}}`,
+}, {
+ label: "range",
+ details: "range over slice",
+ body: `{{if and (eq .Kind "slice") .StmtOK -}}
+for {{.VarName nil "i"}}, {{.VarName .ElemType "v"}} := range {{.X}} {
+ {{.Cursor}}
+}
+{{- end}}`,
+}, {
+ label: "append",
+ details: "append and re-assign slice",
+ body: `{{if and (eq .Kind "slice") .StmtOK .Obj -}}
+{{.X}} = append({{.X}}, {{.Cursor}})
+{{- end}}`,
+}, {
+ label: "append",
+ details: "append to slice",
+ body: `{{if and (eq .Kind "slice") (not .StmtOK) -}}
+append({{.X}}, {{.Cursor}})
+{{- end}}`,
+}, {
+ label: "copy",
+ details: "duplicate slice",
+ body: `{{if and (eq .Kind "slice") .StmtOK .Obj -}}
+{{$v := (.VarName nil (printf "%sCopy" .X))}}{{$v}} := make([]{{.TypeName .ElemType}}, len({{.X}}))
+copy({{$v}}, {{.X}})
+{{end}}`,
+}, {
+ label: "range",
+ details: "range over map",
+ body: `{{if and (eq .Kind "map") .StmtOK -}}
+for {{.VarName .KeyType "k"}}, {{.VarName .ElemType "v"}} := range {{.X}} {
+ {{.Cursor}}
+}
+{{- end}}`,
+}, {
+ label: "clear",
+ details: "clear map contents",
+ body: `{{if and (eq .Kind "map") .StmtOK -}}
+{{$k := (.VarName .KeyType "k")}}for {{$k}} := range {{.X}} {
+ delete({{.X}}, {{$k}})
+}
+{{end}}`,
+}, {
+ label: "keys",
+ details: "create slice of keys",
+ body: `{{if and (eq .Kind "map") .StmtOK -}}
+{{$keysVar := (.VarName nil "keys")}}{{$keysVar}} := make([]{{.TypeName .KeyType}}, 0, len({{.X}}))
+{{$k := (.VarName .KeyType "k")}}for {{$k}} := range {{.X}} {
+ {{$keysVar}} = append({{$keysVar}}, {{$k}})
+}
+{{end}}`,
+}, {
+ label: "range",
+ details: "range over channel",
+ body: `{{if and (eq .Kind "chan") .StmtOK -}}
+for {{.VarName .ElemType "e"}} := range {{.X}} {
+ {{.Cursor}}
+}
+{{- end}}`,
+}, {
+ label: "var",
+ details: "assign to variables",
+ body: `{{if and (eq .Kind "tuple") .StmtOK -}}
+{{$a := .}}{{range $i, $v := .Tuple}}{{if $i}}, {{end}}{{$a.VarName $v.Type $v.Name}}{{end}} := {{.X}}
+{{- end}}`,
+}, {
+ label: "var",
+ details: "assign to variable",
+ body: `{{if and (ne .Kind "tuple") .StmtOK -}}
+{{.VarName .Type ""}} := {{.X}}
+{{- end}}`,
+}, {
+ label: "print",
+ details: "print to stdout",
+ body: `{{if and (ne .Kind "tuple") .StmtOK -}}
+{{.Import "fmt"}}.Printf("{{.EscapeQuotes .X}}: %v\n", {{.X}})
+{{- end}}`,
+}, {
+ label: "print",
+ details: "print to stdout",
+ body: `{{if and (eq .Kind "tuple") .StmtOK -}}
+{{.Import "fmt"}}.Println({{.X}})
+{{- end}}`,
+}, {
+ label: "split",
+ details: "split string",
+ body: `{{if (eq (.TypeName .Type) "string") -}}
+{{.Import "strings"}}.Split({{.X}}, "{{.Cursor}}")
+{{- end}}`,
+}, {
+ label: "join",
+ details: "join string slice",
+ body: `{{if and (eq .Kind "slice") (eq (.TypeName .ElemType) "string") -}}
+{{.Import "strings"}}.Join({{.X}}, "{{.Cursor}}")
+{{- end}}`,
+}}
+
+// Cursor indicates where the client's cursor should end up after the
+// snippet is done.
+func (a *postfixTmplArgs) Cursor() string {
+ a.snip.WriteFinalTabstop()
+ return ""
+}
+
+// Import makes sure the package corresponding to path is imported,
+// returning the identifier to use to refer to the package.
+func (a *postfixTmplArgs) Import(path string) (string, error) {
+ name, edits, err := a.importIfNeeded(path, a.scope)
+ if err != nil {
+ return "", fmt.Errorf("couldn't import %q: %w", path, err)
+ }
+ a.edits = append(a.edits, edits...)
+ return name, nil
+}
+
+func (a *postfixTmplArgs) EscapeQuotes(v string) string {
+ return strings.ReplaceAll(v, `"`, `\\"`)
+}
+
+// ElemType returns the Elem() type of xType, if applicable.
+func (a *postfixTmplArgs) ElemType() types.Type {
+ if e, _ := a.Type.(interface{ Elem() types.Type }); e != nil {
+ return e.Elem()
+ }
+ return nil
+}
+
+// Kind returns the underlying kind of type, e.g. "slice", "struct",
+// etc.
+func (a *postfixTmplArgs) Kind() string {
+ t := reflect.TypeOf(a.Type.Underlying())
+ return strings.ToLower(strings.TrimPrefix(t.String(), "*types."))
+}
+
+// KeyType returns the type of X's key. KeyType panics if X is not a
+// map.
+func (a *postfixTmplArgs) KeyType() types.Type {
+ return a.Type.Underlying().(*types.Map).Key()
+}
+
+// Tuple returns the tuple result vars if X is a call expression.
+func (a *postfixTmplArgs) Tuple() []*types.Var {
+ tuple, _ := a.Type.(*types.Tuple)
+ if tuple == nil {
+ return nil
+ }
+
+ typs := make([]*types.Var, 0, tuple.Len())
+ for i := 0; i < tuple.Len(); i++ {
+ typs = append(typs, tuple.At(i))
+ }
+ return typs
+}
+
+// TypeName returns the textual representation of type t.
+func (a *postfixTmplArgs) TypeName(t types.Type) (string, error) {
+ if t == nil || t == types.Typ[types.Invalid] {
+ return "", fmt.Errorf("invalid type: %v", t)
+ }
+ return types.TypeString(t, a.qf), nil
+}
+
+// VarName returns a suitable variable name for the type t. If t
+// implements the error interface, "err" is used. If t is not a named
+// type then nonNamedDefault is used. Otherwise a name is made by
+// abbreviating the type name. If the resultant name is already in
+// scope, an integer is appended to make a unique name.
+func (a *postfixTmplArgs) VarName(t types.Type, nonNamedDefault string) string {
+ if t == nil {
+ t = types.Typ[types.Invalid]
+ }
+
+ var name string
+ // go/types predicates are undefined on types.Typ[types.Invalid].
+ if !types.Identical(t, types.Typ[types.Invalid]) && types.Implements(t, errorIntf) {
+ name = "err"
+ } else if _, isNamed := source.Deref(t).(*types.Named); !isNamed {
+ name = nonNamedDefault
+ }
+
+ if name == "" {
+ name = types.TypeString(t, func(p *types.Package) string {
+ return ""
+ })
+ name = abbreviateTypeName(name)
+ }
+
+ if dot := strings.LastIndex(name, "."); dot > -1 {
+ name = name[dot+1:]
+ }
+
+ uniqueName := name
+ for i := 2; ; i++ {
+ if s, _ := a.scope.LookupParent(uniqueName, token.NoPos); s == nil && !a.varNames[uniqueName] {
+ break
+ }
+ uniqueName = fmt.Sprintf("%s%d", name, i)
+ }
+
+ a.varNames[uniqueName] = true
+
+ return uniqueName
+}
+
+func (c *completer) addPostfixSnippetCandidates(ctx context.Context, sel *ast.SelectorExpr) {
+ if !c.opts.postfix {
+ return
+ }
+
+ initPostfixRules()
+
+ if sel == nil || sel.Sel == nil {
+ return
+ }
+
+ selType := c.pkg.GetTypesInfo().TypeOf(sel.X)
+ if selType == nil {
+ return
+ }
+
+ // Skip empty tuples since there is no value to operate on.
+ if tuple, ok := selType.Underlying().(*types.Tuple); ok && tuple == nil {
+ return
+ }
+
+ tokFile := c.pkg.FileSet().File(c.pos)
+
+ // Only replace sel with a statement if sel is already a statement.
+ var stmtOK bool
+ for i, n := range c.path {
+ if n == sel && i < len(c.path)-1 {
+ switch p := c.path[i+1].(type) {
+ case *ast.ExprStmt:
+ stmtOK = true
+ case *ast.AssignStmt:
+ // In cases like:
+ //
+ // foo.<>
+ // bar = 123
+ //
+ // detect that "foo." makes up the entire statement since the
+ // apparent selector spans lines.
+ stmtOK = tokFile.Line(c.pos) < tokFile.Line(p.TokPos)
+ }
+ break
+ }
+ }
+
+ scope := c.pkg.GetTypes().Scope().Innermost(c.pos)
+ if scope == nil {
+ return
+ }
+
+ // afterDot is the position after selector dot, e.g. "|" in
+ // "foo.|print".
+ afterDot := sel.Sel.Pos()
+
+ // We must detect dangling selectors such as:
+ //
+ // foo.<>
+ // bar
+ //
+ // and adjust afterDot so that we don't mistakenly delete the
+ // newline thinking "bar" is part of our selector.
+ if startLine := tokFile.Line(sel.Pos()); startLine != tokFile.Line(afterDot) {
+ if tokFile.Line(c.pos) != startLine {
+ return
+ }
+ afterDot = c.pos
+ }
+
+ for _, rule := range postfixTmpls {
+ // When completing foo.print<>, "print" is naturally overwritten,
+ // but we need to also remove "foo." so the snippet has a clean
+ // slate.
+ edits, err := c.editText(sel.Pos(), afterDot, "")
+ if err != nil {
+ event.Error(ctx, "error calculating postfix edits", err)
+ return
+ }
+
+ tmplArgs := postfixTmplArgs{
+ X: source.FormatNode(c.pkg.FileSet(), sel.X),
+ StmtOK: stmtOK,
+ Obj: exprObj(c.pkg.GetTypesInfo(), sel.X),
+ Type: selType,
+ qf: c.qf,
+ importIfNeeded: c.importIfNeeded,
+ scope: scope,
+ varNames: make(map[string]bool),
+ }
+
+ // Feed the template straight into the snippet builder. This
+ // allows templates to build snippets as they are executed.
+ err = rule.tmpl.Execute(&tmplArgs.snip, &tmplArgs)
+ if err != nil {
+ event.Error(ctx, "error executing postfix template", err)
+ continue
+ }
+
+ if strings.TrimSpace(tmplArgs.snip.String()) == "" {
+ continue
+ }
+
+ score := c.matcher.Score(rule.label)
+ if score <= 0 {
+ continue
+ }
+
+ c.items = append(c.items, CompletionItem{
+ Label: rule.label + "!",
+ Detail: rule.details,
+ Score: float64(score) * 0.01,
+ Kind: protocol.SnippetCompletion,
+ snippet: &tmplArgs.snip,
+ AdditionalTextEdits: append(edits, tmplArgs.edits...),
+ })
+ }
+}
+
+var postfixRulesOnce sync.Once
+
+func initPostfixRules() {
+ postfixRulesOnce.Do(func() {
+ var idx int
+ for _, rule := range postfixTmpls {
+ var err error
+ rule.tmpl, err = template.New("postfix_snippet").Parse(rule.body)
+ if err != nil {
+ log.Panicf("error parsing postfix snippet template: %v", err)
+ }
+ postfixTmpls[idx] = rule
+ idx++
+ }
+ postfixTmpls = postfixTmpls[:idx]
+ })
+}
+
+// importIfNeeded returns the package identifier and any necessary
+// edits to import package pkgPath.
+func (c *completer) importIfNeeded(pkgPath string, scope *types.Scope) (string, []protocol.TextEdit, error) {
+ defaultName := imports.ImportPathToAssumedName(pkgPath)
+
+ // Check if file already imports pkgPath.
+ for _, s := range c.file.Imports {
+ // TODO(adonovan): what if pkgPath has a vendor/ suffix?
+ // This may be the cause of go.dev/issue/56291.
+ if source.UnquoteImportPath(s) == source.ImportPath(pkgPath) {
+ if s.Name == nil {
+ return defaultName, nil, nil
+ }
+ if s.Name.Name != "_" {
+ return s.Name.Name, nil, nil
+ }
+ }
+ }
+
+ // Give up if the package's name is already in use by another object.
+ if _, obj := scope.LookupParent(defaultName, token.NoPos); obj != nil {
+ return "", nil, fmt.Errorf("import name %q of %q already in use", defaultName, pkgPath)
+ }
+
+ edits, err := c.importEdits(&importInfo{
+ importPath: pkgPath,
+ })
+ if err != nil {
+ return "", nil, err
+ }
+
+ return defaultName, edits, nil
+}
diff --git a/gopls/internal/lsp/source/completion/printf.go b/gopls/internal/lsp/source/completion/printf.go
new file mode 100644
index 000000000..432011755
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/printf.go
@@ -0,0 +1,172 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "go/ast"
+ "go/constant"
+ "go/types"
+ "strconv"
+ "strings"
+ "unicode/utf8"
+)
+
+// printfArgKind returns the expected objKind when completing a
+// printf-like operand. call is the printf-like function call, and
+// argIdx is the index of call.Args being completed.
+func printfArgKind(info *types.Info, call *ast.CallExpr, argIdx int) objKind {
+ // Printf-like function name must end in "f".
+ fn := exprObj(info, call.Fun)
+ if fn == nil || !strings.HasSuffix(fn.Name(), "f") {
+ return kindAny
+ }
+
+ sig, _ := fn.Type().(*types.Signature)
+ if sig == nil {
+ return kindAny
+ }
+
+ // Must be variadic and take at least two params.
+ numParams := sig.Params().Len()
+ if !sig.Variadic() || numParams < 2 || argIdx < numParams-1 {
+ return kindAny
+ }
+
+ // Param preceding variadic args must be a (format) string.
+ if !types.Identical(sig.Params().At(numParams-2).Type(), types.Typ[types.String]) {
+ return kindAny
+ }
+
+ // Format string must be a constant.
+ strArg := info.Types[call.Args[numParams-2]].Value
+ if strArg == nil || strArg.Kind() != constant.String {
+ return kindAny
+ }
+
+ return formatOperandKind(constant.StringVal(strArg), argIdx-(numParams-1)+1)
+}
+
+// formatOperandKind returns the objKind corresponding to format's
+// operandIdx'th operand.
+func formatOperandKind(format string, operandIdx int) objKind {
+ var (
+ prevOperandIdx int
+ kind = kindAny
+ )
+ for {
+ i := strings.Index(format, "%")
+ if i == -1 {
+ break
+ }
+
+ var operands []formatOperand
+ format, operands = parsePrintfVerb(format[i+1:], prevOperandIdx)
+
+ // Check if any this verb's operands correspond to our target
+ // operandIdx.
+ for _, v := range operands {
+ if v.idx == operandIdx {
+ if kind == kindAny {
+ kind = v.kind
+ } else if v.kind != kindAny {
+ // If multiple verbs refer to the same operand, take the
+ // intersection of their kinds.
+ kind &= v.kind
+ }
+ }
+
+ prevOperandIdx = v.idx
+ }
+ }
+ return kind
+}
+
+type formatOperand struct {
+ // idx is the one-based printf operand index.
+ idx int
+ // kind is a mask of expected kinds of objects for this operand.
+ kind objKind
+}
+
+// parsePrintfVerb parses the leading printf verb in f. The opening
+// "%" must already be trimmed from f. prevIdx is the previous
+// operand's index, or zero if this is the first verb. The format
+// string is returned with the leading verb removed. Multiple operands
+// can be returned in the case of dynamic widths such as "%*.*f".
+func parsePrintfVerb(f string, prevIdx int) (string, []formatOperand) {
+ var verbs []formatOperand
+
+ addVerb := func(k objKind) {
+ verbs = append(verbs, formatOperand{
+ idx: prevIdx + 1,
+ kind: k,
+ })
+ prevIdx++
+ }
+
+ for len(f) > 0 {
+ // Trim first rune off of f so we are guaranteed to make progress.
+ r, l := utf8.DecodeRuneInString(f)
+ f = f[l:]
+
+ // We care about three things:
+ // 1. The verb, which maps directly to object kind.
+ // 2. Explicit operand indices like "%[2]s".
+ // 3. Dynamic widths using "*".
+ switch r {
+ case '%':
+ return f, nil
+ case '*':
+ addVerb(kindInt)
+ continue
+ case '[':
+ // Parse operand index as in "%[2]s".
+ i := strings.Index(f, "]")
+ if i == -1 {
+ return f, nil
+ }
+
+ idx, err := strconv.Atoi(f[:i])
+ f = f[i+1:]
+ if err != nil {
+ return f, nil
+ }
+
+ prevIdx = idx - 1
+ continue
+ case 'v', 'T':
+ addVerb(kindAny)
+ case 't':
+ addVerb(kindBool)
+ case 'c', 'd', 'o', 'O', 'U':
+ addVerb(kindInt)
+ case 'e', 'E', 'f', 'F', 'g', 'G':
+ addVerb(kindFloat | kindComplex)
+ case 'b':
+ addVerb(kindInt | kindFloat | kindComplex | kindBytes)
+ case 'q', 's':
+ addVerb(kindString | kindBytes | kindStringer | kindError)
+ case 'x', 'X':
+ // Omit kindStringer and kindError though technically allowed.
+ addVerb(kindString | kindBytes | kindInt | kindFloat | kindComplex)
+ case 'p':
+ addVerb(kindPtr | kindSlice)
+ case 'w':
+ addVerb(kindError)
+ case '+', '-', '#', ' ', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ // Flag or numeric width/precision value.
+ continue
+ default:
+ // Assume unrecognized rune is a custom fmt.Formatter verb.
+ addVerb(kindAny)
+ }
+
+ if len(verbs) > 0 {
+ break
+ }
+ }
+
+ return f, verbs
+}
diff --git a/gopls/internal/lsp/source/completion/printf_test.go b/gopls/internal/lsp/source/completion/printf_test.go
new file mode 100644
index 000000000..19d295b8d
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/printf_test.go
@@ -0,0 +1,72 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "fmt"
+ "testing"
+)
+
+func TestFormatOperandKind(t *testing.T) {
+ cases := []struct {
+ f string
+ idx int
+ kind objKind
+ }{
+ {"", 1, kindAny},
+ {"%", 1, kindAny},
+ {"%%%", 1, kindAny},
+ {"%[1", 1, kindAny},
+ {"%[?%s", 2, kindAny},
+ {"%[abc]v", 1, kindAny},
+
+ {"%v", 1, kindAny},
+ {"%T", 1, kindAny},
+ {"%t", 1, kindBool},
+ {"%d", 1, kindInt},
+ {"%c", 1, kindInt},
+ {"%o", 1, kindInt},
+ {"%O", 1, kindInt},
+ {"%U", 1, kindInt},
+ {"%e", 1, kindFloat | kindComplex},
+ {"%E", 1, kindFloat | kindComplex},
+ {"%f", 1, kindFloat | kindComplex},
+ {"%F", 1, kindFloat | kindComplex},
+ {"%g", 1, kindFloat | kindComplex},
+ {"%G", 1, kindFloat | kindComplex},
+ {"%b", 1, kindInt | kindFloat | kindComplex | kindBytes},
+ {"%q", 1, kindString | kindBytes | kindStringer | kindError},
+ {"%s", 1, kindString | kindBytes | kindStringer | kindError},
+ {"%x", 1, kindString | kindBytes | kindInt | kindFloat | kindComplex},
+ {"%X", 1, kindString | kindBytes | kindInt | kindFloat | kindComplex},
+ {"%p", 1, kindPtr | kindSlice},
+ {"%w", 1, kindError},
+
+ {"%1.2f", 1, kindFloat | kindComplex},
+ {"%*f", 1, kindInt},
+ {"%*f", 2, kindFloat | kindComplex},
+ {"%*.*f", 1, kindInt},
+ {"%*.*f", 2, kindInt},
+ {"%*.*f", 3, kindFloat | kindComplex},
+ {"%[3]*.[2]*[1]f", 1, kindFloat | kindComplex},
+ {"%[3]*.[2]*[1]f", 2, kindInt},
+ {"%[3]*.[2]*[1]f", 3, kindInt},
+
+ {"foo %% %d", 1, kindInt},
+ {"%#-12.34f", 1, kindFloat | kindComplex},
+ {"% d", 1, kindInt},
+
+ {"%s %[1]X %d", 1, kindString | kindBytes},
+ {"%s %[1]X %d", 2, kindInt},
+ }
+
+ for _, c := range cases {
+ t.Run(fmt.Sprintf("%q#%d", c.f, c.idx), func(t *testing.T) {
+ if got := formatOperandKind(c.f, c.idx); got != c.kind {
+ t.Errorf("expected %d (%[1]b), got %d (%[2]b)", c.kind, got)
+ }
+ })
+ }
+}
diff --git a/gopls/internal/lsp/source/completion/snippet.go b/gopls/internal/lsp/source/completion/snippet.go
new file mode 100644
index 000000000..f4ea767e9
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/snippet.go
@@ -0,0 +1,116 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "go/ast"
+
+ "golang.org/x/tools/gopls/internal/lsp/safetoken"
+ "golang.org/x/tools/gopls/internal/lsp/snippet"
+)
+
+// structFieldSnippet calculates the snippet for struct literal field names.
+func (c *completer) structFieldSnippet(cand candidate, detail string, snip *snippet.Builder) {
+ if !c.wantStructFieldCompletions() {
+ return
+ }
+
+ // If we are in a deep completion then we can't be completing a field
+ // name (e.g. "Foo{f<>}" completing to "Foo{f.Bar}" should not generate
+ // a snippet).
+ if len(cand.path) > 0 {
+ return
+ }
+
+ clInfo := c.enclosingCompositeLiteral
+
+ // If we are already in a key-value expression, we don't want a snippet.
+ if clInfo.kv != nil {
+ return
+ }
+
+ // A plain snippet turns "Foo{Ba<>" into "Foo{Bar: <>".
+ snip.WriteText(": ")
+ snip.WritePlaceholder(func(b *snippet.Builder) {
+ // A placeholder snippet turns "Foo{Ba<>" into "Foo{Bar: <*int*>".
+ if c.opts.placeholders {
+ b.WriteText(detail)
+ }
+ })
+
+ fset := c.pkg.FileSet()
+
+ // If the cursor position is on a different line from the literal's opening brace,
+ // we are in a multiline literal. Ignore line directives.
+ if safetoken.StartPosition(fset, c.pos).Line != safetoken.StartPosition(fset, clInfo.cl.Lbrace).Line {
+ snip.WriteText(",")
+ }
+}
+
+// functionCallSnippet calculates the snippet for function calls.
+func (c *completer) functionCallSnippet(name string, tparams, params []string, snip *snippet.Builder) {
+ // If there is no suffix then we need to reuse existing call parens
+ // "()" if present. If there is an identifier suffix then we always
+ // need to include "()" since we don't overwrite the suffix.
+ if c.surrounding != nil && c.surrounding.Suffix() == "" && len(c.path) > 1 {
+ // If we are the left side (i.e. "Fun") part of a call expression,
+ // we don't want a snippet since there are already parens present.
+ switch n := c.path[1].(type) {
+ case *ast.CallExpr:
+ // The Lparen != Rparen check detects fudged CallExprs we
+ // inserted when fixing the AST. In this case, we do still need
+ // to insert the calling "()" parens.
+ if n.Fun == c.path[0] && n.Lparen != n.Rparen {
+ return
+ }
+ case *ast.SelectorExpr:
+ if len(c.path) > 2 {
+ if call, ok := c.path[2].(*ast.CallExpr); ok && call.Fun == c.path[1] && call.Lparen != call.Rparen {
+ return
+ }
+ }
+ }
+ }
+
+ snip.WriteText(name)
+
+ if len(tparams) > 0 {
+ snip.WriteText("[")
+ if c.opts.placeholders {
+ for i, tp := range tparams {
+ if i > 0 {
+ snip.WriteText(", ")
+ }
+ snip.WritePlaceholder(func(b *snippet.Builder) {
+ b.WriteText(tp)
+ })
+ }
+ } else {
+ snip.WritePlaceholder(nil)
+ }
+ snip.WriteText("]")
+ }
+
+ snip.WriteText("(")
+
+ if c.opts.placeholders {
+ // A placeholder snippet turns "someFun<>" into "someFunc(<*i int*>, *s string*)".
+ for i, p := range params {
+ if i > 0 {
+ snip.WriteText(", ")
+ }
+ snip.WritePlaceholder(func(b *snippet.Builder) {
+ b.WriteText(p)
+ })
+ }
+ } else {
+ // A plain snippet turns "someFun<>" into "someFunc(<>)".
+ if len(params) > 0 {
+ snip.WritePlaceholder(nil)
+ }
+ }
+
+ snip.WriteText(")")
+}
diff --git a/gopls/internal/lsp/source/completion/statements.go b/gopls/internal/lsp/source/completion/statements.go
new file mode 100644
index 000000000..707375fa1
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/statements.go
@@ -0,0 +1,361 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "golang.org/x/tools/gopls/internal/lsp/protocol"
+ "golang.org/x/tools/gopls/internal/lsp/snippet"
+ "golang.org/x/tools/gopls/internal/lsp/source"
+)
+
+// addStatementCandidates adds full statement completion candidates
+// appropriate for the current context.
+func (c *completer) addStatementCandidates() {
+ c.addErrCheck()
+ c.addAssignAppend()
+}
+
+// addAssignAppend offers a completion candidate of the form:
+//
+// someSlice = append(someSlice, )
+//
+// It will offer the "append" completion in either of two situations:
+//
+// 1. Position is in RHS of assign, prefix matches "append", and
+// corresponding LHS object is a slice. For example,
+// "foo = ap<>" completes to "foo = append(foo, )".
+//
+// 2. Prefix is an ident or selector in an *ast.ExprStmt (i.e.
+// beginning of statement), and our best matching candidate is a
+// slice. For example: "foo.ba" completes to "foo.bar = append(foo.bar, )".
+func (c *completer) addAssignAppend() {
+ if len(c.path) < 3 {
+ return
+ }
+
+ ident, _ := c.path[0].(*ast.Ident)
+ if ident == nil {
+ return
+ }
+
+ var (
+ // sliceText is the full name of our slice object, e.g. "s.abc" in
+ // "s.abc = app<>".
+ sliceText string
+ // needsLHS is true if we need to prepend the LHS slice name and
+ // "=" to our candidate.
+ needsLHS = false
+ fset = c.pkg.FileSet()
+ )
+
+ switch n := c.path[1].(type) {
+ case *ast.AssignStmt:
+ // We are already in an assignment. Make sure our prefix matches "append".
+ if c.matcher.Score("append") <= 0 {
+ return
+ }
+
+ exprIdx := exprAtPos(c.pos, n.Rhs)
+ if exprIdx == len(n.Rhs) || exprIdx > len(n.Lhs)-1 {
+ return
+ }
+
+ lhsType := c.pkg.GetTypesInfo().TypeOf(n.Lhs[exprIdx])
+ if lhsType == nil {
+ return
+ }
+
+ // Make sure our corresponding LHS object is a slice.
+ if _, isSlice := lhsType.Underlying().(*types.Slice); !isSlice {
+ return
+ }
+
+ // The name or our slice is whatever's in the LHS expression.
+ sliceText = source.FormatNode(fset, n.Lhs[exprIdx])
+ case *ast.SelectorExpr:
+ // Make sure we are a selector at the beginning of a statement.
+ if _, parentIsExprtStmt := c.path[2].(*ast.ExprStmt); !parentIsExprtStmt {
+ return
+ }
+
+ // So far we only know the first part of our slice name. For
+ // example in "s.a<>" we only know our slice begins with "s."
+ // since the user could still be typing.
+ sliceText = source.FormatNode(fset, n.X) + "."
+ needsLHS = true
+ case *ast.ExprStmt:
+ needsLHS = true
+ default:
+ return
+ }
+
+ var (
+ label string
+ snip snippet.Builder
+ score = highScore
+ )
+
+ if needsLHS {
+ // Offer the long form assign + append candidate if our best
+ // candidate is a slice.
+ bestItem := c.topCandidate()
+ if bestItem == nil || !bestItem.isSlice {
+ return
+ }
+
+ // Don't rank the full form assign + append candidate above the
+ // slice itself.
+ score = bestItem.Score - 0.01
+
+ // Fill in rest of sliceText now that we have the object name.
+ sliceText += bestItem.Label
+
+ // Fill in the candidate's LHS bits.
+ label = fmt.Sprintf("%s = ", bestItem.Label)
+ snip.WriteText(label)
+ }
+
+ snip.WriteText(fmt.Sprintf("append(%s, ", sliceText))
+ snip.WritePlaceholder(nil)
+ snip.WriteText(")")
+
+ c.items = append(c.items, CompletionItem{
+ Label: label + fmt.Sprintf("append(%s, )", sliceText),
+ Kind: protocol.FunctionCompletion,
+ Score: score,
+ snippet: &snip,
+ })
+}
+
+// topCandidate returns the strictly highest scoring candidate
+// collected so far. If the top two candidates have the same score,
+// nil is returned.
+func (c *completer) topCandidate() *CompletionItem {
+ var bestItem, secondBestItem *CompletionItem
+ for i := range c.items {
+ if bestItem == nil || c.items[i].Score > bestItem.Score {
+ bestItem = &c.items[i]
+ } else if secondBestItem == nil || c.items[i].Score > secondBestItem.Score {
+ secondBestItem = &c.items[i]
+ }
+ }
+
+ // If secondBestItem has the same score, bestItem isn't
+ // the strict best.
+ if secondBestItem != nil && secondBestItem.Score == bestItem.Score {
+ return nil
+ }
+
+ return bestItem
+}
+
+// addErrCheck offers a completion candidate of the form:
+//
+// if err != nil {
+// return nil, err
+// }
+//
+// In the case of test functions, it offers a completion candidate of the form:
+//
+// if err != nil {
+// t.Fatal(err)
+// }
+//
+// The position must be in a function that returns an error, and the
+// statement preceding the position must be an assignment where the
+// final LHS object is an error. addErrCheck will synthesize
+// zero values as necessary to make the return statement valid.
+func (c *completer) addErrCheck() {
+ if len(c.path) < 2 || c.enclosingFunc == nil || !c.opts.placeholders {
+ return
+ }
+
+ var (
+ errorType = types.Universe.Lookup("error").Type()
+ result = c.enclosingFunc.sig.Results()
+ testVar = getTestVar(c.enclosingFunc, c.pkg)
+ isTest = testVar != ""
+ doesNotReturnErr = result.Len() == 0 || !types.Identical(result.At(result.Len()-1).Type(), errorType)
+ )
+ // Make sure our enclosing function is a Test func or returns an error.
+ if !isTest && doesNotReturnErr {
+ return
+ }
+
+ prevLine := prevStmt(c.pos, c.path)
+ if prevLine == nil {
+ return
+ }
+
+ // Make sure our preceding statement was as assignment.
+ assign, _ := prevLine.(*ast.AssignStmt)
+ if assign == nil || len(assign.Lhs) == 0 {
+ return
+ }
+
+ lastAssignee := assign.Lhs[len(assign.Lhs)-1]
+
+ // Make sure the final assignee is an error.
+ if !types.Identical(c.pkg.GetTypesInfo().TypeOf(lastAssignee), errorType) {
+ return
+ }
+
+ var (
+ // errVar is e.g. "err" in "foo, err := bar()".
+ errVar = source.FormatNode(c.pkg.FileSet(), lastAssignee)
+
+ // Whether we need to include the "if" keyword in our candidate.
+ needsIf = true
+ )
+
+ // If the returned error from the previous statement is "_", it is not a real object.
+ // If we don't have an error, and the function signature takes a testing.TB that is either ignored
+ // or an "_", then we also can't call t.Fatal(err).
+ if errVar == "_" {
+ return
+ }
+
+ // Below we try to detect if the user has already started typing "if
+ // err" so we can replace what they've typed with our complete
+ // statement.
+ switch n := c.path[0].(type) {
+ case *ast.Ident:
+ switch c.path[1].(type) {
+ case *ast.ExprStmt:
+ // This handles:
+ //
+ // f, err := os.Open("foo")
+ // i<>
+
+ // Make sure they are typing "if".
+ if c.matcher.Score("if") <= 0 {
+ return
+ }
+ case *ast.IfStmt:
+ // This handles:
+ //
+ // f, err := os.Open("foo")
+ // if er<>
+
+ // Make sure they are typing the error's name.
+ if c.matcher.Score(errVar) <= 0 {
+ return
+ }
+
+ needsIf = false
+ default:
+ return
+ }
+ case *ast.IfStmt:
+ // This handles:
+ //
+ // f, err := os.Open("foo")
+ // if <>
+
+ // Avoid false positives by ensuring the if's cond is a bad
+ // expression. For example, don't offer the completion in cases
+ // like "if <> somethingElse".
+ if _, bad := n.Cond.(*ast.BadExpr); !bad {
+ return
+ }
+
+ // If "if" is our direct prefix, we need to include it in our
+ // candidate since the existing "if" will be overwritten.
+ needsIf = c.pos == n.Pos()+token.Pos(len("if"))
+ }
+
+ // Build up a snippet that looks like:
+ //
+ // if err != nil {
+ // return <zero value>, ..., ${1:err}
+ // }
+ //
+ // We make the error a placeholder so it is easy to alter the error.
+ var snip snippet.Builder
+ if needsIf {
+ snip.WriteText("if ")
+ }
+ snip.WriteText(fmt.Sprintf("%s != nil {\n\t", errVar))
+
+ var label string
+ if isTest {
+ snip.WriteText(fmt.Sprintf("%s.Fatal(%s)", testVar, errVar))
+ label = fmt.Sprintf("%[1]s != nil { %[2]s.Fatal(%[1]s) }", errVar, testVar)
+ } else {
+ snip.WriteText("return ")
+ for i := 0; i < result.Len()-1; i++ {
+ snip.WriteText(formatZeroValue(result.At(i).Type(), c.qf))
+ snip.WriteText(", ")
+ }
+ snip.WritePlaceholder(func(b *snippet.Builder) {
+ b.WriteText(errVar)
+ })
+ label = fmt.Sprintf("%[1]s != nil { return %[1]s }", errVar)
+ }
+
+ snip.WriteText("\n}")
+
+ if needsIf {
+ label = "if " + label
+ }
+
+ c.items = append(c.items, CompletionItem{
+ Label: label,
+ // There doesn't seem to be a more appropriate kind.
+ Kind: protocol.KeywordCompletion,
+ Score: highScore,
+ snippet: &snip,
+ })
+}
+
+// getTestVar checks the function signature's input parameters and returns
+// the name of the first parameter that implements "testing.TB". For example,
+// func someFunc(t *testing.T) returns the string "t", func someFunc(b *testing.B)
+// returns "b" etc. An empty string indicates that the function signature
+// does not take a testing.TB parameter or does so but is ignored such
+// as func someFunc(*testing.T).
+func getTestVar(enclosingFunc *funcInfo, pkg source.Package) string {
+ if enclosingFunc == nil || enclosingFunc.sig == nil {
+ return ""
+ }
+
+ var testingPkg *types.Package
+ for _, p := range pkg.GetTypes().Imports() {
+ if p.Path() == "testing" {
+ testingPkg = p
+ break
+ }
+ }
+ if testingPkg == nil {
+ return ""
+ }
+ tbObj := testingPkg.Scope().Lookup("TB")
+ if tbObj == nil {
+ return ""
+ }
+ iface, ok := tbObj.Type().Underlying().(*types.Interface)
+ if !ok {
+ return ""
+ }
+
+ sig := enclosingFunc.sig
+ for i := 0; i < sig.Params().Len(); i++ {
+ param := sig.Params().At(i)
+ if param.Name() == "_" {
+ continue
+ }
+ if !types.Implements(param.Type(), iface) {
+ continue
+ }
+ return param.Name()
+ }
+
+ return ""
+}
diff --git a/gopls/internal/lsp/source/completion/util.go b/gopls/internal/lsp/source/completion/util.go
new file mode 100644
index 000000000..4b6ec09a0
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/util.go
@@ -0,0 +1,344 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "golang.org/x/tools/go/types/typeutil"
+ "golang.org/x/tools/gopls/internal/lsp/protocol"
+ "golang.org/x/tools/gopls/internal/lsp/safetoken"
+ "golang.org/x/tools/gopls/internal/lsp/source"
+ "golang.org/x/tools/internal/diff"
+ "golang.org/x/tools/internal/typeparams"
+)
+
+// exprAtPos returns the index of the expression containing pos.
+func exprAtPos(pos token.Pos, args []ast.Expr) int {
+ for i, expr := range args {
+ if expr.Pos() <= pos && pos <= expr.End() {
+ return i
+ }
+ }
+ return len(args)
+}
+
+// eachField invokes fn for each field that can be selected from a
+// value of type T.
+func eachField(T types.Type, fn func(*types.Var)) {
+ // TODO(adonovan): this algorithm doesn't exclude ambiguous
+ // selections that match more than one field/method.
+ // types.NewSelectionSet should do that for us.
+
+ // for termination on recursive types
+ var seen typeutil.Map
+
+ var visit func(T types.Type)
+ visit = func(T types.Type) {
+ if T, ok := source.Deref(T).Underlying().(*types.Struct); ok {
+ if seen.At(T) != nil {
+ return
+ }
+
+ for i := 0; i < T.NumFields(); i++ {
+ f := T.Field(i)
+ fn(f)
+ if f.Anonymous() {
+ seen.Set(T, true)
+ visit(f.Type())
+ }
+ }
+ }
+ }
+ visit(T)
+}
+
+// typeIsValid reports whether typ doesn't contain any Invalid types.
+func typeIsValid(typ types.Type) bool {
+ // Check named types separately, because we don't want
+ // to call Underlying() on them to avoid problems with recursive types.
+ if _, ok := typ.(*types.Named); ok {
+ return true
+ }
+
+ switch typ := typ.Underlying().(type) {
+ case *types.Basic:
+ return typ.Kind() != types.Invalid
+ case *types.Array:
+ return typeIsValid(typ.Elem())
+ case *types.Slice:
+ return typeIsValid(typ.Elem())
+ case *types.Pointer:
+ return typeIsValid(typ.Elem())
+ case *types.Map:
+ return typeIsValid(typ.Key()) && typeIsValid(typ.Elem())
+ case *types.Chan:
+ return typeIsValid(typ.Elem())
+ case *types.Signature:
+ return typeIsValid(typ.Params()) && typeIsValid(typ.Results())
+ case *types.Tuple:
+ for i := 0; i < typ.Len(); i++ {
+ if !typeIsValid(typ.At(i).Type()) {
+ return false
+ }
+ }
+ return true
+ case *types.Struct, *types.Interface:
+ // Don't bother checking structs, interfaces for validity.
+ return true
+ default:
+ return false
+ }
+}
+
+// resolveInvalid traverses the node of the AST that defines the scope
+// containing the declaration of obj, and attempts to find a user-friendly
+// name for its invalid type. The resulting Object and its Type are fake.
+func resolveInvalid(fset *token.FileSet, obj types.Object, node ast.Node, info *types.Info) types.Object {
+ var resultExpr ast.Expr
+ ast.Inspect(node, func(node ast.Node) bool {
+ switch n := node.(type) {
+ case *ast.ValueSpec:
+ for _, name := range n.Names {
+ if info.Defs[name] == obj {
+ resultExpr = n.Type
+ }
+ }
+ return false
+ case *ast.Field: // This case handles parameters and results of a FuncDecl or FuncLit.
+ for _, name := range n.Names {
+ if info.Defs[name] == obj {
+ resultExpr = n.Type
+ }
+ }
+ return false
+ default:
+ return true
+ }
+ })
+ // Construct a fake type for the object and return a fake object with this type.
+ typename := source.FormatNode(fset, resultExpr)
+ typ := types.NewNamed(types.NewTypeName(token.NoPos, obj.Pkg(), typename, nil), types.Typ[types.Invalid], nil)
+ return types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), typ)
+}
+
+func isPointer(T types.Type) bool {
+ _, ok := T.(*types.Pointer)
+ return ok
+}
+
+func isVar(obj types.Object) bool {
+ _, ok := obj.(*types.Var)
+ return ok
+}
+
+func isTypeName(obj types.Object) bool {
+ _, ok := obj.(*types.TypeName)
+ return ok
+}
+
+func isFunc(obj types.Object) bool {
+ _, ok := obj.(*types.Func)
+ return ok
+}
+
+func isEmptyInterface(T types.Type) bool {
+ intf, _ := T.(*types.Interface)
+ return intf != nil && intf.NumMethods() == 0 && typeparams.IsMethodSet(intf)
+}
+
+func isUntyped(T types.Type) bool {
+ if basic, ok := T.(*types.Basic); ok {
+ return basic.Info()&types.IsUntyped > 0
+ }
+ return false
+}
+
+func isPkgName(obj types.Object) bool {
+ _, ok := obj.(*types.PkgName)
+ return ok
+}
+
+func isASTFile(n ast.Node) bool {
+ _, ok := n.(*ast.File)
+ return ok
+}
+
+func deslice(T types.Type) types.Type {
+ if slice, ok := T.Underlying().(*types.Slice); ok {
+ return slice.Elem()
+ }
+ return nil
+}
+
+// isSelector returns the enclosing *ast.SelectorExpr when pos is in the
+// selector.
+func enclosingSelector(path []ast.Node, pos token.Pos) *ast.SelectorExpr {
+ if len(path) == 0 {
+ return nil
+ }
+
+ if sel, ok := path[0].(*ast.SelectorExpr); ok {
+ return sel
+ }
+
+ if _, ok := path[0].(*ast.Ident); ok && len(path) > 1 {
+ if sel, ok := path[1].(*ast.SelectorExpr); ok && pos >= sel.Sel.Pos() {
+ return sel
+ }
+ }
+
+ return nil
+}
+
+// enclosingDeclLHS returns LHS idents from containing value spec or
+// assign statement.
+func enclosingDeclLHS(path []ast.Node) []*ast.Ident {
+ for _, n := range path {
+ switch n := n.(type) {
+ case *ast.ValueSpec:
+ return n.Names
+ case *ast.AssignStmt:
+ ids := make([]*ast.Ident, 0, len(n.Lhs))
+ for _, e := range n.Lhs {
+ if id, ok := e.(*ast.Ident); ok {
+ ids = append(ids, id)
+ }
+ }
+ return ids
+ }
+ }
+
+ return nil
+}
+
+// exprObj returns the types.Object associated with the *ast.Ident or
+// *ast.SelectorExpr e.
+func exprObj(info *types.Info, e ast.Expr) types.Object {
+ var ident *ast.Ident
+ switch expr := e.(type) {
+ case *ast.Ident:
+ ident = expr
+ case *ast.SelectorExpr:
+ ident = expr.Sel
+ default:
+ return nil
+ }
+
+ return info.ObjectOf(ident)
+}
+
+// typeConversion returns the type being converted to if call is a type
+// conversion expression.
+func typeConversion(call *ast.CallExpr, info *types.Info) types.Type {
+ // Type conversion (e.g. "float64(foo)").
+ if fun, _ := exprObj(info, call.Fun).(*types.TypeName); fun != nil {
+ return fun.Type()
+ }
+
+ return nil
+}
+
+// fieldsAccessible returns whether s has at least one field accessible by p.
+func fieldsAccessible(s *types.Struct, p *types.Package) bool {
+ for i := 0; i < s.NumFields(); i++ {
+ f := s.Field(i)
+ if f.Exported() || f.Pkg() == p {
+ return true
+ }
+ }
+ return false
+}
+
+// prevStmt returns the statement that precedes the statement containing pos.
+// For example:
+//
+// foo := 1
+// bar(1 + 2<>)
+//
+// If "<>" is pos, prevStmt returns "foo := 1"
+func prevStmt(pos token.Pos, path []ast.Node) ast.Stmt {
+ var blockLines []ast.Stmt
+ for i := 0; i < len(path) && blockLines == nil; i++ {
+ switch n := path[i].(type) {
+ case *ast.BlockStmt:
+ blockLines = n.List
+ case *ast.CommClause:
+ blockLines = n.Body
+ case *ast.CaseClause:
+ blockLines = n.Body
+ }
+ }
+
+ for i := len(blockLines) - 1; i >= 0; i-- {
+ if blockLines[i].End() < pos {
+ return blockLines[i]
+ }
+ }
+
+ return nil
+}
+
+// formatZeroValue produces Go code representing the zero value of T. It
+// returns the empty string if T is invalid.
+func formatZeroValue(T types.Type, qf types.Qualifier) string {
+ switch u := T.Underlying().(type) {
+ case *types.Basic:
+ switch {
+ case u.Info()&types.IsNumeric > 0:
+ return "0"
+ case u.Info()&types.IsString > 0:
+ return `""`
+ case u.Info()&types.IsBoolean > 0:
+ return "false"
+ default:
+ return ""
+ }
+ case *types.Pointer, *types.Interface, *types.Chan, *types.Map, *types.Slice, *types.Signature:
+ return "nil"
+ default:
+ return types.TypeString(T, qf) + "{}"
+ }
+}
+
+// isBasicKind returns whether t is a basic type of kind k.
+func isBasicKind(t types.Type, k types.BasicInfo) bool {
+ b, _ := t.Underlying().(*types.Basic)
+ return b != nil && b.Info()&k > 0
+}
+
+func (c *completer) editText(from, to token.Pos, newText string) ([]protocol.TextEdit, error) {
+ start, end, err := safetoken.Offsets(c.tokFile, from, to)
+ if err != nil {
+ return nil, err // can't happen: from/to came from c
+ }
+ return source.ToProtocolEdits(c.mapper, []diff.Edit{{
+ Start: start,
+ End: end,
+ New: newText,
+ }})
+}
+
+// assignableTo is like types.AssignableTo, but returns false if
+// either type is invalid.
+func assignableTo(x, to types.Type) bool {
+ if x == types.Typ[types.Invalid] || to == types.Typ[types.Invalid] {
+ return false
+ }
+
+ return types.AssignableTo(x, to)
+}
+
+// convertibleTo is like types.ConvertibleTo, but returns false if
+// either type is invalid.
+func convertibleTo(x, to types.Type) bool {
+ if x == types.Typ[types.Invalid] || to == types.Typ[types.Invalid] {
+ return false
+ }
+
+ return types.ConvertibleTo(x, to)
+}
diff --git a/gopls/internal/lsp/source/completion/util_test.go b/gopls/internal/lsp/source/completion/util_test.go
new file mode 100644
index 000000000..c94d279fb
--- /dev/null
+++ b/gopls/internal/lsp/source/completion/util_test.go
@@ -0,0 +1,28 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "go/types"
+ "testing"
+)
+
+func TestFormatZeroValue(t *testing.T) {
+ tests := []struct {
+ typ types.Type
+ want string
+ }{
+ {types.Typ[types.String], `""`},
+ {types.Typ[types.Byte], "0"},
+ {types.Typ[types.Invalid], ""},
+ {types.Universe.Lookup("error").Type(), "nil"},
+ }
+
+ for _, test := range tests {
+ if got := formatZeroValue(test.typ, nil); got != test.want {
+ t.Errorf("formatZeroValue(%v) = %q, want %q", test.typ, got, test.want)
+ }
+ }
+}