- Project: Case Split Plugin
- Contributor: Enrico Maria De Angelis (@Aster89)
- Mentor: Magnus Viernickel (@MangoIV) + Hannes Siebenhandl (@fendor) + Andreas Klebinger (@andreaspk) @ Well-Typed
- Organization: Haskell.org
- Pull request with code: haskell/haskell-language-server#5014
This PR is for (re-¹)introducing the so called case-split plugin, as requested in haskell/haskell-language-server#5013.
The main goals were:
- create a plugin that uses existing APIs, e.g.
ghc-exactprint's, in order to correctly insert missing patterns tocase/\caseexpressions with a non-exhaustive list of patterns, i.e. a plugin that can turn, as a simple example, thisinto thisdata X = A | B | C Int | D Int Int | E Int Int Int Int | F foo :: X -> Int foo x = case x of A -> 3; B -> 4; C _ -> 5
data X = -- same as above foo :: X -> Int foo x = case x of A -> 3; B -> 4; C _ -> 5 D _ _ -> _ E {} -> _ F -> _
- write code in a way that it would easily be reused for scenarii other than incomplete
caseexpression "proper" use-cases (see § Future work below) - support "ordinary" ADTs;
- support GATDs.
My work has consisted mainly of:
- implementing the plugin by incrementally focusing on several use-cases from less to more complex,
- contextually writing tests for guarding such use-cases as I enabled them,
- addressing feedback received by my mentors' via their reviews and via live discussions in our weekly meetings.
Besides, some minor contributions were also carried out:
-
having spotted the bug haskell/haskell-language-server#4935,
- I caught it as an opportunity to gain some initial experience with the
ghc-exactprintlibrary in a very localized context, - and eventually even submitted a solution for it in haskell/haskell-language-server#4937;
- I caught it as an opportunity to gain some initial experience with the
-
it is known that
withWarningsshould be get rid of, and with haskell/haskell-language-server#4971 I removed it from a single file I came through as part of my initial explorations; -
I improved readability of some existing HLS code by fixing comments to it and simplifying it by applying some guarantees documented in
ghc-exactprintthat were not leveraged so far (haskell/haskell-language-server#5026); -
having come through the Haskell debugger and having used it for my early explorations of HLS, I fostered its improvement by judiciously reporting bugs/possible improvements;
- crucially, one of these was an actual DAP non-compliance.
-
Becoming comfortable in inspecting an AST itself (specifically a
ParsedSource, i.e. aLocated (HsModule GhcPs)), was a challenge, but way easier than it would have been, once @fendor told me aboutshowAst. -
By far, the greatest challenge was - or actually is, because I'm working on the stretch goals below - understanding
ghc-exactprintAPIs and how they relate to the AST. -
The simple fact of getting used to names of relevant types, and remembering them took a lot of commitment, that initially translated in me going back and forth from my IDE to Hoogle multiple times per coding session, even.
-
Lay out missing alternatives in a reasonable way.
- There are two main ways to complete a case
;-separated alternatives all on a single line- one alternative per line.
- Eventually, I've opted for choosing the layout according to the following heuristic:
- in the absence of pre-existing patterns, preferring the one-per-line layout,
- in the presence of pre-existing patterns, relying on the last line of them to decide how many patterns per line to layout.
- There are two main ways to complete a case
-
Extend the plugin to also work in these scenarii:
- incomplete patterns in
\case;- I'm working on this at the time of writing,
but some difficulty has arisen that I had not anticipated; - turns out it was a bug promptly fixed by @alanz in aedfb90a77f091edc4166788439a2c59b9039322
- I'm working on this at the time of writing,
- incomplete patterns in
\cases; - incomplete patterns in function definitions;
- support
PmAltLitand non-;RealDataConpatterns- the plugin doesn't support yet the case where one of the missing pattern is not a PmAltConLike (at the time of writing, the only alternative is
PmAltLit)or it is not a.RealDataCon(at the time of writing, the only alternative isPatSynCon) - I ended up adding support for
PatSynConwithin the PR for GSoC.
- the plugin doesn't support yet the case where one of the missing pattern is not a PmAltConLike (at the time of writing, the only alternative is
- incomplete patterns in
-
Use
ExceptTinstead ofState Bool- Currently we traverse the whole AST and create a new one and we alter only one node along the way, the one relative to the inner-most incomplete
caseexpression containing the cursor position; - to do that, we use
everywhereMto traverse the AST, and we run inside aState Boolmonad to keep track of whether we've already altered the relevant node, after which we simply return the current node without performing anyRange-related checks; - changing the approach to use an
ExceptTmonadic transformer would probably benefit the code from the performance (and readability?) perspective.
- Currently we traverse the whole AST and create a new one and we alter only one node along the way, the one relative to the inner-most incomplete
-
- Obviously, we should change the text title of the action, as discussed here.
- Currently, we retrieve the list of missing patterns from the diagnostic, and such a list is limited by
-fmax-uncovered-patterns, which means that when more patterns than those are missing, the plugin's action will only add that many patterns and thecaseexpression will remain incomplete, if less incomplete than before.- To work around such a limitation is enough to trigger the plugin multiple times, as it will add
-fmax-uncovered-patternsmissing patterns each time.
- To work around such a limitation is enough to trigger the plugin multiple times, as it will add
- Ideally, we'd want the number of patterns added by the plugin be a separate, HLS setting, rather than relying on the GHC flag the user has chose for the given project.
-
Allow customization a few aspects of the snippet produced by the plugin:
- allow configuring the indentation (currently hardcoded to 2);
- allow configuring the maximum number (possibly ∞) of underscores for the ctors' arguments before resorting to
{}.
-
Use unicode syntax when appropriate:
-
add support and tests for pattern synonyms;
-
Investigate why the "Expression is
_" needs the hack commented here. -
workaround the bug (inghc-exactprint?) by which triggering the case-split for acase x ofexpression that occurs at EOF in a file that ends without line terminator, which is custom when you save a file after removing all trailing empty lines on Windows.report the bug toghc-exactprint. Done alanz/ghc-exactprint#147- This was not a bug in
ghc-exactprint, but in HLS, and it's actually not specific to the case-split plugin, as it affects others.
Earlier attempts at introducing such a plugin, documented in haskell/haskell-language-server#3525, were productive in outlining the challenges and in exploring plans of attack and possible solutions, but the resulting product ultimately did not stand the test of time, due to its poor maintainability.
A few years later, HLS, ghc-exactprint, GHC and the whole Haskell ecosystem have piled up quite a few more releases worth of experience on their back, which warrants another attempt at addressing this task might be fruitful, hence my work aims at addressing such long-standing lack of support for a feature that seems so simple from the user's perspective, which is my own as well.