haskell-vim-now / ghcmod-vim: Resolving 'ghcmod#command#type Cannot guess type' errors

02 Jul 2017, by Pang Yan Han

NOTE: This post features a very hacky way to work around things which will likely never be merged into the ghcmod-vim project. It is just a place for me to document what has worked for myself in case I encounter the same issue again. This also assumes you are primarily using Stack for your Haskell projects.

Introduction / Problem Statement

Yesterday afternoon, I took some time to install haskell-vim-now. Among the list of features I saw on the README, what caught my attention was its ability to show the type of an expression in Haskell source code by typing ,ht in normal mode. So I tried it on a Haskell file and I see the following text in red at the status line:

ghcmod#command#type: Cannot guess type

It was pretty devastating for me to see this. After all, the installation took closer to an hour than 10 minutes. This morning I decided to do more googling and figured out the reason, which is stated right here: https://github.com/DanielG/ghc-mod/wiki#most-common-stack-related-issue

It turns out that haskell-vim-now uses ghcmod-vim which in turn calls ghc-mod to retrieve the type of the expression under our vim cursor when we press ,ht. But because I was using Stack, the GHC and the libraries I was using to compile the Haskell source code is different from the GHC that ghcmod-vim was using to infer the types.

Here’s the output I got:

$ ghc-mod --version
ghc-mod version 5.7.0.0 compiled by GHC 8.0.2

versus

$ stack exec ghc -- --version
The Glorious Glasgow Haskell Compilation System, version 7.10.3

Notice the difference in the GHC versions.

The workaround stated in https://github.com/DanielG/ghc-mod/wiki#most-common-stack-related-issue only works to a certain extent for myself (will be explained later). Below, I present a step by step solution to solve the problem.

The solution

To set things straight, the ghc-mod invoked by the ghc-mod --version command above is installed in ${HOME}/.local/bin by haskell-vim-now. Other than the fact that it may be using a different GHC version from what your Haskell project uses, it may also be missing the libraries needed by your project. What we want is to

  1. install ghc-mod inside our project
  2. let ghcmod-vim invoke the ghc-mod that is installed inside our project

Step 1. Install ghc-mod inside your project

This assumes that your Haskell source code is inside its own Stack project (created by stack new). If so, cd into the project’s directory and run:

stack install ghc-mod

This step is essentially the same as the workaround given in https://github.com/DanielG/ghc-mod/wiki#most-common-stack-related-issue

Step 2. Modify the ghcmod-vim plugin source code to use the correct ghc-mod

haskell-vim-now installs the ghcmod-vim plugin in ${HOME}/.vim/bundle/ghcmod-vim. I figured out that at some point it has to invoke the ghc-mod command. We just need to change appropriate occurrences of ghc-mod to stack exec ghc-mod so that ghcmod-vim will use the ghc-mod we installed in Step 1. A little browsing around and git grep reveals the following points where code needs to be changed:

NOTE: The following required source code changes are based on commit 1d192d13d68ab59f9f46497a0909bf24a7b7dfff of ghcmod-vim. Things may have changed since the time of writing of this post. Hence for the best effect, please run the git grep -n "\['ghc-mod'" command in ${HOME}/.vim/bundle/ghcmod-vim to figure out where you need to change the code and use the files below as a reference.

Point one: https://github.com/eagletmt/ghcmod-vim/blob/1d192d13d68ab59f9f46497a0909bf24a7b7dfff/autoload/ghcmod.vim#L248

Modify:

let l:cmd = ['ghc-mod', '--silent']

to

let l:cmd = ['stack', 'exec', '--', 'ghc-mod', '--silent']

And in fact this was sufficient to let ,ht correctly infer the type of an expression, because this code is inside the ghcmod#build_command vim function and is used by various parts of the code to build the actual ghc-mod command.

Point two: https://github.com/eagletmt/ghcmod-vim/blob/1d192d13d68ab59f9f46497a0909bf24a7b7dfff/autoload/ghcmod.vim#L344

Modify:

\ substitute(vimproc#system(['ghc-mod', '--silent', 'root']), '\n*$', '', '')

to:

\ substitute(vimproc#system(['stack', 'exec', '--', 'ghc-mod', '--silent', 'root']), '\n*$', '', '')

Point three: https://github.com/eagletmt/ghcmod-vim/blob/1d192d13d68ab59f9f46497a0909bf24a7b7dfff/autoload/ghcmod/util.vim#L109

Modify:

let l:ghcmod = vimproc#system(['ghc-mod','version'])

to:

let l:ghcmod = vimproc#system(['stack', 'exec', 'ghc-mod','version'])

Step 3: Restart vim

This should be pretty obvious but it’s here for completeness.

Afterthoughts / Additional Notes

What did not work for me in the workaround stated at https://github.com/DanielG/ghc-mod/wiki#most-common-stack-related-issue was having the ,ht command work after running vim using stack exec, like stack exec -- vim source-file.hs. Contrary to what is stated, running vim like this does not add the directory containing the current Haskell project’s ghc-mod binary (that we installed via stack install ghc-mod in Step 1 of the solution) to the PATH environment variable. As such, the ghc-mod vim that’s invoked by the ghcmod-vim plugin is still the one installed in ${HOME}/.local/bin.

Because there are a few layers of abstraction, you might want to check if using ghc-mod directly on a Haskell source file actually works. Assuming you have already carried out step 1 of the solution above, simply run stack exec ghc-mod type source-file.hs row col and see if there is meaningful output (of course replacing source-file.hs, row, col with appropriate values).

For instance, given the following source file named hello.hs:

main :: IO ()
main = putStrLn "Hello, World!"

To find out the type of putStrLn, run:

stack exec ghc-mod type hello.hs 2 8

You should see the following output:

2 8 2 16 "String -> IO ()"
2 8 2 32 "IO ()"
2 1 2 32 "IO ()"
comments powered by Disqus