@@ -16,29 +16,29 @@ import Development.IDE (GetHieAst (GetHieAst),
16
16
IdeState (shakeExtras ),
17
17
Pretty (pretty ),
18
18
Recorder , WithPriority ,
19
- printOutputable )
19
+ printOutputableOneLine )
20
20
import Development.IDE.Core.PluginUtils (runIdeActionE ,
21
21
useWithStaleFastE )
22
22
import Development.IDE.Core.PositionMapping (fromCurrentPosition )
23
23
import Development.IDE.GHC.Compat (FastStringCompat , Name ,
24
- RealSrcSpan , SDoc ,
24
+ RealSrcSpan ,
25
25
getSourceNodeIds ,
26
- hie_types ,
27
26
isAnnotationInNodeInfo ,
28
27
mkRealSrcLoc ,
29
28
mkRealSrcSpan , ppr ,
30
29
sourceNodeInfo )
31
30
import Development.IDE.GHC.Compat.Util (LexicalFastString (LexicalFastString ))
32
31
import GHC.Core.Map.Type (deBruijnize )
32
+ import GHC.Core.Type (FunTyFlag (FTF_T_T ),
33
+ Type , dropForAlls ,
34
+ splitFunTy_maybe )
33
35
import GHC.Data.Maybe (rightToMaybe )
34
36
import GHC.Iface.Ext.Types (ContextInfo (Use ),
35
37
HieAST (nodeChildren , nodeSpan ),
36
38
HieASTs (getAsts ),
37
39
IdentifierDetails (identInfo , identType ),
38
40
nodeType )
39
- import GHC.Iface.Ext.Utils (hieTypeToIface ,
40
- recoverFullType ,
41
- smallestContainingSatisfying )
41
+ import GHC.Iface.Ext.Utils (smallestContainingSatisfying )
42
42
import GHC.Types.SrcLoc (isRealSubspanOf )
43
43
import Ide.Plugin.Error (getNormalizedFilePathE )
44
44
import Ide.Types (PluginDescriptor (pluginHandlers ),
@@ -91,44 +91,99 @@ signatureHelpProvider ideState _pluginId (SignatureHelpParams (TextDocumentIdent
91
91
Just (functionName, functionTypes, argumentNumber)
92
92
)
93
93
case results of
94
- -- TODO(@linj) what does non-singleton list mean?
94
+ [(_functionName, [] , _argumentNumber)] -> pure $ InR Null
95
95
[(functionName, functionTypes, argumentNumber)] ->
96
96
pure $ InL $ mkSignatureHelp (fromIntegral argumentNumber - 1 ) functionName functionTypes
97
+ -- TODO(@linj) what does non-singleton list mean?
97
98
_ -> pure $ InR Null
98
99
99
- mkSignatureHelp :: UInt -> Name -> [Text ] -> SignatureHelp
100
+ mkSignatureHelp :: UInt -> Name -> [Type ] -> SignatureHelp
100
101
mkSignatureHelp argumentNumber functionName functionTypes =
101
102
SignatureHelp
102
103
(mkSignatureInformation argumentNumber functionName <$> functionTypes)
103
104
(Just 0 )
104
105
(Just $ InL argumentNumber)
105
106
106
- mkSignatureInformation :: UInt -> Name -> Text -> SignatureInformation
107
+ mkSignatureInformation :: UInt -> Name -> Type -> SignatureInformation
107
108
mkSignatureInformation argumentNumber functionName functionType =
108
- let functionNameLabelPrefix = printOutputable (ppr functionName) <> " :: "
109
+ let functionNameLabelPrefix = printOutputableOneLine (ppr functionName) <> " :: "
109
110
in SignatureInformation
110
- (functionNameLabelPrefix <> functionType)
111
+ (functionNameLabelPrefix <> printOutputableOneLine functionType)
111
112
Nothing
112
113
(Just $ mkArguments (fromIntegral $ T. length functionNameLabelPrefix) functionType)
113
114
(Just $ InL argumentNumber)
114
115
115
- -- TODO(@linj) can type string be a multi-line string?
116
- mkArguments :: UInt -> Text -> [ParameterInformation ]
116
+ mkArguments :: UInt -> Type -> [ParameterInformation ]
117
117
mkArguments offset functionType =
118
- let separator = " -> "
119
- separatorLength = fromIntegral $ T. length separator
120
- splits = T. breakOnAll separator functionType
121
- prefixes = fst <$> splits
122
- prefixLengths = fmap (T. length >>> fromIntegral ) prefixes
123
- ranges =
124
- [ ( if previousPrefixLength == 0 then 0 else previousPrefixLength + separatorLength,
125
- currentPrefixLength
126
- )
127
- | (previousPrefixLength, currentPrefixLength) <- zip (0 : prefixLengths) prefixLengths
128
- ]
129
- in [ ParameterInformation (InR range) Nothing
130
- | range <- bimap (+ offset) (+ offset) <$> ranges
131
- ]
118
+ [ ParameterInformation (InR range) Nothing
119
+ | range <- bimap (+ offset) (+ offset) <$> findArgumentRanges functionType
120
+ ]
121
+
122
+ findArgumentRanges :: Type -> [(UInt , UInt )]
123
+ findArgumentRanges functionType =
124
+ let functionTypeString = printOutputableOneLine functionType
125
+ functionTypeStringLength = fromIntegral $ T. length functionTypeString
126
+ splitFunctionTypes = filter notTypeConstraint $ splitFunTysIgnoringForAll functionType
127
+ splitFunctionTypeStrings = printOutputableOneLine . fst <$> splitFunctionTypes
128
+ -- reverse to avoid matching "a" of "forall a" in "forall a. a -> a"
129
+ reversedRanges =
130
+ drop 1 $ -- do not need the range of the result (last) type
131
+ findArgumentStringRanges
132
+ 0
133
+ (T. reverse functionTypeString)
134
+ (T. reverse <$> reverse splitFunctionTypeStrings)
135
+ in reverse $ modifyRange functionTypeStringLength <$> reversedRanges
136
+ where
137
+ modifyRange functionTypeStringLength (start, end) =
138
+ (functionTypeStringLength - end, functionTypeStringLength - start)
139
+
140
+ {-
141
+ The implemented method uses both structured type and unstructured type string.
142
+ It provides good enough results and is easier to implement than alternative
143
+ method 1 or 2.
144
+
145
+ Alternative method 1: use only structured type
146
+ This method is hard to implement because we need to duplicate some logic of 'ppr' for 'Type'.
147
+ Some tricky cases are as follows:
148
+ - 'Eq a => Num b -> c' is shown as '(Eq a, Numb) => c'
149
+ - 'forall' can appear anywhere in a type when RankNTypes is enabled
150
+ f :: forall a. Maybe a -> forall b. (a, b) -> b
151
+ - '=>' can appear anywhere in a type
152
+ g :: forall a b. Eq a => a -> Num b => b -> b
153
+ - ppr the first argument type of '(a -> b) -> a -> b' is 'a -> b' (no parentheses)
154
+ - 'forall' is not always shown
155
+
156
+ Alternative method 2: use only unstructured type string
157
+ This method is hard to implement because we need to parse the type string.
158
+ Some tricky cases are as follows:
159
+ - h :: forall a (m :: Type -> Type). Monad m => a -> m a
160
+ -}
161
+ findArgumentStringRanges :: UInt -> Text -> [Text ] -> [(UInt , UInt )]
162
+ findArgumentStringRanges _totalPrefixLength _functionTypeString [] = []
163
+ findArgumentStringRanges totalPrefixLength functionTypeString (argumentTypeString: restArgumentTypeStrings) =
164
+ let (prefix, match) = T. breakOn argumentTypeString functionTypeString
165
+ prefixLength = fromIntegral $ T. length prefix
166
+ argumentTypeStringLength = fromIntegral $ T. length argumentTypeString
167
+ start = totalPrefixLength + prefixLength
168
+ in (start, start + argumentTypeStringLength)
169
+ : findArgumentStringRanges
170
+ (totalPrefixLength + prefixLength + argumentTypeStringLength)
171
+ (T. drop (fromIntegral argumentTypeStringLength) match)
172
+ restArgumentTypeStrings
173
+
174
+ -- similar to 'splitFunTys' but
175
+ -- 1) the result (last) type is included and
176
+ -- 2) toplevel foralls are ignored
177
+ splitFunTysIgnoringForAll :: Type -> [(Type , Maybe FunTyFlag )]
178
+ splitFunTysIgnoringForAll ty = case ty & dropForAlls & splitFunTy_maybe of
179
+ Just (funTyFlag, _mult, argumentType, resultType) ->
180
+ (argumentType, Just funTyFlag) : splitFunTysIgnoringForAll resultType
181
+ Nothing -> [(ty, Nothing )]
182
+
183
+ notTypeConstraint :: (Type , Maybe FunTyFlag ) -> Bool
184
+ notTypeConstraint (_type, Just FTF_T_T ) = True
185
+ notTypeConstraint (_type, Nothing ) = True
186
+ notTypeConstraint _ = False
132
187
133
188
extractInfoFromSmallestContainingFunctionApplicationAst ::
134
189
Position -> HieASTs a -> (RealSrcSpan -> HieAST a -> Maybe b ) -> [b ]
@@ -156,7 +211,7 @@ getLeftMostNode thisNode =
156
211
[] -> thisNode
157
212
leftChild: _ -> getLeftMostNode leftChild
158
213
159
- getNodeNameAndTypes :: forall a . HieKind a -> HieAST a -> Maybe (Name , [Text ])
214
+ getNodeNameAndTypes :: HieKind a -> HieAST a -> Maybe (Name , [Type ])
160
215
getNodeNameAndTypes hieKind hieAst =
161
216
if nodeHasAnnotation (" HsVar" , " HsExpr" ) hieAst
162
217
then case hieAst & getSourceNodeIds & M. filter isUse & M. assocs of
@@ -171,26 +226,21 @@ getNodeNameAndTypes hieKind hieAst =
171
226
allTypes = case mTypeOfName of
172
227
Nothing -> typesOfNode
173
228
Just typeOfName -> typeOfName : filter (isDifferentType typeOfName) typesOfNode
174
- in Just (name, prettyType <$> allTypes)
229
+ in Just (name, filterCoreTypes allTypes)
175
230
[] -> Nothing
176
231
_ -> Nothing -- seems impossible
177
232
else Nothing -- TODO(@linj) must function node be HsVar?
178
233
where
179
234
extractName = rightToMaybe
180
235
181
- isDifferentType :: a -> a -> Bool
182
236
isDifferentType type1 type2 = case hieKind of
183
- HieFresh -> deBruijnize type1 /= deBruijnize type2
184
- HieFromDisk _hieFile -> type1 /= type2
185
-
186
- -- modified from Development.IDE.Spans.AtPoint.atPoint
187
- prettyType :: a -> Text
188
- prettyType = expandType >>> printOutputable
237
+ HieFresh -> deBruijnize type1 /= deBruijnize type2
238
+ HieFromDisk {} -> type1 /= type2
189
239
190
- expandType :: a -> SDoc
191
- expandType t = case hieKind of
192
- HieFresh -> ppr t
193
- HieFromDisk hieFile -> ppr $ hieTypeToIface $ recoverFullType t (hie_types hieFile)
240
+ filterCoreTypes types = case hieKind of
241
+ HieFresh -> types
242
+ -- ignore this case since this only happens before we finish startup
243
+ HieFromDisk {} -> []
194
244
195
245
isUse :: IdentifierDetails a -> Bool
196
246
isUse = identInfo >>> S. member Use
0 commit comments