Description
Context
When users add a new module to a cabal project, they are usually greeted with an uninformative Multi Cradle: No prefixes matched
or something similar.
We can identify three different cases and different behaviour:
1. Adding an other-module Test.hs
to an executable component
In an example project with only a Main.hs
module, adding an other-module Test.hs
results in the following error message:
Multi Cradle: No prefixes matched
pwd: d:\Privat\Documents\programming\haskell\example
filepath: D:\Privat\Documents\programming\haskell\example\app\Test.hs
prefixes:
("app/Main.hs",Cabal {component = Just "example:exe:example"})
The cause is that implicit-hie
generates roughly this hie.yaml file
cradle:
cabal:
- path: ./app/Main.hs
component: "example:exe:example"
Which essentially means, only map ./app/Main.hs
to the component example:exe:example
. Clearly ./app/Test.hs
is not listed here, resulting in the sub-par error message from hie-bios
.
Note, implicit-hie
doesn't handle overlapping source directories correctly in most cases, see #3606 for example.
2. Adding an exposed-module to a library component
This generally works ok because implicit-hie
generates hie.yaml
s of the form:
cradle:
cabal:
- path: ./src
component: "example:lib"
Essentially, maps for each source directory to the library component. So why does this work even if a hypothetical Lib.hs
is not part of the library component? hie-bios
doesn't care about that and just gives us the compilation options for the component example.:lib
. Then haskell-language-server just takes the options, creates a unit, and adds Lib.hs
to it:
Read the comment right above it for the justification.
That's why adding modules to a library works "okayish" most of the time.
3. Adding a new module to a component with a simple 'hie.yaml'
However, this all falls apart when we handwrite the recommended hie.yaml file:
cradle:
cabal:
This basically runs cabal repl src/Lib.hs
to find the compilation options. However, then cabal
complains even in the case where it previously was working fine!
Failed to run ["cabal","v2-repl","src\\Lib.hs"] in directory "D:\Privat\Documents\programming\haskell\example". Consult the logs for full command and error.
Failed command: cabal --builddir=C:\Users\Hugin\AppData\Local\hie-bios\dist-example-897af6579b5c0528e593e285a92d28a4 v2-repl --with-compiler C:\Users\Hugin\AppData\Local\hie-bios\wrapper-340ffcbd9b6dc8c3bed91eb5c533e4e3.exe --with-hc-pkg C:\ghcup\ghc\9.2.7\bin\ghc-pkg-9.2.7.exe src\Lib.hs
Error: cabal-3.10.1.0.exe: Failed extracting script block: `{- cabal:` start
marker not found
Naturally, if Lib.hs
is not an exposed- or other-module of the library component, cabal
cannot find it. In this case, the error message is terrible in particular, as it tries to interpret the target as a cabal script, which is not even close to the desired result.
Solution
We can identify at least two possible ways forward.
One way, is improving implicit-hie
to generate more lax hie.yaml
files. I am in general against that approach. It will conceal the problem, and will just produce inconsistent views between the build-tool (cabal) and HLS. E.g. HLS reports no error or warning while cabal build
straight up fails (also the package is rejected on hackage, etc..).
The better approach is to interject these terrible error messages and provide useful, actionable diagnostics to the user.
For example, for the first example, we should display a message like this:
Loading the module 'Test.hs' failed. It seems like it is not listed in your example.cabal file!
Perhaps you need to add `Test` as an other-module to your executable named 'example' in example.cabal.
If you don't know about other-module, yet, read this link <some-link>
It might not always be possible to provide precise information, but general information, perhaps with links to https://errors.haskell.org/, would be great already.
To improve the error message further, there are a couple of hacky solutions we can think of. We can try to guess the component the module likely will be part of by looking at already loaded components, and comparing the importPaths. If the loaded component options have a unit-id (supplied by -this-unit-id
), we can then lookup that unit-id in dist-newstyle/cache/plan.json
, find the component and produce a more accurate error message.
Fixing the issue programmatically is not something we want to do in this issue, we want to focus on purely better error messages.
Implementation Roadmap
- Find the location where the error message/diagnostic is shown to the user
- Here hie-bios is called:
- That result is eventually turned into a Diagnostic
- Change the error message only if we are trying to load a 'Cabal' cradle https://github.com/haskell/hie-bios/blob/78d0cd2332e05c46b747b70ca69c0746209f440e/src/HIE/Bios/Cradle.hs#L200
- Parse the error message, and produce a nicer diagnostics as discussed above, with mostly a high-level description of what's going wrong.
- It might be sensible, to turn the generic 'CradleError' into an ADT describing the error in more details, but parsing is also fine for the first approximation
- Try to guess the location of the module based on its proximity to already loaded components
- Some relevant type
dist-newstyle/cache/plan.json
helps you map aunit-id
to a component name.
This issue tracks the effort for improving the error message.