-
-
Notifications
You must be signed in to change notification settings - Fork 402
Implement signature help #4626
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Implement signature help #4626
Conversation
@@ -764,6 +770,10 @@ instance PluginRequestMethod Method_TextDocumentDocumentSymbol where | |||
si = SymbolInformation name' (ds ^. L.kind) Nothing parent (ds ^. L.deprecated) loc | |||
in [si] <> children' | |||
|
|||
-- TODO(@linj) is this correct? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is reasonable. Really combineResponses
should have a way to return an error. There are a bunch of methods where it really only makes sense if we have one handler.
We could try to combine responses: we would combine the signatures, and so long as only one of them indicated an active signature it would be okay. But that's a bit sketchy and I doubt we'll have several anyway!
TODO: - handle more cases - add successful and (currently failed) tests - show documentation
7a02359
to
9168b74
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think really worth trying to start getting some tests in place!
plugins/hls-signature-help-plugin/src/Ide/Plugin/SignatureHelp.hs
Outdated
Show resolved
Hide resolved
plugins/hls-signature-help-plugin/src/Ide/Plugin/SignatureHelp.hs
Outdated
Show resolved
Hide resolved
plugins/hls-signature-help-plugin/src/Ide/Plugin/SignatureHelp.hs
Outdated
Show resolved
Hide resolved
plugins/hls-signature-help-plugin/src/Ide/Plugin/SignatureHelp.hs
Outdated
Show resolved
Hide resolved
plugins/hls-signature-help-plugin/src/Ide/Plugin/SignatureHelp.hs
Outdated
Show resolved
Hide resolved
67f5cdb
to
62fbccf
Compare
This is discussed with @fendor.
This improves performance. It also improves correctness because functionName, functionType and argumentNumber are extracted from the same AST.
Vscode and Emacs(eglot) seems to treat newline as a normal char.
See comment for a comparison with alternative methods.
All tests on Linux and darwin passed! Not sure what happens on windows. Added a few more tests and now |
findArgumentRanges :: Type -> [(UInt, UInt)] | ||
findArgumentRanges functionType = | ||
let functionTypeString = printOutputableOneLine functionType | ||
functionTypeStringLength = fromIntegral $ T.length functionTypeString | ||
splitFunctionTypes = filter notTypeConstraint $ splitFunTysIgnoringForAll functionType | ||
splitFunctionTypeStrings = printOutputableOneLine . fst <$> splitFunctionTypes | ||
-- reverse to avoid matching "a" of "forall a" in "forall a. a -> a" | ||
reversedRanges = | ||
drop 1 $ -- do not need the range of the result (last) type | ||
findArgumentStringRanges | ||
0 | ||
(T.reverse functionTypeString) | ||
(T.reverse <$> reverse splitFunctionTypeStrings) | ||
in reverse $ modifyRange functionTypeStringLength <$> reversedRanges | ||
where | ||
modifyRange functionTypeStringLength (start, end) = | ||
(functionTypeStringLength - end, functionTypeStringLength - start) | ||
|
||
{- | ||
The implemented method uses both structured type and unstructured type string. | ||
It provides good enough results and is easier to implement than alternative | ||
method 1 or 2. | ||
|
||
Alternative method 1: use only structured type | ||
This method is hard to implement because we need to duplicate some logic of 'ppr' for 'Type'. | ||
Some tricky cases are as follows: | ||
- 'Eq a => Num b -> c' is shown as '(Eq a, Numb) => c' | ||
- 'forall' can appear anywhere in a type when RankNTypes is enabled | ||
f :: forall a. Maybe a -> forall b. (a, b) -> b | ||
- '=>' can appear anywhere in a type | ||
g :: forall a b. Eq a => a -> Num b => b -> b | ||
- ppr the first argument type of '(a -> b) -> a -> b' is 'a -> b' (no parentheses) | ||
- 'forall' is not always shown | ||
|
||
Alternative method 2: use only unstructured type string | ||
This method is hard to implement because we need to parse the type string. | ||
Some tricky cases are as follows: | ||
- h :: forall a (m :: Type -> Type). Monad m => a -> m a | ||
-} | ||
findArgumentStringRanges :: UInt -> Text -> [Text] -> [(UInt, UInt)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Context:
- Implement signature help #4626 (comment)
- Implement signature help #4626 (comment)
- Implement signature help #4626 (comment)
I implemented a mixed way which uses both structured type and type string. See the comment for the reason of doing it this way instead of using only structured type or only type string.
It seems to work pretty well. "3 out of 87 tests failed".
One failed test case is f :: Integer -> Num Integer => Integer -> Integer
. We matched Num Integer
as the first argument. This probably can be fixed by using regex.
Another similar failed test case is f :: forall l. l -> forall a. a -> a
. When we should highlight the argument l
, we highlight the l
for the second forall
.
Here is another failed test case.
f :: a -> forall a. a -> a
f = _
The printed type string for f
is f :: forall a. a -> forall a1. a1 -> a1
. This seems tricky to fix in the current implementation because it needs us to duplicate the renaming logic (the second forall a
becomes forall a1
) of ppr
.
This case only happens when RankNTypes
is used so I do not think it is very common.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering how bad it would be to just use the structured type and re-implement some pretty-printing logic. I think the only bit we'd need to duplicate would be the printing of ->
, =>
, and forall
? Maybe that's okay?
Another thing we should think about: type arguments.
Type arguments are kind of annoying because they might be optional (with normal foralls), or they might be required (with required type arguments). |
type DocMap = NameEnv SpanDoc | ||
type TyThingMap = NameEnv TyThing | ||
type ArgDocMap = NameEnv (IntMap SpanDoc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of creating a new ArgDocMap
, an alternative implementation is to extend DocMap
to NameEnv (SpanDoc, IntMap SpanDoc)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds sensible to me? Any reason not to do that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not have a strong opinion on this. I will change the code to type DocMap = NameEnv (SpanDoc, IntMap SpanDoc)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the code it seems like it would simplifiy things quite a lot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I have second thoughts about this because the alternative implementation type DocMap = NameEnv (SpanDoc, IntMap SpanDoc)
is semantically incorrect. The semantically correct version should be type DocMap = NameEnv (Maybe SpanDoc, IntMap SpanDoc)
or type DocMap = NameEnv (Maybe SpanDoc, Maybe (IntMap SpanDoc))
because a Name
can have only function doc or arg doc.
I prefer to keep the old implementation, i.e., introducing a new ArgDocMap
, since the semantically correct version of the alternative implementation seems more complicated.
This makes tests less flaky.
Uris are already shown in the function documentation. No need to show them in the argument documentation again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am a bit confused about this failed test in CI which I cannot reproduce locally.
In CI, the function doc of pure
is "\n\nLift a value.\n\n"
. Locally, it is "\n\nLift a value.\n\n\\[Documentation\\]\\(file://.*\\)\n\n\\[Source\\]\\(file://.*\\)\n\n"
. So somehow in CI HLS fails to find uris of the imported function pure
.
type constraint with kind signatures
type constraint with kind signatures 2: FAIL (0.15s)
src/Test/Hls.hs:400:
Full Line: "x = pure True"
Cursor Column: " ^"
Prefix Text: ""
expected: Just (SignatureHelp {_signatures = [SignatureInformation {_label = "pure :: forall (f :: Type -> Type) a. Applicative f => a -> f a", _documentation = Just (InR (MarkupContent {_kind = MarkupKind_Markdown, _value = "\n\nLift a value.\n\n\\[Documentation\\]\\(file://.*\\)\n\n\\[Source\\]\\(file://.*\\)\n\n"})), _parameters = Just [ParameterInformation {_label = InR (55,56), _documentation = Nothing}], _activeParameter = Just (InL 0)},SignatureInformation {_label = "pure :: Bool -> IO Bool", _documentation = Just (InR (MarkupContent {_kind = MarkupKind_Markdown, _value = "\n\nLift a value.\n\n\\[Documentation\\]\\(file://.*\\)\n\n\\[Source\\]\\(file://.*\\)\n\n"})), _parameters = Just [ParameterInformation {_label = InR (8,12), _documentation = Nothing}], _activeParameter = Just (InL 0)},SignatureInformation {_label = "pure :: forall a. a -> IO a", _documentation = Just (InR (MarkupContent {_kind = MarkupKind_Markdown, _value = "\n\nLift a value.\n\n\\[Documentation\\]\\(file://.*\\)\n\n\\[Source\\]\\(file://.*\\)\n\n"})), _parameters = Just [ParameterInformation {_label = InR (18,19), _documentation = Nothing}], _activeParameter = Just (InL 0)}], _activeSignature = Just 0, _activeParameter = Just (InL 0)})
but got: Just (SignatureHelp {_signatures = [SignatureInformation {_label = "pure :: forall (f :: Type -> Type) a. Applicative f => a -> f a", _documentation = Just (InR (MarkupContent {_kind = MarkupKind_Markdown, _value = "\n\nLift a value.\n\n"})), _parameters = Just [ParameterInformation {_label = InR (55,56), _documentation = Nothing}], _activeParameter = Just (InL 0)},SignatureInformation {_label = "pure :: Bool -> IO Bool", _documentation = Just (InR (MarkupContent {_kind = MarkupKind_Markdown, _value = "\n\nLift a value.\n\n"})), _parameters = Just [ParameterInformation {_label = InR (8,12), _documentation = Nothing}], _activeParameter = Just (InL 0)},SignatureInformation {_label = "pure :: forall a. a -> IO a", _documentation = Just (InR (MarkupContent {_kind = MarkupKind_Markdown, _value = "\n\nLift a value.\n\n"})), _parameters = Just [ParameterInformation {_label = InR (18,19), _documentation = Nothing}], _activeParameter = Just (InL 0)}], _activeSignature = Just 0, _activeParameter = Just (InL 0)})
Use -p '/type constraint with kind signatures 2/' to rerun this test only.
Closes #3598
I will update progress here.
This PR also relates to #2348 because we let mkDocMap expose the argument doc map.
2025-06-02 - 2025-06-08
commit: 7a54a1d
click for screenshot
2025-06-09 - 2025-07-13
commit: 9168b74
click for video demo
Screencast.From.2025-07-13.02-11-44.webm
2025-07-14 - 2025-07-20
2025-07-21 - 2025-07-27
maybe
with case for better readability (bf0b4d5)2025-07-28 - 2025-08-03
2025-08-04 - 2025-08-10
2025-08-11 - 2025-08-17
Show function documentation in signature help (a522e88)
Show function argument documentation in signature help (d826d06)
Do not error if doc is not available (35399e7)
click for screenshots
2025-08-18 - 2025-08-24
getSignatureHelp
tolsp-test
: Add signature help request to lsp-test lsp#621Any
becomesZonkAny 0
(c7931a2)