@@ -16,7 +16,8 @@ module Ide.Plugin.OverloadedRecordDot
16
16
import Control.Lens ((^.) )
17
17
import Control.Monad.IO.Class (MonadIO , liftIO )
18
18
import Control.Monad.Trans.Except (ExceptT )
19
- import Data.Generics (GenericQ , everything , mkQ )
19
+ import Data.Generics (GenericQ , everything ,
20
+ everythingBut , mkQ )
20
21
import qualified Data.HashMap.Strict as HashMap
21
22
import Data.Maybe (listToMaybe , maybeToList )
22
23
import Data.Text (Text )
@@ -37,11 +38,17 @@ import Development.IDE.GHC.Compat (HsExpr (HsRecSel))
37
38
import Development.IDE.GHC.Compat (HsExpr (HsRecFld ))
38
39
#endif
39
40
41
+ import Control.DeepSeq (rwhnf )
42
+ import Data.Bifunctor (Bifunctor (first ))
40
43
import Development.IDE.GHC.Compat (Extension (OverloadedRecordDot ),
41
44
GhcPass ,
42
- HsExpr (HsApp , HsPar , HsVar , OpApp ),
45
+ HsExpansion (HsExpanded ),
46
+ HsExpr (HsApp , HsPar , HsVar , OpApp , XExpr ),
43
47
LHsExpr , Outputable , Pass (.. ),
44
- RealSrcSpan , getLoc , hs_valds ,
48
+ RealSrcSpan , appPrec ,
49
+ dollarName , getLoc , hs_valds ,
50
+ parenthesizeHsContext ,
51
+ parenthesizeHsExpr ,
45
52
pattern RealSrcSpan , unLoc )
46
53
import Development.IDE.GHC.Util (getExtensions , printOutputable )
47
54
import Development.IDE.Graph (RuleResult )
@@ -71,168 +78,138 @@ import Language.LSP.Types (CodeAction (..),
71
78
normalizedFilePathToUri ,
72
79
type (|? ) (InR ))
73
80
import qualified Language.LSP.Types.Lens as L
74
-
75
81
data Log
76
82
= LogShake Shake. Log
77
- | LogCollectedRecordSelectors [RecordSelectors ]
78
- | LogRenderedRecordSelectors [ ConvertedRecordSelector ]
83
+ | LogCollectedRecordSelectors [RecordSelectorExpr ]
84
+ | LogTextEdits [ TextEdit ]
79
85
80
86
instance Pretty Log where
81
87
pretty = \ case
82
88
LogShake shakeLog -> pretty shakeLog
83
89
LogCollectedRecordSelectors recs -> " Collected record selectors:" <+> pretty recs
84
- LogRenderedRecordSelectors recs -> " Rendered record selectors:" <+> pretty recs
85
90
86
91
data CollectRecordSelectors = CollectRecordSelectors
87
92
deriving (Eq , Show , Generic )
88
93
89
94
instance Hashable CollectRecordSelectors
90
95
instance NFData CollectRecordSelectors
91
96
92
- data CollectConvertedRecordSelectorsResult = CCRSR
93
- { recordInfos :: RangeMap ConvertedRecordSelector
94
- , enabledExtensions :: [GhcExtension ]
97
+ data CollectRecordSelectorsResult = CRSR
98
+ { recordInfos :: RangeMap RecordSelectorExpr
99
+ , enabledExtensions :: [Extension ]
95
100
}
96
101
deriving (Generic )
97
102
98
- instance NFData CollectConvertedRecordSelectorsResult
103
+ instance NFData CollectRecordSelectorsResult
99
104
100
- instance Show CollectConvertedRecordSelectorsResult where
105
+ instance Show CollectRecordSelectorsResult where
101
106
show _ = " <CollectRecordsResult>"
102
107
103
- type instance RuleResult CollectRecordSelectors = CollectConvertedRecordSelectorsResult
104
-
105
- -- `Extension` is wrapped so that we can provide an `NFData` instance
106
- -- (without resorting to creating an orphan instance).
107
- newtype GhcExtension = GhcExtension { unExt :: Extension }
108
-
109
- instance NFData GhcExtension where
110
- rnf x = x `seq` ()
108
+ type instance RuleResult CollectRecordSelectors = CollectRecordSelectorsResult
111
109
112
- data RecordSelectors
113
- = RecordSelectors RealSrcSpan (HsExpr (GhcPass 'Renamed))
114
110
115
- instance Pretty RecordSelectors where
116
- pretty (RecordSelectors ss e) = pretty (printOutputable ss) <> " :" <+> pretty (printOutputable e)
117
-
118
- data ConvertedRecordSelector = ConvertedRecordSelector
119
- { range :: Range
120
- , convertedDotRecord :: Text
121
- }
122
- deriving (Generic )
111
+ data RecordSelectorExpr = RecordSelectorExpr { location :: Range ,
112
+ selectorExpr :: LHsExpr (GhcPass 'Renamed),
113
+ recordExpr :: LHsExpr (GhcPass 'Renamed) }
123
114
124
- instance Pretty ConvertedRecordSelector where
125
- pretty (ConvertedRecordSelector r cdr ) = pretty (show r ) <> " :" <+> pretty cdr
115
+ instance Pretty RecordSelectorExpr where
116
+ pretty (RecordSelectorExpr l rs se ) = pretty (printOutputable rs ) <> " :" <+> pretty (printOutputable se)
126
117
127
- instance NFData ConvertedRecordSelector
118
+ instance NFData RecordSelectorExpr where
119
+ rnf = rwhnf
128
120
129
121
descriptor :: Recorder (WithPriority Log ) -> PluginId -> PluginDescriptor IdeState
130
122
descriptor recorder plId = (defaultPluginDescriptor plId)
131
123
{ pluginHandlers = mkPluginHandler STextDocumentCodeAction codeActionProvider
132
- , pluginRules = collectConvRecSelsRule recorder
124
+ , pluginRules = collectRecSelsRule recorder
133
125
}
134
126
135
127
codeActionProvider :: PluginMethodHandler IdeState 'TextDocumentCodeAction
136
128
codeActionProvider ideState pId (CodeActionParams _ _ caDocId caRange _) = pluginResponse $ do
137
129
nfp <- getNormalizedFilePath (caDocId ^. L. uri)
138
130
pragma <- getFirstPragma pId ideState nfp
139
- CCRSR crsMap (coerce -> exts) <- collectConvRecSels' ideState nfp
140
- let actions = map (mkCodeAction nfp exts pragma) (RangeMap. filterByRange caRange crsMap)
131
+ CRSR crsMap exts <- collectRecSelResult ideState nfp
132
+ let
133
+ pragmaEdit = if OverloadedRecordDot `elem` exts
134
+ then Nothing
135
+ else Just $ insertNewPragma pragma OverloadedRecordDot
136
+ edits crs = maybeToList (convertRecordSelectors crs) <> maybeToList pragmaEdit
137
+ changes crs = Just $ HashMap. singleton (fromNormalizedUri (normalizedFilePathToUri nfp)) (List (edits crs))
138
+ mkCodeAction crs = InR CodeAction
139
+ { _title = mkCodeActionTitle exts crs
140
+ , _kind = Just CodeActionRefactorRewrite
141
+ , _diagnostics = Nothing
142
+ , _isPreferred = Nothing
143
+ , _disabled = Nothing
144
+ , _edit = Just $ WorkspaceEdit (changes crs) Nothing Nothing
145
+ , _command = Nothing
146
+ , _xdata = Nothing
147
+ }
148
+ actions = map mkCodeAction (RangeMap. filterByRange caRange crsMap)
141
149
pure $ List actions
142
150
where
143
- mkCodeAction :: NormalizedFilePath -> [Extension ] -> NextPragmaInfo -> ConvertedRecordSelector -> Command |? CodeAction
144
- mkCodeAction nfp exts pragma crs = InR CodeAction
145
- { _title = mkCodeActionTitle exts
146
- , _kind = Just CodeActionRefactorRewrite
147
- , _diagnostics = Nothing
148
- , _isPreferred = Nothing
149
- , _disabled = Nothing
150
- , _edit = Just $ mkWorkspaceEdit nfp edits
151
- , _command = Nothing
152
- , _xdata = Nothing
153
- }
154
- where
155
- edits = mkTextEdit crs : maybeToList pragmaEdit
156
-
157
- mkTextEdit :: ConvertedRecordSelector -> TextEdit
158
- mkTextEdit (ConvertedRecordSelector r cdr) = TextEdit r cdr
159
-
160
- pragmaEdit :: Maybe TextEdit
161
- pragmaEdit = if OverloadedRecordDot `elem` exts
162
- then Nothing
163
- else Just $ insertNewPragma pragma OverloadedRecordDot
164
-
165
- mkWorkspaceEdit :: NormalizedFilePath -> [TextEdit ] -> WorkspaceEdit
166
- mkWorkspaceEdit nfp edits = WorkspaceEdit changes Nothing Nothing
167
- where
168
- changes = Just $ HashMap. singleton (fromNormalizedUri (normalizedFilePathToUri nfp)) (List edits)
169
-
170
- mkCodeActionTitle :: [Extension ] -> Text
171
- mkCodeActionTitle exts =
151
+ mkCodeActionTitle :: [Extension ] -> RecordSelectorExpr -> Text
152
+ mkCodeActionTitle exts (RecordSelectorExpr _ se _) =
172
153
if OverloadedRecordDot `elem` exts
173
154
then title
174
155
else title <> " (needs extension: OverloadedRecordDot)"
175
156
where
176
- title = " Convert to record dot syntax"
157
+ title = " Convert `" <> name <> " ` to record dot syntax"
158
+ name = printOutputable se
177
159
178
- collectConvRecSelsRule :: Recorder (WithPriority Log ) -> Rules ()
179
- collectConvRecSelsRule recorder = define (cmapWithPrio LogShake recorder) $ \ CollectRecordSelectors nfp ->
160
+ collectRecSelsRule :: Recorder (WithPriority Log ) -> Rules ()
161
+ collectRecSelsRule recorder = define (cmapWithPrio LogShake recorder) $ \ CollectRecordSelectors nfp ->
180
162
use TypeCheck nfp >>= \ case
181
163
Nothing -> pure ([] , Nothing )
182
164
Just tmr -> do
183
165
let exts = getEnabledExtensions tmr
184
166
recSels = getRecordSelectors tmr
185
167
logWith recorder Debug (LogCollectedRecordSelectors recSels)
186
- let convertedRecordSelectors = traverse convertRecordSelectors recSels
187
- crsMap = RangeMap. fromList range <$> convertedRecordSelectors
188
- logWith recorder Debug (LogRenderedRecordSelectors (concat convertedRecordSelectors))
189
- pure ([] , CCRSR <$> crsMap <*> Just exts)
168
+ let crsMap :: RangeMap RecordSelectorExpr
169
+ crsMap = RangeMap. fromList location recSels
170
+ pure ([] , CRSR <$> Just crsMap <*> Just exts)
190
171
where
191
- getEnabledExtensions :: TcModuleResult -> [GhcExtension ]
192
- getEnabledExtensions = map GhcExtension . getExtensions . tmrParsed
172
+ getEnabledExtensions :: TcModuleResult -> [Extension ]
173
+ getEnabledExtensions = getExtensions . tmrParsed
193
174
194
- getRecordSelectors :: TcModuleResult -> [RecordSelectors ]
175
+ getRecordSelectors :: TcModuleResult -> [RecordSelectorExpr ]
195
176
getRecordSelectors (tmrRenamed -> (hs_valds -> valBinds,_,_,_)) =
196
177
collectRecordSelectors valBinds
197
178
198
- convertRecordSelectors :: RecordSelectors -> Maybe ConvertedRecordSelector
199
- convertRecordSelectors (RecordSelectors ss expr ) = ConvertedRecordSelector (realSrcSpanToRange ss) <$> convertRecSel expr
179
+ convertRecordSelectors :: RecordSelectorExpr -> Maybe TextEdit
180
+ convertRecordSelectors (RecordSelectorExpr l se re ) = TextEdit l <$> convertRecSel se re
200
181
201
- convertRecSel :: Outputable (HsExpr (GhcPass c )) => HsExpr (GhcPass c ) -> Maybe Text
202
- convertRecSel (HsApp _ s r) =
203
- Just $ printOutputable r <> " ." <> printOutputable s
204
- convertRecSel ( OpApp _ s _ r) =
205
- Just $ " (" <> printOutputable r <> " )." <> printOutputable s
206
- convertRecSel _ = Nothing
182
+ convertRecSel :: Outputable (LHsExpr (GhcPass 'Renamed)) => LHsExpr (GhcPass 'Renamed) -> LHsExpr (GhcPass 'Renamed) -> Maybe Text
183
+ convertRecSel se re =
184
+ Just $ printOutputable (parenthesizeHsExpr appPrec re) <> " ." <> printOutputable se
207
185
208
- collectRecordSelectors :: GenericQ [RecordSelectors ]
209
- collectRecordSelectors = everything (<>) (maybeToList . (Nothing `mkQ` getRecSels))
186
+ -- It's important that we use everthingBut here, because if we used everything we would
187
+ -- get duplicates for every case that occures inside a HsExpanded expression.
188
+ collectRecordSelectors :: GenericQ [RecordSelectorExpr ]
189
+ collectRecordSelectors = everythingBut (<>) (first maybeToList. ((Nothing , False ) `mkQ` getRecSels))
210
190
191
+ getRecSels :: LHsExpr (GhcPass 'Renamed) -> (Maybe RecordSelectorExpr , Bool )
192
+ -- When we stumble upon an occurance of HsExpanded, we only want to follow one branch
193
+ -- we do this here, by explicitly returning an occurance from traversing the original branch,
194
+ -- and returning True, which keeps syb from implicitly continuing to traverse.
195
+ getRecSels (unLoc -> XExpr (HsExpanded a _)) = (listToMaybe $ collectRecordSelectors a, True )
211
196
#if __GLASGOW_HASKELL__ >= 903
212
- getRecSels :: LHsExpr (GhcPass 'Renamed) -> Maybe RecordSelectors
213
- -- standard record selection: "field record"
214
- getRecSels e@ (unLoc -> HsApp _ (unLoc -> HsRecSel _ _) (unLoc -> HsVar _ _)) =
215
- listToMaybe [ RecordSelectors realSpan' (unLoc e) | RealSrcSpan realSpan' _ <- [ getLoc e ]]
216
- -- Record selection where the field is being applied to a parenthesised expression: "field (record)"
217
- getRecSels e@ (unLoc -> HsApp _ (unLoc -> HsRecSel _ _) (unLoc -> HsPar _ _ _ _)) =
218
- listToMaybe [ RecordSelectors realSpan' (unLoc e) | RealSrcSpan realSpan' _ <- [ getLoc e ]]
197
+ -- applied record selection: "field record" or "field (record)" or "field field.record"
198
+ getRecSels e@ (unLoc -> HsApp _ se@ (unLoc -> HsRecSel _ _) re) =
199
+ (listToMaybe [ RecordSelectorExpr (realSrcSpanToRange realSpan') se re | RealSrcSpan realSpan' _ <- [ getLoc e ]], False )
219
200
-- Record selection where the field is being applied with the "$" operator: "field $ record"
220
- getRecSels e@ (unLoc -> OpApp _ (unLoc -> HsRecSel _ _) (unLoc -> HsVar _ (unLoc -> d)) _)
221
- | printOutputable d == " $" = listToMaybe [ RecordSelectors realSpan' (unLoc e) | RealSrcSpan realSpan' _ <- [ getLoc e ]]
222
- getRecSels _ = Nothing
201
+ getRecSels e@ (unLoc -> OpApp _ se@ (unLoc -> HsRecSel _ _) (unLoc -> HsVar _ (unLoc -> d)) re)
202
+ | d == dollarName = (listToMaybe [ RecordSelectorExpr (realSrcSpanToRange realSpan') se re | RealSrcSpan realSpan' _ <- [ getLoc e ]], False )
223
203
#else
224
- getRecSels :: LHsExpr (GhcPass 'Renamed) -> Maybe RecordSelectors
225
- getRecSels e@ (unLoc -> HsApp _ (unLoc -> HsRecFld _ _) (unLoc -> HsVar _ _)) =
226
- listToMaybe [ RecordSelectors realSpan' (unLoc e) | RealSrcSpan realSpan' _ <- [ getLoc e ]]
227
- getRecSels e@ (unLoc -> HsApp _ (unLoc -> HsRecFld _ _) (unLoc -> HsPar _ _)) =
228
- listToMaybe [ RecordSelectors realSpan' (unLoc e) | RealSrcSpan realSpan' _ <- [ getLoc e ]]
229
- getRecSels e@ (unLoc -> OpApp _ (unLoc -> HsRecFld _ _) (unLoc -> HsVar _ (unLoc -> d)) _)
230
- | printOutputable d == " $" = listToMaybe [ RecordSelectors realSpan' (unLoc e) | RealSrcSpan realSpan' _ <- [ getLoc e ]]
231
- getRecSels _ = Nothing
204
+ getRecSels e@ (unLoc -> HsApp _ se@ (unLoc -> HsRecFld _ _) re) =
205
+ (listToMaybe [ RecordSelectorExpr (realSrcSpanToRange realSpan') se re | RealSrcSpan realSpan' _ <- [ getLoc e ]], False )
206
+ getRecSels e@ (unLoc -> OpApp _ se@ (unLoc -> HsRecFld _ _) (unLoc -> HsVar _ (unLoc -> d)) re)
207
+ | d == dollarName = (listToMaybe [ RecordSelectorExpr (realSrcSpanToRange realSpan') se re | RealSrcSpan realSpan' _ <- [ getLoc e ]], False )
232
208
#endif
209
+ getRecSels _ = (Nothing , False )
233
210
234
- collectConvRecSels' :: MonadIO m => IdeState -> NormalizedFilePath -> ExceptT String m CollectConvertedRecordSelectorsResult
235
- collectConvRecSels' ideState =
211
+ collectRecSelResult :: MonadIO m => IdeState -> NormalizedFilePath -> ExceptT String m CollectRecordSelectorsResult
212
+ collectRecSelResult ideState =
236
213
handleMaybeM " Unable to TypeCheck"
237
214
. liftIO
238
215
. runAction " overloadedRecordDot.collectRecordSelectors" ideState
0 commit comments