aboutsummaryrefslogtreecommitdiff
path: root/internal/lsp/source/completion
diff options
context:
space:
mode:
Diffstat (limited to 'internal/lsp/source/completion')
-rw-r--r--internal/lsp/source/completion/builtin.go147
-rw-r--r--internal/lsp/source/completion/completion.go2967
-rw-r--r--internal/lsp/source/completion/deep_completion.go362
-rw-r--r--internal/lsp/source/completion/deep_completion_test.go33
-rw-r--r--internal/lsp/source/completion/definition.go127
-rw-r--r--internal/lsp/source/completion/format.go340
-rw-r--r--internal/lsp/source/completion/fuzz.go142
-rw-r--r--internal/lsp/source/completion/keywords.go154
-rw-r--r--internal/lsp/source/completion/labels.go112
-rw-r--r--internal/lsp/source/completion/literal.go440
-rw-r--r--internal/lsp/source/completion/package.go364
-rw-r--r--internal/lsp/source/completion/package_test.go77
-rw-r--r--internal/lsp/source/completion/postfix_snippets.go461
-rw-r--r--internal/lsp/source/completion/printf.go172
-rw-r--r--internal/lsp/source/completion/printf_test.go72
-rw-r--r--internal/lsp/source/completion/snippet.go115
-rw-r--r--internal/lsp/source/completion/statements.go360
-rw-r--r--internal/lsp/source/completion/util.go326
-rw-r--r--internal/lsp/source/completion/util_test.go28
19 files changed, 0 insertions, 6799 deletions
diff --git a/internal/lsp/source/completion/builtin.go b/internal/lsp/source/completion/builtin.go
deleted file mode 100644
index 39732d864..000000000
--- a/internal/lsp/source/completion/builtin.go
+++ /dev/null
@@ -1,147 +0,0 @@
-// 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/internal/lsp/source/completion/completion.go b/internal/lsp/source/completion/completion.go
deleted file mode 100644
index 60c404dc5..000000000
--- a/internal/lsp/source/completion/completion.go
+++ /dev/null
@@ -1,2967 +0,0 @@
-// 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 (
- "context"
- "fmt"
- "go/ast"
- "go/constant"
- "go/scanner"
- "go/token"
- "go/types"
- "math"
- "sort"
- "strconv"
- "strings"
- "sync"
- "time"
- "unicode"
-
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/imports"
- "golang.org/x/tools/internal/lsp/fuzzy"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/snippet"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/typeparams"
- errors "golang.org/x/xerrors"
-)
-
-type CompletionItem struct {
- // 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
-
- // obj is the object from which this candidate was derived, if any.
- // obj is for internal use only.
- obj types.Object
-}
-
-// 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
- 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
-
- // pos is the position at which the request was triggered.
- 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.ColumnMapper
-
- // startTime is when we started processing this completion request. It does
- // not include any time the request spent in the queue.
- startTime time.Time
-}
-
-// 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
- pkg source.Package
-}
-
-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
- cursor token.Pos
- source.MappedRange
-}
-
-func (p Selection) Content() string {
- return p.content
-}
-
-func (p Selection) Start() token.Pos {
- return p.MappedRange.SpanRange().Start
-}
-
-func (p Selection) End() token.Pos {
- return p.MappedRange.SpanRange().End
-}
-
-func (p Selection) Prefix() string {
- return p.content[:p.cursor-p.SpanRange().Start]
-}
-
-func (p Selection) Suffix() string {
- return p.content[p.cursor-p.SpanRange().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.
- MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper, ident.Pos(), ident.End()),
- }
-
- 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,
- MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper, c.pos, c.pos),
- }
- }
- return c.surrounding
-}
-
-// candidate represents a completion candidate.
-type candidate struct {
- // obj is the types.Object to complete to.
- obj types.Object
-
- // score is used to rank candidates.
- score float64
-
- // 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 insterted 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.GetParsedFile(ctx, snapshot, fh, 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, errors.Errorf("getting file for Completion: %w (package completions: %v)", err, innerErr)
- }
- return items, surrounding, nil
- }
- spn, err := pgf.Mapper.PointSpan(protoPos)
- if err != nil {
- return nil, nil, err
- }
- rng, err := spn.Range(pgf.Mapper.Converter)
- 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, rng.Start-1, rng.Start-1)
- if path == nil {
- return nil, nil, errors.Errorf("cannot find node enclosing position")
- }
-
- pos := rng.Start
-
- // 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, snapshot.FileSet(), pgf.Mapper, fh)
- 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}
- }
- }
- }
-
- opts := snapshot.View().Options()
- c := &completer{
- pkg: pkg,
- snapshot: snapshot,
- qf: source.Qualifier(pgf.File, pkg.GetTypes(), pkg.GetTypesInfo()),
- completionContext: completionContext{
- triggerCharacter: protoContext.TriggerCharacter,
- triggerKind: protoContext.TriggerKind,
- },
- fh: fh,
- filename: fh.URI().Filename(),
- file: pgf.File,
- path: path,
- pos: pos,
- seen: make(map[types.Object]bool),
- enclosingFunc: enclosingFunction(path, pkg.GetTypesInfo()),
- enclosingCompositeLiteral: enclosingCompositeLiteral(path, rng.Start, 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,
- }
-
- 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 identifer being completed.
- if ident, ok := c.path[0].(*ast.Ident); ok {
- return ident
- }
-
- pos, tkn, lit := c.scanToken(src)
- if !pos.IsValid() {
- return nil
- }
-
- fakeIdent := &ast.Ident{Name: lit, NamePos: pos}
-
- if _, isBadDecl := c.path[0].(*ast.BadDecl); isBadDecl {
- // You don't get *ast.Idents at the file level, so look for bad
- // decls and use the manually extracted token.
- return fakeIdent
- } else if c.emptySwitchStmt() {
- // Only keywords are allowed in empty switch statements.
- // *ast.Idents are not parsed, so we must use the manually
- // extracted token.
- return fakeIdent
- } else if tkn.IsKeyword() {
- // Otherwise, manually extract the prefix if our containing token
- // is a keyword. This improves completion after an "accidental
- // keyword", e.g. completing to "variance" in "someFunc(var<>)".
- return fakeIdent
- }
-
- return nil
-}
-
-// scanToken scans pgh's contents for the token containing pos.
-func (c *completer) scanToken(contents []byte) (token.Pos, token.Token, string) {
- tok := c.snapshot.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,
- MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper, start, end),
- }
-
- 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.snapshot.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,
- MappedRange: source.NewMappedRange(c.snapshot.FileSet(), c.mapper,
- token.Pos(int(cursorComment.Slash)+start), token.Pos(int(cursorComment.Slash)+end)),
- }
- 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)
-
- // Is sel a qualified identifier?
- if id, ok := sel.X.(*ast.Ident); ok {
- if pkgName, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok {
- var pkg source.Package
- for _, imp := range c.pkg.Imports() {
- if imp.PkgPath() == pkgName.Imported().Path() {
- pkg = imp
- }
- }
- // If the package is not imported, try searching for unimported
- // completions.
- if pkg == nil && c.opts.unimported {
- if err := c.unimportedMembers(ctx, id); err != nil {
- return err
- }
- }
- c.packageMembers(pkgName.Imported(), stdScore, nil, func(cand candidate) {
- c.deepState.enqueue(cand)
- })
- return nil
- }
- }
-
- // Invariant: sel is a true selector.
- tv, ok := c.pkg.GetTypesInfo().Types[sel.X]
- if ok {
- c.methodsAndFields(tv.Type, tv.Addressable(), nil, func(cand candidate) {
- c.deepState.enqueue(cand)
- })
-
- c.addPostfixSnippetCandidates(ctx, sel)
-
- return nil
- }
-
- // Try unimported packages.
- if id, ok := sel.X.(*ast.Ident); ok && c.opts.unimported {
- if err := c.unimportedMembers(ctx, id); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (c *completer) unimportedMembers(ctx context.Context, id *ast.Ident) error {
- // Try loaded packages first. They're relevant, fast, and fully typed.
- known, err := c.snapshot.CachedImportPaths(ctx)
- if err != nil {
- return err
- }
-
- var paths []string
- for path, pkg := range known {
- if pkg.GetTypes().Name() != id.Name {
- continue
- }
- paths = append(paths, path)
- }
-
- 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]]
- })
-
- for _, path := range paths {
- pkg := known[path]
- if pkg.GetTypes().Name() != id.Name {
- continue
- }
- imp := &importInfo{
- importPath: path,
- pkg: pkg,
- }
- if imports.ImportPathToAssumedName(path) != pkg.GetTypes().Name() {
- imp.name = pkg.GetTypes().Name()
- }
- c.packageMembers(pkg.GetTypes(), unimportedScore(relevances[path]), imp, func(cand candidate) {
- c.deepState.enqueue(cand)
- })
- if len(c.items) >= unimportedMemberTarget {
- return nil
- }
- }
-
- ctx, cancel := context.WithCancel(ctx)
-
- var mu sync.Mutex
- add := func(pkgExport imports.PackageExport) {
- mu.Lock()
- defer mu.Unlock()
- if _, ok := known[pkgExport.Fix.StmtInfo.ImportPath]; ok {
- return // We got this one above.
- }
-
- // Continue with untyped proposals.
- pkg := types.NewPackage(pkgExport.Fix.StmtInfo.ImportPath, pkgExport.Fix.IdentName)
- for _, export := range pkgExport.Exports {
- 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 typ.String() == "*testing.F" && addressable {
- // is that a sufficient test? (or is more care needed?)
- if c.fuzz(typ, mset, imp, cb, c.snapshot.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),
- })
- })
-}
-
-// lexical finds completions in the lexical environment.
-func (c *completer) lexical(ctx context.Context) error {
- scopes := source.CollectScopes(c.pkg.GetTypesInfo(), c.path, c.pos)
- scopes = append(scopes, c.pkg.GetTypes().Scope(), types.Universe)
-
- var (
- builtinIota = types.Universe.Lookup("iota")
- builtinNil = types.Universe.Lookup("nil")
- // comparable is an interface that exists on the dev.typeparams Go branch.
- // Filter it out from completion results to stabilize tests.
- // TODO(rFindley) update (or remove) our handling for comparable once the
- // type parameter API has stabilized.
- builtinAny = types.Universe.Lookup("any")
- 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 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 == builtinComparable || obj == builtinAny {
- continue
- }
-
- // 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.snapshot.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.
- if _, ok := seen[pkg.Name()]; !ok && pkg != c.pkg.GetTypes() && !alreadyImports(c.file, pkg.Path()) {
- seen[pkg.Name()] = struct{}{}
- obj := types.NewPkgName(0, nil, pkg.Name(), pkg)
- 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
-}
-
-// injectInferredType manufacters candidates based on the given type.
-// 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.
- if _, named := t.(*types.Named); !named {
- // 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
-
- known, err := c.snapshot.CachedImportPaths(ctx)
- if err != nil {
- return err
- }
- var paths []string
- for path, pkg := range known {
- if !strings.HasPrefix(pkg.GetTypes().Name(), prefix) {
- continue
- }
- paths = append(paths, path)
- }
-
- 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 {
- pkg := known[path]
- if _, ok := seen[pkg.GetTypes().Name()]; ok {
- continue
- }
- imp := &importInfo{
- importPath: path,
- pkg: pkg,
- }
- if imports.ImportPathToAssumedName(path) != pkg.GetTypes().Name() {
- imp.name = pkg.GetTypes().Name()
- }
- if count >= maxUnimportedPackageNames {
- return nil
- }
- c.deepState.enqueue(candidate{
- // Pass an empty *types.Package to disable deep completions.
- obj: types.NewPkgName(0, nil, pkg.GetTypes().Name(), types.NewPackage(path, pkg.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 string) bool {
- for _, s := range f.Imports {
- if source.ImportPath(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 {
- // Otherwse, record our single assignee, even if its type is
- // not available. We use this info to downrank functions
- // with the wrong number of result values.
- inf.assignees = append(inf.assignees, c.pkg.GetTypesInfo().TypeOf(node.Lhs[i]))
- }
- }
- 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
- }
-
- if tv, ok := c.pkg.GetTypesInfo().Types[node.Fun]; ok {
- if sig, ok := tv.Type.(*types.Signature); ok {
- 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 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 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 && types.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 && types.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 !types.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 types.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 wouuld 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 {
- 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 types.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 !source.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
-}
diff --git a/internal/lsp/source/completion/deep_completion.go b/internal/lsp/source/completion/deep_completion.go
deleted file mode 100644
index a13d807d4..000000000
--- a/internal/lsp/source/completion/deep_completion.go
+++ /dev/null
@@ -1,362 +0,0 @@
-// 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 wether 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/internal/lsp/source/completion/deep_completion_test.go b/internal/lsp/source/completion/deep_completion_test.go
deleted file mode 100644
index 27009af1b..000000000
--- a/internal/lsp/source/completion/deep_completion_test.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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/internal/lsp/source/completion/definition.go b/internal/lsp/source/completion/definition.go
deleted file mode 100644
index 17b251cb0..000000000
--- a/internal/lsp/source/completion/definition.go
+++ /dev/null
@@ -1,127 +0,0 @@
-// 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/token"
- "go/types"
- "strings"
- "unicode"
- "unicode/utf8"
-
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/snippet"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-// some definitions 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, fset *token.FileSet, mapper *protocol.ColumnMapper, fh source.FileHandle) ([]CompletionItem, *Selection) {
- if _, ok := obj.(*types.Func); !ok {
- return nil, nil // not a function at all
- }
- if !strings.HasSuffix(fh.URI().Filename(), "_test.go") {
- return nil, nil
- }
-
- name := path[0].(*ast.Ident).Name
- if len(name) == 0 {
- // can't happen
- return nil, nil
- }
- pos := path[0].Pos()
- sel := &Selection{
- content: "",
- cursor: pos,
- MappedRange: source.NewMappedRange(fset, mapper, pos, pos),
- }
- var ans []CompletionItem
-
- // Always suggest TestMain, if possible
- if strings.HasPrefix("TestMain", name) {
- ans = []CompletionItem{defItem("TestMain(m *testing.M)", obj)}
- }
-
- // If a snippet is possible, suggest it
- if strings.HasPrefix("Test", name) {
- ans = append(ans, defSnippet("Test", "Xxx", "(t *testing.T)", obj))
- return ans, sel
- } else if strings.HasPrefix("Benchmark", name) {
- ans = append(ans, defSnippet("Benchmark", "Xxx", "(b *testing.B)", obj))
- return ans, sel
- } else if strings.HasPrefix("Fuzz", name) {
- ans = append(ans, defSnippet("Fuzz", "Xxx", "(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
-}
-
-func defMatches(name, pat string, path []ast.Node, arg string) string {
- idx := strings.Index(name, pat)
- if idx < 0 {
- 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 fp != nil && len(fp.List) > 0 {
- // signature already there, minimal suggestion
- return name
- }
- // suggesting signature too
- return name + arg
-}
-
-func defSnippet(prefix, placeholder, suffix string, obj types.Object) CompletionItem {
- var sn snippet.Builder
- sn.WriteText(prefix)
- if placeholder != "" {
- sn.WritePlaceholder(func(b *snippet.Builder) { b.WriteText(placeholder) })
- }
- sn.WriteText(suffix + " {\n")
- sn.WriteFinalTabstop()
- sn.WriteText("\n}")
- return CompletionItem{
- Label: prefix + placeholder + suffix,
- Detail: "tab, type the rest of the name, then tab",
- Kind: protocol.FunctionCompletion,
- Depth: 0,
- Score: 10,
- snippet: &sn,
- Documentation: prefix + " test function",
- obj: 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 parameter",
- obj: obj,
- }
-}
diff --git a/internal/lsp/source/completion/format.go b/internal/lsp/source/completion/format.go
deleted file mode 100644
index e67456911..000000000
--- a/internal/lsp/source/completion/format.go
+++ /dev/null
@@ -1,340 +0,0 @@
-// 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/ast"
- "go/doc"
- "go/types"
- "strings"
-
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/imports"
- "golang.org/x/tools/internal/lsp/debug/tag"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/snippet"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/typeparams"
- errors "golang.org/x/xerrors"
-)
-
-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 wont 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() {
- detail = source.FormatVarType(ctx, c.snapshot, c.pkg, obj, c.qf)
- }
- 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 := source.NewSignature(ctx, c.snapshot, c.pkg, sig, nil, c.qf)
- 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,
- obj: obj,
- }
- // If the user doesn't want documentation for completion items.
- if !c.opts.documentation {
- return item, nil
- }
- pos := c.snapshot.FileSet().Position(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
- }
- uri := span.URIFromPath(pos.Filename)
-
- // Find the source file of the candidate.
- pkg, err := source.FindPackageFromPos(ctx, c.snapshot, obj.Pos())
- if err != nil {
- return item, nil
- }
-
- decl, err := c.snapshot.PosToDecl(ctx, pkg, obj.Pos())
- if err != nil {
- return CompletionItem{}, err
- }
- hover, err := source.FindHoverContext(ctx, c.snapshot, pkg, obj, decl, nil)
- if err != nil {
- event.Error(ctx, "failed to find Hover", err, tag.URI.Of(uri))
- return item, nil
- }
- if c.opts.fullDocumentation {
- item.Documentation = hover.Comment.Text()
- } else {
- item.Documentation = doc.Synopsis(hover.Comment.Text())
- }
- // The desired pattern is `^// Deprecated`, but the prefix has been removed
- if strings.HasPrefix(hover.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/internal/lsp/source/completion/fuzz.go b/internal/lsp/source/completion/fuzz.go
deleted file mode 100644
index 92349ab93..000000000
--- a/internal/lsp/source/completion/fuzz.go
+++ /dev/null
@@ -1,142 +0,0 @@
-// 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/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",
- obj: nil,
- }
- 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/internal/lsp/source/completion/keywords.go b/internal/lsp/source/completion/keywords.go
deleted file mode 100644
index bbf59b022..000000000
--- a/internal/lsp/source/completion/keywords.go
+++ /dev/null
@@ -1,154 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/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/internal/lsp/source/completion/labels.go b/internal/lsp/source/completion/labels.go
deleted file mode 100644
index e4fd961e3..000000000
--- a/internal/lsp/source/completion/labels.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// 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/internal/lsp/source/completion/literal.go b/internal/lsp/source/completion/literal.go
deleted file mode 100644
index 5025f1f74..000000000
--- a/internal/lsp/source/completion/literal.go
+++ /dev/null
@@ -1,440 +0,0 @@
-// 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/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/snippet"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-// 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 "" }
- }
-
- typeName := types.TypeString(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
- }
- }
-
- switch t := literalType.Underlying().(type) {
- case *types.Struct, *types.Array, *types.Slice, *types.Map:
- c.compositeLiteral(t, 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 source.IsInterface(expType) {
- c.basicLiteral(t, 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 source.IsInterface(expType) || types.Identical(expType, literalType) {
- c.basicLiteral(t, 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(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(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 && !source.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)
- )
- for i := 0; i < sig.Params().Len(); i++ {
- var (
- p = sig.Params().At(i)
- name = p.Name()
- )
- 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".
- name = source.FormatVarType(ctx, c.snapshot, c.pkg, p, func(p *types.Package) string {
- return ""
- })
- name = abbreviateTypeName(name)
- }
- 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 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 := source.FormatVarType(ctx, c.snapshot, c.pkg, p, c.qf)
- if sig.Variadic() && i == sig.Params().Len()-1 {
- typeStr = strings.Replace(typeStr, "[]", "...", 1)
- }
- 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() != ""
-
- if resultsNeedParens {
- snip.WriteText("(")
- }
- for i := 0; i < results.Len(); i++ {
- if i > 0 {
- snip.WriteText(", ")
- }
- r := results.At(i)
- if name := r.Name(); name != "" {
- snip.WriteText(name + " ")
- }
- snip.WriteText(source.FormatVarType(ctx, c.snapshot, c.pkg, r, c.qf))
- }
- 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, typeName string, matchScore float64, edits []protocol.TextEdit) {
- snip := &snippet.Builder{}
- snip.WriteText(typeName + "{")
- // 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, typeName string, matchScore float64, edits []protocol.TextEdit) {
- // Never give type conversions like "untyped int()".
- if isUntyped(T) {
- return
- }
-
- snip := &snippet.Builder{}
- snip.WriteText(typeName + "(")
- 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(typeName string, secondArg string, matchScore float64, edits []protocol.TextEdit) {
- // Keep it simple and don't add any placeholders for optional "make()" arguments.
-
- snip := &snippet.Builder{}
- snip.WriteText("make(" + typeName)
- 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,
- })
-}
diff --git a/internal/lsp/source/completion/package.go b/internal/lsp/source/completion/package.go
deleted file mode 100644
index c7e52d718..000000000
--- a/internal/lsp/source/completion/package.go
+++ /dev/null
@@ -1,364 +0,0 @@
-// 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"
- "fmt"
- "go/ast"
- "go/parser"
- "go/scanner"
- "go/token"
- "go/types"
- "path/filepath"
- "strings"
- "unicode"
-
- "golang.org/x/tools/internal/lsp/debug"
- "golang.org/x/tools/internal/lsp/fuzzy"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-// 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, pos 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.
- pgf, err := snapshot.ParseGo(ctx, fh, source.ParseFull)
- if err != nil {
- return nil, nil, err
- }
-
- cursorSpan, err := pgf.Mapper.PointSpan(pos)
- if err != nil {
- return nil, nil, err
- }
- rng, err := cursorSpan.Range(pgf.Mapper.Converter)
- if err != nil {
- return nil, nil, err
- }
-
- surrounding, err := packageCompletionSurrounding(ctx, snapshot.FileSet(), pgf, rng.Start)
- if err != nil {
- return nil, nil, errors.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 position. A valid location
-// for package completion is above any declarations or import statements.
-func packageCompletionSurrounding(ctx context.Context, fset *token.FileSet, pgf *source.ParsedGoFile, pos token.Pos) (*Selection, error) {
- // 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.
- filename := pgf.URI.Filename()
- expr, _ := parser.ParseExprFrom(fset, filename, pgf.Src, parser.Mode(0))
- if expr == nil {
- return nil, fmt.Errorf("unparseable file (%s)", pgf.URI)
- }
- tok := fset.File(expr.Pos())
- offset, err := source.Offset(pgf.Tok, pos)
- if err != nil {
- return nil, err
- }
- if offset > tok.Size() {
- debug.Bug(ctx, "out of bounds cursor", "cursor offset (%d) out of bounds for %s (size: %d)", offset, pgf.URI, tok.Size())
- return nil, fmt.Errorf("cursor out of bounds")
- }
- cursor := tok.Pos(offset)
- m := &protocol.ColumnMapper{
- URI: pgf.URI,
- Content: pgf.Src,
- Converter: span.NewContentConverter(filename, pgf.Src),
- }
-
- // 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,
- MappedRange: source.NewMappedRange(fset, m, name.Pos(), name.End()),
- }, 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 fset.Position(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,
- MappedRange: source.NewMappedRange(fset, m, start, end),
- }, 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(fset, cursor, pgf.Src) {
- return nil, fmt.Errorf("cursor in comment")
- }
-
- // The surrounding range in this case is the cursor except for empty file,
- // in which case it's end of file - 1
- start, end := cursor, cursor
- if tok.Size() == 0 {
- start, end = tok.Pos(0)-1, tok.Pos(0)-1
- }
-
- return &Selection{
- content: "",
- cursor: cursor,
- MappedRange: source.NewMappedRange(fset, m, start, end),
- }, nil
-}
-
-func cursorInComment(fset *token.FileSet, cursor token.Pos, src []byte) bool {
- var s scanner.Scanner
- s.Init(fset.File(cursor), 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) {
- workspacePackages, err := snapshot.ActivePackages(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[string]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 _, pkg := range workspacePackages {
- if pkg.Name() == "main" || pkg.Name() == "" {
- continue
- }
- if _, ok := seenPkgs[pkg.Name()]; ok {
- continue
- }
-
- // Only add packages that are previously used in the current directory.
- var relevantPkg bool
- for _, pgf := range pkg.CompiledGoFiles() {
- if filepath.Dir(pgf.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(pkg.Name())); score > 0 {
- packages = append(packages, toCandidate(pkg.Name(), score*highScore))
- }
- seenPkgs[pkg.Name()] = struct{}{}
-
- testPkgName := pkg.Name() + "_test"
- if _, ok := seenPkgs[testPkgName]; ok || strings.HasSuffix(pkg.Name(), "_test") {
- continue
- }
- if score := float64(matcher.Score(testPkgName)); score > 0 {
- packages = append(packages, toCandidate(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(pkgName)); score > 0 {
- packages = append(packages, toCandidate(pkgName, score*lowScore))
- }
-
- testPkgName := pkgName + "_test"
- if score := float64(matcher.Score(testPkgName)); score > 0 {
- packages = append(packages, toCandidate(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) string {
- 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 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/internal/lsp/source/completion/package_test.go b/internal/lsp/source/completion/package_test.go
deleted file mode 100644
index 6436984fd..000000000
--- a/internal/lsp/source/completion/package_test.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// 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"
-
-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 string
- }{
- {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/internal/lsp/source/completion/postfix_snippets.go b/internal/lsp/source/completion/postfix_snippets.go
deleted file mode 100644
index 7ea962118..000000000
--- a/internal/lsp/source/completion/postfix_snippets.go
+++ /dev/null
@@ -1,461 +0,0 @@
-// 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/internal/event"
- "golang.org/x/tools/internal/imports"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/snippet"
- "golang.org/x/tools/internal/lsp/source"
- errors "golang.org/x/xerrors"
-)
-
-// 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: "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 "", errors.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
- if 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.snapshot.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.snapshot.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 {
- if source.ImportPath(s) == 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/internal/lsp/source/completion/printf.go b/internal/lsp/source/completion/printf.go
deleted file mode 100644
index ce74af53b..000000000
--- a/internal/lsp/source/completion/printf.go
+++ /dev/null
@@ -1,172 +0,0 @@
-// 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/precicision 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/internal/lsp/source/completion/printf_test.go b/internal/lsp/source/completion/printf_test.go
deleted file mode 100644
index 19d295b8d..000000000
--- a/internal/lsp/source/completion/printf_test.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// 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/internal/lsp/source/completion/snippet.go b/internal/lsp/source/completion/snippet.go
deleted file mode 100644
index 72c351f94..000000000
--- a/internal/lsp/source/completion/snippet.go
+++ /dev/null
@@ -1,115 +0,0 @@
-// 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/internal/lsp/snippet"
-)
-
-// structFieldSnippets 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.snapshot.FileSet()
-
- // If the cursor position is on a different line from the literal's opening brace,
- // we are in a multiline literal.
- if fset.Position(c.pos).Line != fset.Position(clInfo.cl.Lbrace).Line {
- snip.WriteText(",")
- }
-}
-
-// functionCallSnippets 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/internal/lsp/source/completion/statements.go b/internal/lsp/source/completion/statements.go
deleted file mode 100644
index 3280bb52c..000000000
--- a/internal/lsp/source/completion/statements.go
+++ /dev/null
@@ -1,360 +0,0 @@
-// 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/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/snippet"
- "golang.org/x/tools/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 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, )".
-//
-// Or
-//
-// 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.snapshot.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.obj == nil || bestItem.obj.Type() == nil {
- return
- }
-
- if _, isSlice := bestItem.obj.Type().Underlying().(*types.Slice); !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.snapshot.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 ""
- }
-
- sig := enclosingFunc.sig
- for i := 0; i < sig.Params().Len(); i++ {
- param := sig.Params().At(i)
- if param.Name() == "_" {
- continue
- }
- testingPkg, err := pkg.GetImport("testing")
- if err != nil {
- continue
- }
- tbObj := testingPkg.GetTypes().Scope().Lookup("TB")
- if tbObj == nil {
- continue
- }
- iface, ok := tbObj.Type().Underlying().(*types.Interface)
- if !ok {
- continue
- }
- if !types.Implements(param.Type(), iface) {
- continue
- }
- return param.Name()
- }
-
- return ""
-}
diff --git a/internal/lsp/source/completion/util.go b/internal/lsp/source/completion/util.go
deleted file mode 100644
index 505c7e256..000000000
--- a/internal/lsp/source/completion/util.go
+++ /dev/null
@@ -1,326 +0,0 @@
-// 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/internal/lsp/diff"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
-)
-
-// 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 map[*types.Struct]bool
-
- var visit func(T types.Type)
- visit = func(T types.Type) {
- if T, ok := source.Deref(T).Underlying().(*types.Struct); ok {
- if seen[T] {
- return
- }
-
- for i := 0; i < T.NumFields(); i++ {
- f := T.Field(i)
- fn(f)
- if f.Anonymous() {
- if seen == nil {
- // Lazily create "seen" since it is only needed for
- // embedded structs.
- seen = make(map[*types.Struct]bool)
- }
- seen[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
-}
-
-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) {
- rng := source.NewMappedRange(c.snapshot.FileSet(), c.mapper, from, to)
- spn, err := rng.Span()
- if err != nil {
- return nil, err
- }
- return source.ToProtocolEdits(c.mapper, []diff.TextEdit{{
- Span: spn,
- NewText: newText,
- }})
-}
diff --git a/internal/lsp/source/completion/util_test.go b/internal/lsp/source/completion/util_test.go
deleted file mode 100644
index c94d279fb..000000000
--- a/internal/lsp/source/completion/util_test.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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)
- }
- }
-}