| // 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/token" |
| "go/types" |
| "strings" |
| "unicode" |
| |
| "golang.org/x/tools/internal/event" |
| "golang.org/x/tools/internal/lsp/diff" |
| "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) { |
| 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(ctx, 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.takeAddress { |
| 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 := prependEdit(c.snapshot.FileSet(), c.mapper, sel, "&") |
| 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.takeAddress && 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.takeAddress && score > 0 && !source.IsInterface(expType) { |
| switch t := literalType.Underlying().(type) { |
| case *types.Signature: |
| c.functionLiteral(ctx, t, float64(score)) |
| } |
| } |
| } |
| |
| // prependEdit produces text edits that preprend the specified prefix |
| // to the specified node. |
| func prependEdit(fset *token.FileSet, m *protocol.ColumnMapper, node ast.Node, prefix string) ([]protocol.TextEdit, error) { |
| rng := source.NewMappedRange(fset, m, node.Pos(), node.Pos()) |
| spn, err := rng.Span() |
| if err != nil { |
| return nil, err |
| } |
| return source.ToProtocolEdits(m, []diff.TextEdit{{ |
| Span: spn, |
| NewText: prefix, |
| }}) |
| } |
| |
| // 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, c.file, 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, c.file, 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, c.file, 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, |
| }) |
| } |
| |
| // 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) |
| }) |
| |
| 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) { |
| 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, |
| }) |
| } |