Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.

textDocument/xdefinition: do not return "nil" AND no error. #103

Merged
merged 1 commit into from
Jan 23, 2017

Conversation

emidoots
Copy link
Member

The return type signature of handleDefinition is ([]lsp.Location, error). If nil, nil
is returned then (*LangHandler).Handle also returns result interface{} as nil. When
the result of a jsonrpc2 handler is nil, jsonrpc2.HandlerWithError converts it into a struct
which is then encoded as a JSON object instead of an array. When decoding, this results in:

json: cannot unmarshal object into Go value of type []lspext.SymbolLocationInformation

Which is (obviously) bad because that looks like an error instead of 'no results'.

According to that jsonrpc2 implementation (linked above):

// isNilValue tests if an interface is empty, because an empty interface does
// not encode any information, we can't encode it in JSON so that the proxy
// knows it's a response, not a request.
func isNilValue(resp interface{}) bool {

This makes it unclear to me whether or not the conversion into a struct is
truly correct or not. I'm open to other ideas on how to resolve this issue.

@emidoots emidoots requested review from sqs and keegancsmith January 22, 2017 05:57
@emidoots
Copy link
Member Author

@sqs I've tagged you in this PR because you wrote that code and it's very unclear from the docs why it's doing that. Please let me know what you think?

The return type signature of `handleDefinition` is `([]lsp.Location, error)`. If `nil, nil`
is returned then ` (*LangHandler).Handle` also returns `result interface{}` as `nil`. When
the result of a jsonrpc2 handler is nil, `jsonrpc2.HandlerWithError` [converts it into a struct](https://sourcegraph.com/github.com/sourcegraph/jsonrpc2/-/blob/handler_with_error.go#L25-28)
which is then encoded as a JSON object instead of an array. When decoding, this results in:

```
json: cannot unmarshal object into Go value of type []lspext.SymbolLocationInformation
```

Which is (obviously) bad because that looks like an error instead of 'no results'.

According to that jsonrpc2 implementation (linked above):

```
// isNilValue tests if an interface is empty, because an empty interface does
// not encode any information, we can't encode it in JSON so that the proxy
// knows it's a response, not a request.
func isNilValue(resp interface{}) bool {
```

This makes it unclear to me whether or not the conversion into a struct is
truly correct or not. I'm open to other ideas on how to resolve this issue.
@sqs
Copy link
Member

sqs commented Jan 22, 2017

Thanks for digging into this.

A response of {"id":123,"result":{},"jsonrpc":"2.0"} is a correct "no result found" response for some LSP methods, such as textDocument/definition. It was coincidental (well, somewhat of a convenience that no longer works) that return nil, nil mapped to that JSON-RPC response. I had return nil, nil to indicate "there is no definition found but there is also no unexpected error", as "no definition found" is not really considered an error at the LSP level. This was a fragile hack.

I think that if handleDefinition encounters "there is no definition found but there is also no unexpected error", it should return a sentinel error value like ErrNoDefinition that is translated into {"id":123,"result":{},"jsonrpc":"2.0"}, not a JSON-RPC error ({"id":123,"error":{...whatever...},"jsonrpc":"2.0"}), by the response handler. That means the handler should basically do if err == ErrNoDefinition { return struct{}{}, nil } in textDocument/definition.

This makes it unclear to me whether or not the conversion into a struct is truly correct or not.

It is not. That is code I wrote and I see it is incorrect. An JSON-RPC 2.0 response's result can be any JSON type (http://www.jsonrpc.org/specification#response_object, note result has no constraints on type, unlike params in section 4.2). The jsonrpc2.HandlerWithError should return {..., "result":null} if the result interface{} return value is nil, and an LSP handler should never return nil, nil (because afaik null does not appear in any methods' specification as a valid value for the result field).

@@ -51,7 +51,7 @@ func (h *LangHandler) handleXDefinition(ctx context.Context, conn JSONRPC2Conn,
//
// TODO(sqs): find a way to actually emit builtin locations
// (pointing to builtin/builtin.go).
return nil, nil
return []symbolLocationInformation{}, nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes are both correct.

@sqs
Copy link
Member

sqs commented Jan 22, 2017

I think that if handleDefinition encounters

Nevermind about this paragraph. I see that your change accomplishes the same result with less code (I had forgotten handleDefinition directly returns the LSP result).

@felixfbecker
Copy link

textDocument/xdefintition must return an empty array if no result was found. Neither null or an empty object {} are valid return types. If there was an error, result must be omitted from the JSON, null is not valid either.

@keegancsmith
Copy link
Member

@felixfbecker FYI in go by default an empty slice (ie empty list) is nil.

var foo []string // foo is nil at this point
foo = append(foo, "hello world") // now foo is not nil

// instead you can do this, but this isn't idiomatic
foo := []string{}

@emidoots emidoots merged commit ded64a5 into master Jan 23, 2017
@emidoots emidoots deleted the sg/fix-500 branch January 23, 2017 02:50
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants