Skip to content

Commit 84d76fe

Browse files
committed
internal/lsp: fix unimported completions with -mod=readonly
When -mod=readonly and GOPROXY=off are set, the newly imported package is not type-checked with the new import until it is reflected in the go.mod file. In such cases, we can continue treating the package as unimported and get symbols through unimported completions. Fixes golang/go#43339 Change-Id: I864c2c6738b537093c0670a266e9030af33f2d36 Reviewed-on: https://go-review.googlesource.com/c/tools/+/280095 Trust: Rebecca Stambler <rstambler@golang.org> Run-TryBot: Rebecca Stambler <rstambler@golang.org> Reviewed-by: Robert Findley <rfindley@google.com> gopls-CI: kokoro <noreply+kokoro@google.com>
1 parent 0661ca7 commit 84d76fe

File tree

4 files changed

+96
-0
lines changed

4 files changed

+96
-0
lines changed

gopls/internal/regtest/completion_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,60 @@ func compareCompletionResults(want []string, gotItems []protocol.CompletionItem)
213213

214214
return ""
215215
}
216+
217+
func TestUnimportedCompletion(t *testing.T) {
218+
testenv.NeedsGo1Point(t, 14)
219+
220+
const mod = `
221+
-- go.mod --
222+
module mod.com
223+
224+
go 1.12
225+
-- main.go --
226+
package main
227+
228+
func main() {
229+
_ = blah
230+
}
231+
`
232+
withOptions(
233+
ProxyFiles(proxy),
234+
).run(t, mod, func(t *testing.T, env *Env) {
235+
// Explicitly download example.com so it's added to the module cache
236+
// and offered as an unimported completion.
237+
env.RunGoCommand("get", "example.com@v1.2.3")
238+
env.RunGoCommand("mod", "tidy")
239+
240+
// Trigger unimported completions for the example.com/blah package.
241+
env.OpenFile("main.go")
242+
pos := env.RegexpSearch("main.go", "ah")
243+
completions := env.Completion("main.go", pos)
244+
if len(completions.Items) == 0 {
245+
t.Fatalf("no completion items")
246+
}
247+
env.AcceptCompletion("main.go", pos, completions.Items[0])
248+
249+
// Trigger completions once again for the blah.<> selector.
250+
env.RegexpReplace("main.go", "_ = blah", "_ = blah.")
251+
env.Await(
252+
CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChange), 2),
253+
)
254+
pos = env.RegexpSearch("main.go", "\n}")
255+
completions = env.Completion("main.go", pos)
256+
if len(completions.Items) != 1 {
257+
t.Fatalf("expected 1 completion item, got %v", len(completions.Items))
258+
}
259+
item := completions.Items[0]
260+
if item.Label != "Name" {
261+
t.Fatalf("expected completion item blah.Name, got %v", item.Label)
262+
}
263+
env.AcceptCompletion("main.go", pos, item)
264+
265+
// Await the diagnostics to add example.com/blah to the go.mod file.
266+
env.SaveBufferWithoutActions("main.go")
267+
env.Await(
268+
env.DiagnosticAtRegexp("go.mod", "module mod.com"),
269+
env.DiagnosticAtRegexp("main.go", `"example.com/blah"`),
270+
)
271+
})
272+
}

gopls/internal/regtest/wrappers.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,15 @@ func (e *Env) Completion(path string, pos fake.Pos) *protocol.CompletionList {
331331
return completions
332332
}
333333

334+
// AcceptCompletion accepts a completion for the given item at the given
335+
// position.
336+
func (e *Env) AcceptCompletion(path string, pos fake.Pos, item protocol.CompletionItem) {
337+
e.T.Helper()
338+
if err := e.Editor.AcceptCompletion(e.Ctx, path, pos, item); err != nil {
339+
e.T.Fatal(err)
340+
}
341+
}
342+
334343
// CodeAction calls testDocument/codeAction for the given path, and calls
335344
// t.Fatal if there are errors.
336345
func (e *Env) CodeAction(path string) []protocol.CodeAction {

internal/lsp/fake/editor.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,23 @@ func (e *Editor) Completion(ctx context.Context, path string, pos Pos) (*protoco
907907
return completions, nil
908908
}
909909

910+
// AcceptCompletion accepts a completion for the given item at the given
911+
// position.
912+
func (e *Editor) AcceptCompletion(ctx context.Context, path string, pos Pos, item protocol.CompletionItem) error {
913+
if e.Server == nil {
914+
return nil
915+
}
916+
e.mu.Lock()
917+
defer e.mu.Unlock()
918+
_, ok := e.buffers[path]
919+
if !ok {
920+
return fmt.Errorf("buffer %q is not open", path)
921+
}
922+
return e.editBufferLocked(ctx, path, convertEdits(append([]protocol.TextEdit{
923+
*item.TextEdit,
924+
}, item.AdditionalTextEdits...)))
925+
}
926+
910927
// References executes a reference request on the server.
911928
func (e *Editor) References(ctx context.Context, path string, pos Pos) ([]protocol.Location, error) {
912929
if e.Server == nil {

internal/lsp/source/completion/completion.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,19 @@ func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error {
10701070
// Is sel a qualified identifier?
10711071
if id, ok := sel.X.(*ast.Ident); ok {
10721072
if pkgName, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok {
1073+
var pkg source.Package
1074+
for _, imp := range c.pkg.Imports() {
1075+
if imp.PkgPath() == pkgName.Imported().Path() {
1076+
pkg = imp
1077+
}
1078+
}
1079+
// If the package is not imported, try searching for unimported
1080+
// completions.
1081+
if pkg == nil && c.opts.unimported {
1082+
if err := c.unimportedMembers(ctx, id); err != nil {
1083+
return err
1084+
}
1085+
}
10731086
candidates := c.packageMembers(pkgName.Imported(), stdScore, nil)
10741087
for _, cand := range candidates {
10751088
c.deepState.enqueue(cand)

0 commit comments

Comments
 (0)