merlin icon indicating copy to clipboard operation
merlin copied to clipboard

hover over type with mutual recursion fails silently

Open ulugbekna opened this issue 4 years ago • 8 comments

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.

ulugbekna avatar Dec 04 '20 10:12 ulugbekna

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.

trefis avatar Dec 04 '20 11:12 trefis

Thanks for a prompt response!

ulugbekna avatar Dec 08 '20 07:12 ulugbekna

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)

ulugbekna avatar Jan 05 '21 20:01 ulugbekna

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.

ulugbekna avatar Jan 05 '21 20:01 ulugbekna

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.

trefis avatar Jan 06 '21 08:01 trefis

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 🤷‍♂️

ulugbekna avatar Jan 06 '21 10:01 ulugbekna

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.

trefis avatar Jan 06 '21 10:01 trefis

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.

rgrinberg avatar May 03 '21 19:05 rgrinberg