Skip to content
This repository has been archived by the owner on Jan 2, 2021. It is now read-only.

Allow GHC plugins to be called with an updated StringBuffer #698

Conversation

adinapoli
Copy link
Contributor

While converting LiquidHaskell into a plugin, we tried to integrate it with ghcide. It has been a journey with some lumps and bumps, but eventually we succeeded (with proviso). The plugin itself requires the minimum version of GHC to be 8.10.1 (for technical reasons) and soon enough we discovered that plugins and 8.10.1 don't get along too well, mostly due to https://gitlab.haskell.org/ghc/ghc/-/issues/18070 (which was originally discovered as part of #556).

However, I've tried building a custom ghc version (via Hadrian, as Leon Schoorl mentioned in that GHC ticket) and indeed got ghcide to sort of work with the LH plugin. I say "sort of" because we were observing some strange behaviour where diagnostics would be emitted only when saving the corresponding file, and not in real time, as usual. This happened (in short) for two reasons:

  • ghcide seems to not be calling any other plugin "hook" than the typecheckResultAction. It doesn't do it directly,
    but rather indirectly as part of the GHC API call to typecheckModule. This means that plugins have only "one single place"
    where they can intercept the normal ghcide workflow and do something else;

  • Crucially speaking, when ghcide was calling the typecheckResultAction, it was doing it with a stale view of the file
    being checked. In other terms, plugins received an input ModSummary that virtually pointed to the on disk snapshot of
    a file, and not the in memory one that the user was editing (via its editor of choice).

However, a point could be made that is ghcide's responsibility to call any registered plugin with the same view of the world that ghcide also "sees", to ensure there is no mismatch between what users can do and that plugins can do.

This patch fixes it:

lh_ghcide

While this works, it's also my first foray into ghcide hacking, so I have no clue whether or not this patch could add a severe overhead to the typechecking rule. If I understand correctly getFileContents caches the information, so in theory if the file doesn't change too often, this call is relatively cheap. Having said that, there might be better ways to achieve the same result I simply didn't think of.

Other caveats

This fix relies, as most fixes in this space, on the delicate house of cards provided by the GHC API. It works for LH (that needs to re-parse the input module) because the parseModule function from the GHC API looks first at the ms_hspp_buf content, and only if that's Nothing, it re-parses a module. On top of that, we have no guarantees (in the types, that is) that somebody else won't override the ms_hspp_buf downstream, before calling GHC.typecheckModule.

@digitalasset-cla
Copy link

digitalasset-cla commented Jul 14, 2020

CLA assistant check
All committers have signed the CLA.

@pepeiborra
Copy link
Collaborator

Thanks for the PR and the detailed description! I think that your diagnosis is spot on, and I am interested in figuring out why the ms_hspp_buf field is incorrectly populated in the ModSummary passed to typecheckModule.

Did you try populating the field in getModSummaryFromBuffer? The signature will need to be extended in order to take the StringBuffer as an argument, but that should be all.

https://github.com/digital-asset/ghcide/blob/master/src/Development/IDE/Core/Compile.hs#L431

@adinapoli
Copy link
Contributor Author

adinapoli commented Jul 15, 2020

Hey @pepeiborra ! Thanks for getting back to me so quickly 😉

Did you try populating the field in getModSummaryFromBuffer? The signature will need to be extended in order to take the StringBuffer as an argument, but that should be all.

I haven't. In my rabbit hole exploration I came across another homemade ModSummary construction as part of getModSummaryFromImports, and I've tried replacing the ms_hspp_buf content there, but that didn't change a thing. When I found the other ModSummary you mention (as part of getModSummaryFromBuffer) I didn't see any StringBuffer ready to be used as said "screw that, I am going to fix this as close as possible to the typecheckModule call 😉

But you make a good point, that ms_hspp_buf feels like the morally correct place to edit. I will try and report back.

@adinapoli adinapoli force-pushed the adinapoli/allow-plugins-to-see-updated-stringbuffer branch from 9b6e1d3 to 80d5f53 Compare July 15, 2020 07:20
The `getModSummaryFromBuffer` function constructs a `ModSummary` that
will be included in the `ParsedModule` data structure ghcide will later
on typecheck, calling any registred plugin in the process.

There was a problem, though: such `ModSummary` didn't include the
updated `StringBuffer` representing the in-memory content of a file
being edited (inclusive of all its unsaved changes). This was causing
plugins to not react in real time and emitting diagnostics only upon
save.

This commit fixes it.
@adinapoli adinapoli force-pushed the adinapoli/allow-plugins-to-see-updated-stringbuffer branch from 80d5f53 to b27b2e3 Compare July 15, 2020 07:25
@adinapoli
Copy link
Contributor Author

@pepeiborra Ah, success! You intuition was definitely correct. Properly setting the ms_hspp_buf inside getModSummaryFromBuffer did the trick, and didn't require the extra call to getFileContents. Nice! 😉

Copy link
Collaborator

@cocreature cocreature left a comment

Choose a reason for hiding this comment

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

Great work, thank you!

@cocreature cocreature merged commit 4b6e691 into haskell:master Jul 20, 2020
@adinapoli adinapoli deleted the adinapoli/allow-plugins-to-see-updated-stringbuffer branch July 20, 2020 09:10
pepeiborra pushed a commit to pepeiborra/ide that referenced this pull request Dec 29, 2020
…ghcide#698)

* Ignore tags file

* Pass an updated StringBuffer in ModSummary construction

The `getModSummaryFromBuffer` function constructs a `ModSummary` that
will be included in the `ParsedModule` data structure ghcide will later
on typecheck, calling any registred plugin in the process.

There was a problem, though: such `ModSummary` didn't include the
updated `StringBuffer` representing the in-memory content of a file
being edited (inclusive of all its unsaved changes). This was causing
plugins to not react in real time and emitting diagnostics only upon
save.

This commit fixes it.
pepeiborra pushed a commit to pepeiborra/ide that referenced this pull request Dec 29, 2020
…ghcide#698)

* Ignore tags file

* Pass an updated StringBuffer in ModSummary construction

The `getModSummaryFromBuffer` function constructs a `ModSummary` that
will be included in the `ParsedModule` data structure ghcide will later
on typecheck, calling any registred plugin in the process.

There was a problem, though: such `ModSummary` didn't include the
updated `StringBuffer` representing the in-memory content of a file
being edited (inclusive of all its unsaved changes). This was causing
plugins to not react in real time and emitting diagnostics only upon
save.

This commit fixes it.
pepeiborra pushed a commit to pepeiborra/ide that referenced this pull request Dec 29, 2020
…ghcide#698)

* Ignore tags file

* Pass an updated StringBuffer in ModSummary construction

The `getModSummaryFromBuffer` function constructs a `ModSummary` that
will be included in the `ParsedModule` data structure ghcide will later
on typecheck, calling any registred plugin in the process.

There was a problem, though: such `ModSummary` didn't include the
updated `StringBuffer` representing the in-memory content of a file
being edited (inclusive of all its unsaved changes). This was causing
plugins to not react in real time and emitting diagnostics only upon
save.

This commit fixes it.
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