@@ -12,52 +12,60 @@ module Ide.Plugin.ExplicitFields
12
12
, Log
13
13
) where
14
14
15
+ import Control.Arrow ((&&&) )
15
16
import Control.Lens ((&) , (?~) , (^.) )
17
+ import Control.Monad (replicateM )
16
18
import Control.Monad.IO.Class (MonadIO (liftIO ))
19
+ import Control.Monad.Trans.Class (lift )
17
20
import Control.Monad.Trans.Maybe
21
+ import Data.Aeson (ToJSON (toJSON ))
18
22
import Data.Generics (GenericQ , everything ,
19
23
everythingBut , extQ , mkQ )
20
24
import qualified Data.IntMap.Strict as IntMap
25
+ import Data.List (find , intersperse )
21
26
import qualified Data.Map as Map
22
27
import Data.Maybe (fromMaybe , isJust ,
23
28
mapMaybe , maybeToList )
24
29
import Data.Text (Text )
25
- import Data.Unique (hashUnique , newUnique )
26
-
27
- import Control.Monad (replicateM )
28
- import Control.Monad.Trans.Class (lift )
29
- import Data.Aeson (ToJSON (toJSON ))
30
- import Data.List (find , intersperse )
31
30
import qualified Data.Text as T
31
+ import Data.Unique (hashUnique , newUnique )
32
32
import Development.IDE (IdeState ,
33
33
Location (Location ),
34
34
Pretty (.. ),
35
35
Range (Range , _end , _start ),
36
36
Recorder (.. ), Rules ,
37
37
WithPriority (.. ),
38
38
defineNoDiagnostics ,
39
- getDefinition , printName ,
39
+ getDefinition , hsep ,
40
+ printName ,
40
41
realSrcSpanToRange ,
41
42
shakeExtras ,
43
+ srcSpanToLocation ,
42
44
srcSpanToRange , viaShow )
43
45
import Development.IDE.Core.PluginUtils
44
46
import Development.IDE.Core.PositionMapping (toCurrentRange )
45
47
import Development.IDE.Core.RuleTypes (TcModuleResult (.. ),
46
48
TypeCheck (.. ))
47
49
import qualified Development.IDE.Core.Shake as Shake
48
- import Development.IDE.GHC.Compat (FieldOcc (FieldOcc ),
49
- GhcPass , GhcTc ,
50
+ import Development.IDE.GHC.Compat (FieldLabel (flSelector ),
51
+ FieldOcc (FieldOcc ),
52
+ GenLocated (L ), GhcPass ,
53
+ GhcTc ,
50
54
HasSrcSpan (getLoc ),
51
55
HsConDetails (RecCon ),
52
- HsExpr (HsVar , XExpr ),
56
+ HsExpr (HsApp , HsVar , XExpr ),
53
57
HsFieldBind (hfbLHS ),
54
58
HsRecFields (.. ),
55
59
Identifier , LPat ,
60
+ Located ,
56
61
NamedThing (getName ),
57
62
Outputable ,
58
63
TcGblEnv (tcg_binds ),
59
64
Var (varName ),
60
65
XXExprGhcTc (.. ),
66
+ conLikeFieldLabels ,
67
+ nameSrcSpan ,
68
+ pprNameUnqualified ,
61
69
recDotDot , unLoc )
62
70
import Development.IDE.GHC.Compat.Core (Extension (NamedFieldPuns ),
63
71
HsExpr (RecordCon , rcon_flds ),
@@ -129,9 +137,10 @@ descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeSta
129
137
descriptor recorder plId =
130
138
let resolveRecorder = cmapWithPrio LogResolve recorder
131
139
(carCommands, caHandlers) = mkCodeActionWithResolveAndCommand resolveRecorder plId codeActionProvider codeActionResolveProvider
132
- ihHandlers = mkPluginHandler SMethod_TextDocumentInlayHint (inlayHintProvider recorder)
140
+ ihDotdotHandler = mkPluginHandler SMethod_TextDocumentInlayHint (inlayHintDotdotProvider recorder)
141
+ ihPosRecHandler = mkPluginHandler SMethod_TextDocumentInlayHint (inlayHintPosRecProvider recorder)
133
142
in (defaultPluginDescriptor plId " Provides a code action to make record wildcards explicit" )
134
- { pluginHandlers = caHandlers <> ihHandlers
143
+ { pluginHandlers = caHandlers <> ihDotdotHandler <> ihPosRecHandler
135
144
, pluginCommands = carCommands
136
145
, pluginRules = collectRecordsRule recorder *> collectNamesRule
137
146
}
@@ -145,9 +154,9 @@ codeActionProvider ideState _ (CodeActionParams _ _ docId range _) = do
145
154
let actions = map (mkCodeAction enabledExtensions) (RangeMap. filterByRange range crCodeActions)
146
155
pure $ InL actions
147
156
where
148
- mkCodeAction :: [Extension ] -> Int -> Command |? CodeAction
157
+ mkCodeAction :: [Extension ] -> Int -> Command |? CodeAction
149
158
mkCodeAction exts uid = InR CodeAction
150
- { _title = mkTitle exts
159
+ { _title = mkTitle exts -- TODO: `Expand positional record` without NamedFieldPuns if RecordInfoApp
151
160
, _kind = Just CodeActionKind_RefactorRewrite
152
161
, _diagnostics = Nothing
153
162
, _isPreferred = Nothing
@@ -167,15 +176,19 @@ codeActionResolveProvider ideState pId ca uri uid = do
167
176
record <- handleMaybe PluginStaleResolve $ IntMap. lookup uid crCodeActionResolve
168
177
-- We should never fail to render
169
178
rendered <- handleMaybe (PluginInternalError " Failed to render" ) $ renderRecordInfoAsTextEdit nameMap record
170
- let edits = [rendered]
171
- <> maybeToList (pragmaEdit enabledExtensions pragma)
179
+ let shouldInsertNamedFieldPuns (RecordInfoApp _ _) = False
180
+ shouldInsertNamedFieldPuns _ = True
181
+ whenMaybe True x = x
182
+ whenMaybe False _ = Nothing
183
+ edits = [rendered]
184
+ <> maybeToList (whenMaybe (shouldInsertNamedFieldPuns record) (pragmaEdit enabledExtensions pragma))
172
185
pure $ ca & L. edit ?~ mkWorkspaceEdit edits
173
186
where
174
187
mkWorkspaceEdit :: [TextEdit ] -> WorkspaceEdit
175
188
mkWorkspaceEdit edits = WorkspaceEdit (Just $ Map. singleton uri edits) Nothing Nothing
176
189
177
- inlayHintProvider :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState 'Method_TextDocumentInlayHint
178
- inlayHintProvider _ state pId InlayHintParams {_textDocument = TextDocumentIdentifier uri, _range = visibleRange} = do
190
+ inlayHintDotdotProvider :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState 'Method_TextDocumentInlayHint
191
+ inlayHintDotdotProvider _ state pId InlayHintParams {_textDocument = TextDocumentIdentifier uri, _range = visibleRange} = do
179
192
nfp <- getNormalizedFilePathE uri
180
193
pragma <- getFirstPragma pId state nfp
181
194
runIdeActionE " ExplicitFields.CollectRecords" (shakeExtras state) $ do
@@ -186,18 +199,18 @@ inlayHintProvider _ state pId InlayHintParams {_textDocument = TextDocumentIdent
186
199
, uid <- RangeMap. elementsInRange range crCodeActions
187
200
, Just record <- [IntMap. lookup uid crCodeActionResolve] ]
188
201
-- Get the definition of each dotdot of record
189
- locations = [ getDefinition nfp pos
202
+ locations = [ fmap (,record) ( getDefinition nfp pos)
190
203
| record <- records
191
204
, pos <- maybeToList $ fmap _start $ recordInfoToDotDotRange record ]
192
205
defnLocsList <- lift $ sequence locations
193
- pure $ InL $ mapMaybe (mkInlayHints crr pragma) ( zip defnLocsList records)
206
+ pure $ InL $ mapMaybe (mkInlayHint crr pragma) defnLocsList
194
207
where
195
- mkInlayHints :: CollectRecordsResult -> NextPragmaInfo -> (Maybe [(Location , Identifier )], RecordInfo ) -> Maybe InlayHint
196
- mkInlayHints CRR {enabledExtensions, nameMap} pragma (defnLocs, record) =
208
+ mkInlayHint :: CollectRecordsResult -> NextPragmaInfo -> (Maybe [(Location , Identifier )], RecordInfo ) -> Maybe InlayHint
209
+ mkInlayHint CRR {enabledExtensions, nameMap} pragma (defnLocs, record) =
197
210
let range = recordInfoToDotDotRange record
198
211
textEdits = maybeToList (renderRecordInfoAsTextEdit nameMap record)
199
212
<> maybeToList (pragmaEdit enabledExtensions pragma)
200
- names = renderRecordInfoAsLabelName record
213
+ names = renderRecordInfoAsDotdotLabelName record
201
214
in do
202
215
end <- fmap _end range
203
216
names' <- names
@@ -224,6 +237,40 @@ inlayHintProvider _ state pId InlayHintParams {_textDocument = TextDocumentIdent
224
237
}
225
238
mkInlayHintLabelPart (value, loc) = InlayHintLabelPart value Nothing loc Nothing
226
239
240
+
241
+ inlayHintPosRecProvider :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState 'Method_TextDocumentInlayHint
242
+ inlayHintPosRecProvider _ state _pId InlayHintParams {_textDocument = TextDocumentIdentifier uri, _range = visibleRange} = do
243
+ nfp <- getNormalizedFilePathE uri
244
+ runIdeActionE " ExplicitFields.CollectRecords" (shakeExtras state) $ do
245
+ (CRR {crCodeActions, nameMap, crCodeActionResolve}, pm) <- useWithStaleFastE CollectRecords nfp
246
+ let records = [ record
247
+ | Just range <- [toCurrentRange pm visibleRange]
248
+ , uid <- RangeMap. elementsInRange range crCodeActions
249
+ , Just record <- [IntMap. lookup uid crCodeActionResolve] ]
250
+ pure $ InL (concatMap (mkInlayHints nameMap) records)
251
+ where
252
+ mkInlayHints :: UniqFM Name [Name ] -> RecordInfo -> [InlayHint ]
253
+ mkInlayHints nameMap record@ (RecordInfoApp _ (RecordAppExpr _ fla)) =
254
+ let textEdits = renderRecordInfoAsTextEdit nameMap record
255
+ in mapMaybe (mkInlayHint textEdits) fla
256
+ mkInlayHints _ _ = []
257
+ mkInlayHint :: Maybe TextEdit -> (Located FieldLabel , HsExpr GhcTc ) -> Maybe InlayHint
258
+ mkInlayHint te (label, _) =
259
+ let (name, loc) = ((flSelector . unLoc) &&& (srcSpanToLocation . getLoc)) label
260
+ fieldDefLoc = srcSpanToLocation (nameSrcSpan name)
261
+ in do
262
+ (Location _ recRange) <- loc
263
+ pure InlayHint { _position = _start recRange
264
+ , _label = InR $ pure (mkInlayHintLabelPart name fieldDefLoc)
265
+ , _kind = Nothing -- neither a type nor a parameter
266
+ , _textEdits = Just (maybeToList te) -- same as CodeAction
267
+ , _tooltip = Just $ InL " Expand positional record" -- same as CodeAction
268
+ , _paddingLeft = Nothing
269
+ , _paddingRight = Nothing
270
+ , _data_ = Nothing
271
+ }
272
+ mkInlayHintLabelPart name loc = InlayHintLabelPart (printOutputable (pprNameUnqualified name) <> " =" ) Nothing loc Nothing
273
+
227
274
mkTitle :: [Extension ] -> Text
228
275
mkTitle exts = " Expand record wildcard"
229
276
<> if NamedFieldPuns `elem` exts
@@ -303,6 +350,7 @@ data CollectRecordsResult = CRR
303
350
304
351
instance NFData CollectRecordsResult
305
352
instance NFData RecordInfo
353
+ instance NFData RecordAppExpr
306
354
307
355
instance Show CollectRecordsResult where
308
356
show _ = " <CollectRecordsResult>"
@@ -325,18 +373,25 @@ instance Show CollectNamesResult where
325
373
326
374
type instance RuleResult CollectNames = CollectNamesResult
327
375
376
+ data RecordAppExpr = RecordAppExpr (LHsExpr GhcTc ) [(Located FieldLabel , HsExpr GhcTc )]
377
+ deriving (Generic )
378
+
328
379
data RecordInfo
329
380
= RecordInfoPat RealSrcSpan (Pat GhcTc )
330
381
| RecordInfoCon RealSrcSpan (HsExpr GhcTc )
382
+ | RecordInfoApp RealSrcSpan RecordAppExpr
331
383
deriving (Generic )
332
384
333
385
instance Pretty RecordInfo where
334
386
pretty (RecordInfoPat ss p) = pretty (printOutputable ss) <> " :" <+> pretty (printOutputable p)
335
387
pretty (RecordInfoCon ss e) = pretty (printOutputable ss) <> " :" <+> pretty (printOutputable e)
388
+ pretty (RecordInfoApp ss (RecordAppExpr _ fla))
389
+ = pretty (printOutputable ss) <> " :" <+> hsep (map (pretty . printOutputable) fla)
336
390
337
391
recordInfoToRange :: RecordInfo -> Range
338
392
recordInfoToRange (RecordInfoPat ss _) = realSrcSpanToRange ss
339
393
recordInfoToRange (RecordInfoCon ss _) = realSrcSpanToRange ss
394
+ recordInfoToRange (RecordInfoApp ss _) = realSrcSpanToRange ss
340
395
341
396
recordInfoToDotDotRange :: RecordInfo -> Maybe Range
342
397
recordInfoToDotDotRange (RecordInfoPat _ (ConPat _ _ (RecCon flds))) = srcSpanToRange . getLoc =<< rec_dotdot flds
@@ -346,10 +401,12 @@ recordInfoToDotDotRange _ = Nothing
346
401
renderRecordInfoAsTextEdit :: UniqFM Name [Name ] -> RecordInfo -> Maybe TextEdit
347
402
renderRecordInfoAsTextEdit names (RecordInfoPat ss pat) = TextEdit (realSrcSpanToRange ss) <$> showRecordPat names pat
348
403
renderRecordInfoAsTextEdit _ (RecordInfoCon ss expr) = TextEdit (realSrcSpanToRange ss) <$> showRecordCon expr
404
+ renderRecordInfoAsTextEdit _ (RecordInfoApp ss appExpr) = TextEdit (realSrcSpanToRange ss) <$> showRecordApp appExpr
349
405
350
- renderRecordInfoAsLabelName :: RecordInfo -> Maybe [Name ]
351
- renderRecordInfoAsLabelName (RecordInfoPat _ pat) = showRecordPatFlds pat
352
- renderRecordInfoAsLabelName (RecordInfoCon _ expr) = showRecordConFlds expr
406
+ renderRecordInfoAsDotdotLabelName :: RecordInfo -> Maybe [Name ]
407
+ renderRecordInfoAsDotdotLabelName (RecordInfoPat _ pat) = showRecordPatFlds pat
408
+ renderRecordInfoAsDotdotLabelName (RecordInfoCon _ expr) = showRecordConFlds expr
409
+ renderRecordInfoAsDotdotLabelName _ = Nothing
353
410
354
411
355
412
-- | Checks if a 'Name' is referenced in the given map of names. The
@@ -468,6 +525,12 @@ showRecordConFlds (RecordCon _ _ flds) =
468
525
getFieldName = getVarName . unLoc . hfbRHS . unLoc
469
526
showRecordConFlds _ = Nothing
470
527
528
+ showRecordApp :: RecordAppExpr -> Maybe Text
529
+ showRecordApp (RecordAppExpr recConstr fla)
530
+ = Just $ printOutputable recConstr <> " { "
531
+ <> T. intercalate " , " (showFieldWithArg <$> fla)
532
+ <> " }"
533
+ where showFieldWithArg (field, arg) = printOutputable field <> " = " <> printOutputable arg
471
534
472
535
collectRecords :: GenericQ [RecordInfo ]
473
536
collectRecords = everythingBut (<>) (([] , False ) `mkQ` getRecPatterns `extQ` getRecCons)
@@ -504,6 +567,23 @@ getRecCons e@(unLoc -> RecordCon _ _ flds)
504
567
mkRecInfo :: LHsExpr GhcTc -> [RecordInfo ]
505
568
mkRecInfo expr =
506
569
[ RecordInfoCon realSpan' (unLoc expr) | RealSrcSpan realSpan' _ <- [ getLoc expr ]]
570
+ getRecCons expr@ (unLoc -> app@ (HsApp _ _ _)) =
571
+ let fieldss = maybeToList $ getFields app []
572
+ recInfo = concatMap mkRecInfo fieldss
573
+ in (recInfo, not (null recInfo))
574
+ where
575
+ mkRecInfo :: RecordAppExpr -> [RecordInfo ]
576
+ mkRecInfo appExpr =
577
+ [ RecordInfoApp realSpan' appExpr | RealSrcSpan realSpan' _ <- [ getLoc expr ] ]
578
+
579
+ getFields :: HsExpr GhcTc -> [LHsExpr GhcTc ] -> Maybe RecordAppExpr
580
+ getFields (HsApp _ constr@ (unLoc -> (XExpr (ConLikeTc (conLikeFieldLabels -> fls) _ _))) arg) args
581
+ | not (null fls)
582
+ = Just (RecordAppExpr constr labelWithArgs)
583
+ where labelWithArgs = zipWith mkLabelWithArg fls (arg : args)
584
+ mkLabelWithArg label arg = (L (getLoc arg) label, unLoc arg)
585
+ getFields (HsApp _ constr arg) args = getFields (unLoc constr) (arg : args)
586
+ getFields _ _ = Nothing
507
587
getRecCons _ = ([] , False )
508
588
509
589
getRecPatterns :: LPat GhcTc -> ([RecordInfo ], Bool )
0 commit comments