merlin
merlin copied to clipboard
hover over type with mutual recursion fails silently
Hover over a type using a mutually recursive type crashes ocaml-lsp silently (ocaml-lsp usually shows thrown exceptions, but not in this case). Say, we have
type t = s
and s = string
hovering over s crashes ocaml-lsp with no stack trace, so possibly segfaulting 🤷♂️
Note: the problem is discovered using ocaml-lsp, so that might not be merlin's fault; sorry for the noise if that's the case.
I don't know what hovering is supposed to trigger in lsp; two reasonable guesses would be "show the type" or "show occurences", both work for me on your example.
Thanks for a prompt response!
I was trying to determine all cases when hover-over-type crashes ocaml-lsp and stumbled upon this case:
Running the following command results in merlin raising stack overflow with a quite long stack trace
ocamlmerlin single type-enclosing -position 1:14 -verbosity 1 -\
> -filename ./test.ml < ./test.ml | jq
with the file test.ml with no .merlin nearby.
type t = {i : a}
and a = int
Defining the same type in ocaml and utop REPLs, #show t works fine and outputs type nonrec t = { i : a; }.
Should that be a bug on the merlin side?
(The Merlin toolkit version v3.4.2, for Ocaml 4.11.1)
Example I gave in the issue description fails with a stack overflow for me when I run command
ocamlmerlin single type-enclosing -position 1:10 -verbosity 1 -\
> -filename ./test.ml < ./test.ml | jq
"hovering over" s in
type t = s and s = string;;
@trefis were you running the same merlin command when you said it was working well for you?
Btw, ocaml-lsp uses type enclosing query command for hovering feature.
Strangely, for this case, utop on #show t returns type nonrec t = t, which is wrong (?), but ocaml returns type nonrec t = s. Utop's behavior seems to hint why we could be getting a stack overflow.
I had the verbosity flag set at 0 (which is the default), but indeed when you increase the value, then you get a stack overflow.
Thanks for the report, we'll look into it.
Btw: is -verbosity 1 the default in lsp? That seems like it could be a mistake.
Btw: is -verbosity 1 the default in lsp?
Yes, see this line
That seems like it could be a mistake.
I don't fully understand what verbosity does, but sometimes verbosity 0 can return strange types. Fro example, for a file t.ml with contents:
type 'a t = 'a
we can run type-enclosing and see a strange type type 'a t = 'b t as the head of the list value
❯ ocamlmerlin single type-enclosing -position 1:9 -verbosity 0 -\
> -filename ./t.ml < ./t.ml | jq
{
"class": "return",
"value": [
{
"start": {
"line": 1,
"col": 8
},
"end": {
"line": 1,
"col": 9
},
"type": "type 'a t = 'b t",
"tail": "no"
},
{
"start": {
"line": 1,
"col": 0
},
"end": {
"line": 1,
"col": 14
},
"type": "type 'a t = 'a",
"tail": "no"
}
],
"notifications": [],
"timing": {
"clock": 4,
"cpu": 4,
"query": 1,
"pp": 0,
"reader": 2,
"ppx": 0,
"typer": 2,
"error": 0
}
}
OCaml-LSP shows the head of this list on hover, i.e., type 'a t = 'b t, which is a puzzling type to show a user. This may've been one of the reasons OCaml-LSP uses verbosity 1. cc @rgrinberg
Also strangely, merlin doesn't fail on hover over t in type 'a t = 'a but ocaml-lsp does, so merlin stack overflowing may not be the culprit behind lsp crashes 🤷♂️
We use the verbosity flag to control the expansion of a type. With a value of 0 we just print the type of the node, which might be something like float something. With a value of 1 we will try to expand that definition, so if we have type 'a something = 'a * int then after expansion you'd get float * int.
This is mostly useful for types like this one.
Apart from us trying to figure out what's going wrong on your example when trying to expand the type, something which would be useful would be for you to open a PR adding tests (under tests/test-dirs/type-enclosing) showing when a verbosity of 0 produces weird results.
Btw: is -verbosity 1 the default in lsp? That seems like it could be a mistake.
Yes, I don't see why it it's 1. I'll revert back to 0.