gopls/internal/lsp/source: fix panic in quickParse Fix a panic in quickParse resulting from a malformed synthetic selector node created during completion. Add a completion test in the new marker framework. Fixes golang/go#59096 Change-Id: Icc3c3cec3084fb76aaecf88574bbd5cbee2a8afa Reviewed-on: https://go-review.googlesource.com/c/tools/+/477255 gopls-CI: kokoro <noreply+kokoro@google.com> Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/gopls/internal/lsp/regtest/marker.go b/gopls/internal/lsp/regtest/marker.go index fb7454d..8b4c61e 100644 --- a/gopls/internal/lsp/regtest/marker.go +++ b/gopls/internal/lsp/regtest/marker.go
@@ -22,6 +22,7 @@ "strings" "testing" + "github.com/google/go-cmp/cmp" "golang.org/x/tools/go/expect" "golang.org/x/tools/gopls/internal/hooks" "golang.org/x/tools/gopls/internal/lsp/cache" @@ -115,6 +116,9 @@ // // The following markers are supported within marker tests: // +// - complete(location, ...labels): specifies expected completion results at +// the given location. +// // - diag(location, regexp): specifies an expected diagnostic matching the // given regexp at the given location. The test runner requires // a 1:1 correspondence between observed diagnostics and diag annotations @@ -455,6 +459,7 @@ // Marker funcs should not mutate the test environment (e.g. via opening files // or applying edits in the editor). var markerFuncs = map[string]markerFunc{ + "complete": makeMarkerFunc(completeMarker), "def": makeMarkerFunc(defMarker), "diag": makeMarkerFunc(diagMarker), "hover": makeMarkerFunc(hoverMarker), @@ -1091,6 +1096,26 @@ // ---- marker functions ---- +// completeMarker implements the @complete marker, running +// textDocument/completion at the given src location and asserting that the +// results match the expected results. +// +// TODO(rfindley): for now, this is just a quick check against the expected +// completion labels. We could do more by assembling richer completion items, +// as is done in the old marker tests. Does that add value? If so, perhaps we +// should support a variant form of the argument, labelOrItem, which allows the +// string form or item form. +func completeMarker(mark marker, src protocol.Location, want ...string) { + list := mark.run.env.Completion(src) + var got []string + for _, item := range list.Items { + got = append(got, item.Label) + } + if diff := cmp.Diff(want, got); diff != "" { + mark.errorf("Completion(...) returned unexpect results (-want +got):\n%s", diff) + } +} + // defMarker implements the @godef marker, running textDocument/definition at // the given src location and asserting that there is exactly one resulting // location, matching dst.
diff --git a/gopls/internal/lsp/source/completion/completion.go b/gopls/internal/lsp/source/completion/completion.go index 315541e..e95c9eb 100644 --- a/gopls/internal/lsp/source/completion/completion.go +++ b/gopls/internal/lsp/source/completion/completion.go
@@ -658,7 +658,11 @@ // recv.‸(arg) case *ast.TypeAssertExpr: // Create a fake selector expression. - return c.selector(ctx, &ast.SelectorExpr{X: n.X}) + // + // The name "_" is the convention used by go/parser to represent phantom + // selectors. + sel := &ast.Ident{NamePos: n.X.End() + token.Pos(len(".")), Name: "_"} + return c.selector(ctx, &ast.SelectorExpr{X: n.X, Sel: sel}) case *ast.SelectorExpr: return c.selector(ctx, n) // At the file scope, only keywords are allowed.
diff --git a/gopls/internal/regtest/marker/testdata/completion/issue59096.txt b/gopls/internal/regtest/marker/testdata/completion/issue59096.txt new file mode 100644 index 0000000..44bb10a --- /dev/null +++ b/gopls/internal/regtest/marker/testdata/completion/issue59096.txt
@@ -0,0 +1,18 @@ +This test exercises the panic in golang/go#59096: completing at a syntactic +type-assert expression was panicking because gopls was translating it into +a (malformed) selector expr. + +-- go.mod -- +module example.com + +-- a/a.go -- +package a + +func _() { + b.(foo) //@complete(re"b.()", "B"), diag("b", re"(undefined|undeclared name): b") +} + +-- b/b.go -- +package b + +const B = 0