Additional code actions/quick fixes#164
Additional code actions/quick fixes#164friedbrice wants to merge 2 commits intojosephsumabat:mainfrom
Conversation
* Add required extension * Insert associated type * Insert cases * Insert fields * Insert missing methods * Use valid hole fit
src/StaticLS/HIE/File.hs
Outdated
| getHieFileFromPath :: (HasStaticEnv m, HasLogger m, MonadIO m, HasLogger m) => AbsPath -> MaybeT m HieFile | ||
| getHieFileFromPath :: (HasStaticEnv m, HasLogger m, MonadIO m) => AbsPath -> MaybeT m HieFile |
There was a problem hiding this comment.
Drive-by improvement.
| getCodeActions :: | ||
| LSP.TextDocumentIdentifier -> | ||
| LSP.CodeActionContext -> | ||
| AbsPath -> | ||
| LineCol -> | ||
| StaticLsM [LSP.CodeAction] | ||
| getCodeActions tdi ctx path lineCol = do |
There was a problem hiding this comment.
My code actions were written to a TextDocumentIdentifier -> CodeActionContext -> StaticLsM [CodeAction] shape, whereas the others were written to conform to AbsPath -> LineCol -> StaticLsM [Assist]. I attempted to refactor mine to be more idiomatic with the existing code actions, but I ran into significant difficulty constructing Assist values when I needed to provide them a Data.Range.Range (from tree-sitter). A tree-sitter Range requires two Data.Pos.Pos (which is what I need to construct), each of which carries just a single Int representing a document as either a sequence of characters or a sequence of bytes---that part's not clear to me---whereas an LSP Position (which is what I have in scope) has line number and column. I doubt there even is a way to convert between the two kinds of positions, considering platform differences such as newline character sequences and such.
Since I didn't have the information at hand to construct a tree-sitter Pos, I changed codeActions to take the info I needed to construct my CodeActions, and pushed assistToCodeAction into the body of codeActions so that it returns CodeAction instead of Assist.
| let issues = S.fromList (mapMaybe Parse.actionableIssue ctx._diagnostics) | ||
| issueActions <- join <$> traverse (issueToActions tdi) (S.toList issues) |
There was a problem hiding this comment.
These are the new code actions. See issueToActions and the type ActionableIssue.
| let issues = S.fromList (mapMaybe Parse.actionableIssue ctx._diagnostics) | ||
| issueActions <- join <$> traverse (issueToActions tdi) (S.toList issues) | ||
| assistActions <- | ||
| traverse assistToCodeAction $ |
There was a problem hiding this comment.
See discussion above for rationale on moving assistToCodeAction into the body of codeAction.
| LSP.Diagnostic -> | ||
| Parse.KnownExtension -> | ||
| LSP.CodeAction | ||
| addRequiredExtension tdi diag (Parse.KnownExtension _ text) = |
There was a problem hiding this comment.
If GHC says "Perhaps you intended to enable TypeApplications" or something like that, this function will create a quick fix for it.
| codeAction = insertAssociatedType | ||
|
|
||
| insertAssociatedType :: LSP.TextDocumentIdentifier -> LSP.Diagnostic -> T.Text -> LSP.CodeAction | ||
| insertAssociatedType tdi diag ty = |
There was a problem hiding this comment.
This code action is pretty superficial. Maybe we should remove it.
| codeAction = insertCases | ||
|
|
||
| insertCases :: LSP.TextDocumentIdentifier -> LSP.Diagnostic -> [T.Text] -> LSP.CodeAction | ||
| insertCases tdi diag pats = |
There was a problem hiding this comment.
If a GHC error (or warning) tells you which cases are missing, this function will create a quick fix that will insert the missing cases.
| Maybe T.Text -> | ||
| [T.Text] -> | ||
| LSP.CodeAction | ||
| insertFields tdi diag ctor existingFields missingFields = |
There was a problem hiding this comment.
If a GHC error (or warning) tells you which fields are missing, this code action will create a quick fix that will insert the missing fields for you. Works with both kinds of missing-fields error messages (i.e. the one for strict fields and the one for non-strict fields).
| codeAction = insertMissingMethods | ||
|
|
||
| insertMissingMethods :: LSP.TextDocumentIdentifier -> LSP.Diagnostic -> [T.Text] -> LSP.CodeAction | ||
| insertMissingMethods tdi diag methods = |
There was a problem hiding this comment.
If a GHC error (or warning) says your class instance is missing methods, this function will create a quick fix that will insert the missing methods for you.
| instance Eq (Ignored a) where (==) _ _ = True | ||
| instance Ord (Ignored a) where compare _ _ = EQ | ||
|
|
||
| data KnownExtension = KnownExtension {-# UNPACK #-} !Extension {-# UNPACK #-} !T.Text |
There was a problem hiding this comment.
I guess I should remove those pragmas. I don't remember why I even had them in there in the first place.
| {-# NOINLINE knownExtensions #-} | ||
| knownExtensions :: [KnownExtension] | ||
| knownExtensions = (\ext -> KnownExtension ext $ T.pack $ show ext) <$> [minBound .. maxBound] |
There was a problem hiding this comment.
No-inline because we only want to allocate this once, and refer to it again and again during execution. That said, it's probably not a huge deal to rebuild it every time. It's at most (what?) 40 or so items.
| knownExtensions :: [KnownExtension] | ||
| knownExtensions = (\ext -> KnownExtension ext $ T.pack $ show ext) <$> [minBound .. maxBound] | ||
|
|
||
| data ActionableIssue |
There was a problem hiding this comment.
An ActionableIssue is a defunctionalization of a method of creating one or more quick fixes. They are constructed by parsing GHC diagnostic messages, extracting the info relevant for making text edits in the relevant source file.
| actionableIssue :: LSP.Diagnostic -> Maybe ActionableIssue | ||
| actionableIssue diag = |
There was a problem hiding this comment.
The parsing code in this module follows similar code in HLS. They literally do the same asinine thing for their quick fixes. That is, instead of using structured data that GHC would hypothetically provided, they parse the human-readable error messages. Go Haskell!
| codeAction = useValidHoleFit | ||
|
|
||
| useValidHoleFit :: LSP.TextDocumentIdentifier -> LSP.Diagnostic -> T.Text -> LSP.CodeAction | ||
| useValidHoleFit tdi diag sym = |
There was a problem hiding this comment.
When GHC suggests valid hole fits, this function will create quick fixes for each such hole fit. (This one is the million-dollar feature.)
|
Thanks for the PR! I'll take a look some time this week/test it out :) |
|
Sorry I haven't gotten to this yet - I was a bit sick earlier this week so have been catching up on things but I will get this merged when I have the chance! |
There was a problem hiding this comment.
Thank you for submitting this, these are some really nice features! I think the PR is pretty close to being merge-able but after testing I ran into a few issues
- Some of the code actions don't actually show up because of the parsing for me for one reason or another - e.g. the
_messagefield of diagnostics actually has the bars stripped on my machine e.g.
_message = "Pattern match(es) are non-exhaustive\nIn a case alternative:\n Patterns of type \8216A _\nB \n C\n ..."
This causes the messages relying on bars to parse to fail. I'm not sure if this behavior is platform dependent though.
Changing the nonExhaustivePatterns locally to not use the bar seemed to make it work for me.
-
The actual action doesn't insert anything (likely related to me not fully fixing the parsing)
-
I suspect that this issue
I would like 2 main changes to the PR before I merge
-
Fixing the functionality - if this is local to my machine we can try to figure this out together
-
I would like at the very least a parsing test suite for the actionable messages - This can be golden tests or just unit tests - the main thing I want is something where I can paste a diagnostic and easily see if it matches its expected output.
it's possible that the error messages change across ghc versions or flags and it would be much easier to see why something isn't working and iterate with such a suite
This PR introduces six new code actions/quick fixes:
These code actions work by parsing relevant information from GHC error messages.