mirror of
https://github.com/golang/go
synced 2024-11-18 20:44:45 -07:00
internal/lsp/source: propose exports for unimported packages
When a user completes rand.<>, propose rand.Seed (from math/rand) and rand.Prime (from crypto/rand), etc. Because we don't necessarily have type checking information for unimported packages, I had to add shortcut cases to a number of functions around the completion code. Better suggestions welcome. Change-Id: I7822dc75c86b24156963e7bdd959443f4f2748b1 Reviewed-on: https://go-review.googlesource.com/c/tools/+/204819 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org> Reviewed-by: Muir Manders <muir@mnd.rs>
This commit is contained in:
parent
8266eea4ea
commit
88c5938121
@ -577,11 +577,24 @@ func (c *completer) selector(sel *ast.SelectorExpr) error {
|
|||||||
|
|
||||||
// Invariant: sel is a true selector.
|
// Invariant: sel is a true selector.
|
||||||
tv, ok := c.pkg.GetTypesInfo().Types[sel.X]
|
tv, ok := c.pkg.GetTypesInfo().Types[sel.X]
|
||||||
if !ok {
|
if ok {
|
||||||
return errors.Errorf("cannot resolve %s", sel.X)
|
return c.methodsAndFields(tv.Type, tv.Addressable(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.methodsAndFields(tv.Type, tv.Addressable(), nil)
|
// Try unimported packages.
|
||||||
|
if id, ok := sel.X.(*ast.Ident); ok {
|
||||||
|
pkgExports, err := PackageExports(c.ctx, c.view, id.Name, c.filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, pkgExport := range pkgExports {
|
||||||
|
pkg := types.NewPackage(pkgExport.Fix.StmtInfo.ImportPath, pkgExport.Fix.IdentName)
|
||||||
|
for _, export := range pkgExport.Exports {
|
||||||
|
c.found(types.NewVar(0, pkg, export, nil), 0.07, &pkgExport.Fix.StmtInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *completer) packageMembers(pkg *types.Package, imp *imports.ImportInfo) {
|
func (c *completer) packageMembers(pkg *types.Package, imp *imports.ImportInfo) {
|
||||||
@ -1347,6 +1360,9 @@ func (c *completer) matchingCandidate(cand *candidate) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
objType := cand.obj.Type()
|
objType := cand.obj.Type()
|
||||||
|
if objType == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Default to invoking *types.Func candidates. This is so function
|
// Default to invoking *types.Func candidates. This is so function
|
||||||
// completions in an empty statement (or other cases with no expected type)
|
// completions in an empty statement (or other cases with no expected type)
|
||||||
|
@ -39,6 +39,9 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
|
|||||||
snip *snippet.Builder
|
snip *snippet.Builder
|
||||||
protocolEdits []protocol.TextEdit
|
protocolEdits []protocol.TextEdit
|
||||||
)
|
)
|
||||||
|
if obj.Type() == nil {
|
||||||
|
detail = ""
|
||||||
|
}
|
||||||
|
|
||||||
// expandFuncCall mutates the completion label, detail, and snippet
|
// expandFuncCall mutates the completion label, detail, and snippet
|
||||||
// to that of an invocation of sig.
|
// to that of an invocation of sig.
|
||||||
@ -64,6 +67,9 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
|
|||||||
} else {
|
} else {
|
||||||
kind = protocol.VariableCompletion
|
kind = protocol.VariableCompletion
|
||||||
}
|
}
|
||||||
|
if obj.Type() == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
if sig, ok := obj.Type().Underlying().(*types.Signature); ok && cand.expandFuncCall {
|
if sig, ok := obj.Type().Underlying().(*types.Signature); ok && cand.expandFuncCall {
|
||||||
expandFuncCall(sig)
|
expandFuncCall(sig)
|
||||||
@ -91,11 +97,20 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
|
|||||||
|
|
||||||
// If this candidate needs an additional import statement,
|
// If this candidate needs an additional import statement,
|
||||||
// add the additional text edits needed.
|
// add the additional text edits needed.
|
||||||
|
if cand.imp != nil {
|
||||||
addlEdits, err := c.importEdits(cand.imp)
|
addlEdits, err := c.importEdits(cand.imp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CompletionItem{}, err
|
return CompletionItem{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
protocolEdits = append(protocolEdits, addlEdits...)
|
protocolEdits = append(protocolEdits, addlEdits...)
|
||||||
|
if kind != protocol.ModuleCompletion {
|
||||||
|
if detail != "" {
|
||||||
|
detail += " "
|
||||||
|
}
|
||||||
|
detail += fmt.Sprintf("(from %q)", cand.imp.ImportPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
detail = strings.TrimPrefix(detail, "untyped ")
|
detail = strings.TrimPrefix(detail, "untyped ")
|
||||||
item := CompletionItem{
|
item := CompletionItem{
|
||||||
@ -146,7 +161,7 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
|
|||||||
return item, nil
|
return item, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// importEdits produces the text eddits necessary to add the given import to the current file.
|
// importEdits produces the text edits necessary to add the given import to the current file.
|
||||||
func (c *completer) importEdits(imp *imports.ImportInfo) ([]protocol.TextEdit, error) {
|
func (c *completer) importEdits(imp *imports.ImportInfo) ([]protocol.TextEdit, error) {
|
||||||
if imp == nil {
|
if imp == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -158,6 +158,10 @@ func (c *completer) deepSearch(obj types.Object, imp *imports.ImportInfo) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if obj.Type() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Don't search embedded fields because they were already included in their
|
// Don't search embedded fields because they were already included in their
|
||||||
// parent's fields.
|
// parent's fields.
|
||||||
if v, ok := obj.(*types.Var); ok && v.Embedded() {
|
if v, ok := obj.(*types.Var); ok && v.Embedded() {
|
||||||
|
@ -240,11 +240,8 @@ func AllImportsFixes(ctx context.Context, view View, f File) (edits []protocol.T
|
|||||||
return edits, editsPerFix, nil
|
return edits, editsPerFix, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllImportsFixes formats f for each possible fix to the imports.
|
// CandidateImports returns every import that could be added to filename.
|
||||||
// In addition to returning the result of applying all edits,
|
func CandidateImports(ctx context.Context, view View, filename string) ([]imports.ImportFix, error) {
|
||||||
// it returns a list of fixes that could be applied to the file, with the
|
|
||||||
// corresponding TextEdits that would be needed to apply that fix.
|
|
||||||
func CandidateImports(ctx context.Context, view View, filename string) (pkgs []imports.ImportFix, err error) {
|
|
||||||
ctx, done := trace.StartSpan(ctx, "source.CandidateImports")
|
ctx, done := trace.StartSpan(ctx, "source.CandidateImports")
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -257,16 +254,41 @@ func CandidateImports(ctx context.Context, view View, filename string) (pkgs []i
|
|||||||
TabIndent: true,
|
TabIndent: true,
|
||||||
TabWidth: 8,
|
TabWidth: 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var imps []imports.ImportFix
|
||||||
importFn := func(opts *imports.Options) error {
|
importFn := func(opts *imports.Options) error {
|
||||||
pkgs, err = imports.GetAllCandidates(filename, opts)
|
var err error
|
||||||
|
imps, err = imports.GetAllCandidates(filename, opts)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = view.RunProcessEnvFunc(ctx, importFn, options)
|
err := view.RunProcessEnvFunc(ctx, importFn, options)
|
||||||
if err != nil {
|
return imps, err
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgs, nil
|
// PackageExports returns all the packages named pkg that could be imported by
|
||||||
|
// filename, and their exports.
|
||||||
|
func PackageExports(ctx context.Context, view View, pkg, filename string) ([]imports.PackageExport, error) {
|
||||||
|
ctx, done := trace.StartSpan(ctx, "source.PackageExports")
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
options := &imports.Options{
|
||||||
|
// Defaults.
|
||||||
|
AllErrors: true,
|
||||||
|
Comments: true,
|
||||||
|
Fragment: true,
|
||||||
|
FormatOnly: false,
|
||||||
|
TabIndent: true,
|
||||||
|
TabWidth: 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkgs []imports.PackageExport
|
||||||
|
importFn := func(opts *imports.Options) error {
|
||||||
|
var err error
|
||||||
|
pkgs, err = imports.GetPackageExports(pkg, filename, opts)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err := view.RunProcessEnvFunc(ctx, importFn, options)
|
||||||
|
return pkgs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasParseErrors returns true if the given file has parse errors.
|
// hasParseErrors returns true if the given file has parse errors.
|
||||||
|
2
internal/lsp/testdata/summary.txt.golden
vendored
2
internal/lsp/testdata/summary.txt.golden
vendored
@ -1,7 +1,7 @@
|
|||||||
-- summary --
|
-- summary --
|
||||||
CompletionsCount = 213
|
CompletionsCount = 213
|
||||||
CompletionSnippetCount = 40
|
CompletionSnippetCount = 40
|
||||||
UnimportedCompletionsCount = 1
|
UnimportedCompletionsCount = 2
|
||||||
DeepCompletionsCount = 5
|
DeepCompletionsCount = 5
|
||||||
FuzzyCompletionsCount = 7
|
FuzzyCompletionsCount = 7
|
||||||
RankedCompletionsCount = 8
|
RankedCompletionsCount = 8
|
||||||
|
@ -2,6 +2,7 @@ package unimported
|
|||||||
|
|
||||||
func _() {
|
func _() {
|
||||||
//@unimported("", bytes, context, cryptoslashrand, time, unsafe, externalpackage)
|
//@unimported("", bytes, context, cryptoslashrand, time, unsafe, externalpackage)
|
||||||
|
bytes. //@unimported(" /", bytesbuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create markers for unimported std lib packages. Only for use by this test.
|
// Create markers for unimported std lib packages. Only for use by this test.
|
||||||
@ -11,3 +12,5 @@ func _() {
|
|||||||
/* time */ //@item(time, "time", "\"time\"", "package")
|
/* time */ //@item(time, "time", "\"time\"", "package")
|
||||||
/* unsafe */ //@item(unsafe, "unsafe", "\"unsafe\"", "package")
|
/* unsafe */ //@item(unsafe, "unsafe", "\"unsafe\"", "package")
|
||||||
/* pkg */ //@item(externalpackage, "pkg", "\"example.com/extramodule/pkg\"", "package" )
|
/* pkg */ //@item(externalpackage, "pkg", "\"example.com/extramodule/pkg\"", "package" )
|
||||||
|
|
||||||
|
/* bytes.Buffer */ //@item(bytesbuffer, "Buffer", "(from \"bytes\")", "var" )
|
@ -19,7 +19,7 @@ func ToProtocolCompletionItems(items []source.CompletionItem) []protocol.Complet
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToProtocolCompletionItem(item source.CompletionItem) protocol.CompletionItem {
|
func ToProtocolCompletionItem(item source.CompletionItem) protocol.CompletionItem {
|
||||||
return protocol.CompletionItem{
|
pItem := protocol.CompletionItem{
|
||||||
Label: item.Label,
|
Label: item.Label,
|
||||||
Kind: item.Kind,
|
Kind: item.Kind,
|
||||||
Detail: item.Detail,
|
Detail: item.Detail,
|
||||||
@ -29,6 +29,10 @@ func ToProtocolCompletionItem(item source.CompletionItem) protocol.CompletionIte
|
|||||||
NewText: item.Snippet(),
|
NewText: item.Snippet(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if pItem.InsertText == "" {
|
||||||
|
pItem.InsertText = pItem.Label
|
||||||
|
}
|
||||||
|
return pItem
|
||||||
}
|
}
|
||||||
|
|
||||||
func FilterBuiltins(items []protocol.CompletionItem) []protocol.CompletionItem {
|
func FilterBuiltins(items []protocol.CompletionItem) []protocol.CompletionItem {
|
||||||
|
Loading…
Reference in New Issue
Block a user