@@ -12,52 +12,60 @@ module Ide.Plugin.ExplicitFields
1212 , Log
1313 ) where
1414
15+ import Control.Arrow ((&&&) )
1516import Control.Lens ((&) , (?~) , (^.) )
17+ import Control.Monad (replicateM )
1618import Control.Monad.IO.Class (MonadIO (liftIO ))
19+ import Control.Monad.Trans.Class (lift )
1720import Control.Monad.Trans.Maybe
21+ import Data.Aeson (ToJSON (toJSON ))
1822import Data.Generics (GenericQ , everything ,
1923 everythingBut , extQ , mkQ )
2024import qualified Data.IntMap.Strict as IntMap
25+ import Data.List (find , intersperse )
2126import qualified Data.Map as Map
2227import Data.Maybe (fromMaybe , isJust ,
2328 mapMaybe , maybeToList )
2429import 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 )
3130import qualified Data.Text as T
31+ import Data.Unique (hashUnique , newUnique )
3232import Development.IDE (IdeState ,
3333 Location (Location ),
3434 Pretty (.. ),
3535 Range (Range , _end , _start ),
3636 Recorder (.. ), Rules ,
3737 WithPriority (.. ),
3838 defineNoDiagnostics ,
39- getDefinition , printName ,
39+ getDefinition , hsep ,
40+ printName ,
4041 realSrcSpanToRange ,
4142 shakeExtras ,
43+ srcSpanToLocation ,
4244 srcSpanToRange , viaShow )
4345import Development.IDE.Core.PluginUtils
4446import Development.IDE.Core.PositionMapping (toCurrentRange )
4547import Development.IDE.Core.RuleTypes (TcModuleResult (.. ),
4648 TypeCheck (.. ))
4749import 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 ,
5054 HasSrcSpan (getLoc ),
5155 HsConDetails (RecCon ),
52- HsExpr (HsVar , XExpr ),
56+ HsExpr (HsApp , HsVar , XExpr ),
5357 HsFieldBind (hfbLHS ),
5458 HsRecFields (.. ),
5559 Identifier , LPat ,
60+ Located ,
5661 NamedThing (getName ),
5762 Outputable ,
5863 TcGblEnv (tcg_binds ),
5964 Var (varName ),
6065 XXExprGhcTc (.. ),
66+ conLikeFieldLabels ,
67+ nameSrcSpan ,
68+ pprNameUnqualified ,
6169 recDotDot , unLoc )
6270import Development.IDE.GHC.Compat.Core (Extension (NamedFieldPuns ),
6371 HsExpr (RecordCon , rcon_flds ),
@@ -129,9 +137,10 @@ descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeSta
129137descriptor recorder plId =
130138 let resolveRecorder = cmapWithPrio LogResolve recorder
131139 (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)
133142 in (defaultPluginDescriptor plId " Provides a code action to make record wildcards explicit" )
134- { pluginHandlers = caHandlers <> ihHandlers
143+ { pluginHandlers = caHandlers <> ihDotdotHandler <> ihPosRecHandler
135144 , pluginCommands = carCommands
136145 , pluginRules = collectRecordsRule recorder *> collectNamesRule
137146 }
@@ -145,9 +154,9 @@ codeActionProvider ideState _ (CodeActionParams _ _ docId range _) = do
145154 let actions = map (mkCodeAction enabledExtensions) (RangeMap. filterByRange range crCodeActions)
146155 pure $ InL actions
147156 where
148- mkCodeAction :: [Extension ] -> Int -> Command |? CodeAction
157+ mkCodeAction :: [Extension ] -> Int -> Command |? CodeAction
149158 mkCodeAction exts uid = InR CodeAction
150- { _title = mkTitle exts
159+ { _title = mkTitle exts -- TODO: `Expand positional record` without NamedFieldPuns if RecordInfoApp
151160 , _kind = Just CodeActionKind_RefactorRewrite
152161 , _diagnostics = Nothing
153162 , _isPreferred = Nothing
@@ -167,15 +176,19 @@ codeActionResolveProvider ideState pId ca uri uid = do
167176 record <- handleMaybe PluginStaleResolve $ IntMap. lookup uid crCodeActionResolve
168177 -- We should never fail to render
169178 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))
172185 pure $ ca & L. edit ?~ mkWorkspaceEdit edits
173186 where
174187 mkWorkspaceEdit :: [TextEdit ] -> WorkspaceEdit
175188 mkWorkspaceEdit edits = WorkspaceEdit (Just $ Map. singleton uri edits) Nothing Nothing
176189
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
179192 nfp <- getNormalizedFilePathE uri
180193 pragma <- getFirstPragma pId state nfp
181194 runIdeActionE " ExplicitFields.CollectRecords" (shakeExtras state) $ do
@@ -186,18 +199,18 @@ inlayHintProvider _ state pId InlayHintParams {_textDocument = TextDocumentIdent
186199 , uid <- RangeMap. elementsInRange range crCodeActions
187200 , Just record <- [IntMap. lookup uid crCodeActionResolve] ]
188201 -- Get the definition of each dotdot of record
189- locations = [ getDefinition nfp pos
202+ locations = [ fmap (,record) ( getDefinition nfp pos)
190203 | record <- records
191204 , pos <- maybeToList $ fmap _start $ recordInfoToDotDotRange record ]
192205 defnLocsList <- lift $ sequence locations
193- pure $ InL $ mapMaybe (mkInlayHints crr pragma) ( zip defnLocsList records)
206+ pure $ InL $ mapMaybe (mkInlayHint crr pragma) defnLocsList
194207 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) =
197210 let range = recordInfoToDotDotRange record
198211 textEdits = maybeToList (renderRecordInfoAsTextEdit nameMap record)
199212 <> maybeToList (pragmaEdit enabledExtensions pragma)
200- names = renderRecordInfoAsLabelName record
213+ names = renderRecordInfoAsDotdotLabelName record
201214 in do
202215 end <- fmap _end range
203216 names' <- names
@@ -224,6 +237,40 @@ inlayHintProvider _ state pId InlayHintParams {_textDocument = TextDocumentIdent
224237 }
225238 mkInlayHintLabelPart (value, loc) = InlayHintLabelPart value Nothing loc Nothing
226239
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+
227274mkTitle :: [Extension ] -> Text
228275mkTitle exts = " Expand record wildcard"
229276 <> if NamedFieldPuns `elem` exts
@@ -303,6 +350,7 @@ data CollectRecordsResult = CRR
303350
304351instance NFData CollectRecordsResult
305352instance NFData RecordInfo
353+ instance NFData RecordAppExpr
306354
307355instance Show CollectRecordsResult where
308356 show _ = " <CollectRecordsResult>"
@@ -325,18 +373,25 @@ instance Show CollectNamesResult where
325373
326374type instance RuleResult CollectNames = CollectNamesResult
327375
376+ data RecordAppExpr = RecordAppExpr (LHsExpr GhcTc ) [(Located FieldLabel , HsExpr GhcTc )]
377+ deriving (Generic )
378+
328379data RecordInfo
329380 = RecordInfoPat RealSrcSpan (Pat GhcTc )
330381 | RecordInfoCon RealSrcSpan (HsExpr GhcTc )
382+ | RecordInfoApp RealSrcSpan RecordAppExpr
331383 deriving (Generic )
332384
333385instance Pretty RecordInfo where
334386 pretty (RecordInfoPat ss p) = pretty (printOutputable ss) <> " :" <+> pretty (printOutputable p)
335387 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)
336390
337391recordInfoToRange :: RecordInfo -> Range
338392recordInfoToRange (RecordInfoPat ss _) = realSrcSpanToRange ss
339393recordInfoToRange (RecordInfoCon ss _) = realSrcSpanToRange ss
394+ recordInfoToRange (RecordInfoApp ss _) = realSrcSpanToRange ss
340395
341396recordInfoToDotDotRange :: RecordInfo -> Maybe Range
342397recordInfoToDotDotRange (RecordInfoPat _ (ConPat _ _ (RecCon flds))) = srcSpanToRange . getLoc =<< rec_dotdot flds
@@ -346,10 +401,12 @@ recordInfoToDotDotRange _ = Nothing
346401renderRecordInfoAsTextEdit :: UniqFM Name [Name ] -> RecordInfo -> Maybe TextEdit
347402renderRecordInfoAsTextEdit names (RecordInfoPat ss pat) = TextEdit (realSrcSpanToRange ss) <$> showRecordPat names pat
348403renderRecordInfoAsTextEdit _ (RecordInfoCon ss expr) = TextEdit (realSrcSpanToRange ss) <$> showRecordCon expr
404+ renderRecordInfoAsTextEdit _ (RecordInfoApp ss appExpr) = TextEdit (realSrcSpanToRange ss) <$> showRecordApp appExpr
349405
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
353410
354411
355412-- | Checks if a 'Name' is referenced in the given map of names. The
@@ -468,6 +525,12 @@ showRecordConFlds (RecordCon _ _ flds) =
468525 getFieldName = getVarName . unLoc . hfbRHS . unLoc
469526showRecordConFlds _ = Nothing
470527
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
471534
472535collectRecords :: GenericQ [RecordInfo ]
473536collectRecords = everythingBut (<>) (([] , False ) `mkQ` getRecPatterns `extQ` getRecCons)
@@ -504,6 +567,23 @@ getRecCons e@(unLoc -> RecordCon _ _ flds)
504567 mkRecInfo :: LHsExpr GhcTc -> [RecordInfo ]
505568 mkRecInfo expr =
506569 [ 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
507587getRecCons _ = ([] , False )
508588
509589getRecPatterns :: LPat GhcTc -> ([RecordInfo ], Bool )
0 commit comments