1
+ {-# LANGUAGE CPP #-}
1
2
{-# LANGUAGE LambdaCase #-}
2
3
{-# LANGUAGE ViewPatterns #-}
3
4
-- | An HLS plugin to provide code actions to change type signatures
4
5
module Ide.Plugin.ChangeTypeSignature (descriptor
5
6
-- * For Unit Tests
7
+ , Log (.. )
6
8
, errorMessageRegexes
7
9
) where
8
10
9
- import Control.Monad (guard )
10
- import Control.Monad.IO.Class (MonadIO )
11
- import Control.Monad.Trans.Except (ExceptT )
12
- import Data.Foldable (asum )
13
- import qualified Data.Map as Map
14
- import Data.Maybe (mapMaybe )
15
- import Data.Text (Text )
16
- import qualified Data.Text as T
17
- import Development.IDE (realSrcSpanToRange )
11
+ import Control.Lens
12
+ import Control.Monad (guard )
13
+ import Control.Monad.IO.Class (MonadIO )
14
+ import Control.Monad.Trans.Class (MonadTrans (lift ))
15
+ import Control.Monad.Trans.Except (ExceptT (.. ))
16
+ import Control.Monad.Trans.Maybe (MaybeT (.. ), hoistMaybe )
17
+ import Data.Foldable (asum )
18
+ import qualified Data.Map as Map
19
+ import Data.Maybe (catMaybes )
20
+ import Data.Text (Text )
21
+ import qualified Data.Text as T
22
+ import Development.IDE (FileDiagnostic ,
23
+ IdeState (.. ), Pretty (.. ),
24
+ Priority (.. ), Recorder ,
25
+ WithPriority ,
26
+ fdLspDiagnosticL ,
27
+ fdStructuredMessageL ,
28
+ logWith , realSrcSpanToRange )
18
29
import Development.IDE.Core.PluginUtils
19
- import Development.IDE.Core.RuleTypes (GetParsedModule (GetParsedModule ))
20
- import Development.IDE.Core.Service (IdeState )
21
- import Development.IDE.GHC.Compat
22
- import Development.IDE.GHC.Util (printOutputable )
23
- import Generics.SYB (extQ , something )
24
- import Ide.Plugin.Error (PluginError ,
25
- getNormalizedFilePathE )
26
- import Ide.Types (PluginDescriptor (.. ),
27
- PluginId (PluginId ),
28
- PluginMethodHandler ,
29
- defaultPluginDescriptor ,
30
- mkPluginHandler )
30
+ import Development.IDE.Core.RuleTypes (GetParsedModule (GetParsedModule ))
31
+ import Development.IDE.GHC.Compat hiding (vcat )
32
+ import Development.IDE.GHC.Compat.Error (_TcRnMessage ,
33
+ msgEnvelopeErrorL )
34
+ import Development.IDE.GHC.Util (printOutputable )
35
+ import Development.IDE.Types.Diagnostics (_SomeStructuredMessage )
36
+ import Generics.SYB (extQ , something )
37
+ import GHC.Tc.Errors.Types (ErrInfo (.. ),
38
+ MismatchMsg (.. ),
39
+ SolverReportWithCtxt (.. ),
40
+ TcRnMessage (.. ),
41
+ TcRnMessageDetailed (.. ),
42
+ TcSolverReportMsg (.. ))
43
+ import qualified Ide.Logger as Logger
44
+ import Ide.Plugin.Error (PluginError ,
45
+ getNormalizedFilePathE )
46
+ import Ide.Types (Config , HandlerM ,
47
+ PluginDescriptor (.. ),
48
+ PluginId (PluginId ),
49
+ PluginMethodHandler ,
50
+ defaultPluginDescriptor ,
51
+ mkPluginHandler )
31
52
import Language.LSP.Protocol.Message
32
53
import Language.LSP.Protocol.Types
33
- import Text.Regex.TDFA ((=~) )
54
+ import Text.Regex.TDFA ((=~) )
34
55
35
- descriptor :: PluginId -> PluginDescriptor IdeState
36
- descriptor plId = (defaultPluginDescriptor plId " Provides a code action to change the type signature of a binding if it is wrong " )
37
- { pluginHandlers = mkPluginHandler SMethod_TextDocumentCodeAction (codeActionHandler plId) }
56
+ data Log
57
+ = LogErrInfoCtxt ErrInfo
58
+ | LogFindSigLocFailure DeclName
38
59
39
- codeActionHandler :: PluginId -> PluginMethodHandler IdeState 'Method_TextDocumentCodeAction
40
- codeActionHandler plId ideState _ CodeActionParams {_textDocument = TextDocumentIdentifier uri, _context = CodeActionContext diags _ _} = do
41
- nfp <- getNormalizedFilePathE uri
42
- decls <- getDecls plId ideState nfp
43
- let actions = mapMaybe (generateAction plId uri decls) diags
44
- pure $ InL actions
60
+ instance Pretty Log where
61
+ pretty = \ case
62
+ LogErrInfoCtxt (ErrInfo ctxt suppl) ->
63
+ Logger. vcat [fromSDoc ctxt, fromSDoc suppl]
64
+ LogFindSigLocFailure name ->
65
+ pretty (" Lookup signature location failure: " <> name)
66
+ where
67
+ fromSDoc = pretty . printOutputable
68
+
69
+ descriptor :: Recorder (WithPriority Log ) -> PluginId -> PluginDescriptor IdeState
70
+ descriptor recorder plId =
71
+ (defaultPluginDescriptor plId " Provides a code action to change the type signature of a binding if it is wrong" )
72
+ { pluginHandlers = mkPluginHandler SMethod_TextDocumentCodeAction (codeActionHandler recorder plId)
73
+ }
74
+
75
+ codeActionHandler
76
+ :: Recorder (WithPriority Log )
77
+ -> PluginId
78
+ -> PluginMethodHandler IdeState 'Method_TextDocumentCodeAction
79
+ codeActionHandler recorder plId ideState _ CodeActionParams {_textDocument, _range} = do
80
+ let TextDocumentIdentifier uri = _textDocument
81
+ nfp <- getNormalizedFilePathE uri
82
+ decls <- getDecls plId ideState nfp
83
+
84
+ activeDiagnosticsInRange (shakeExtras ideState) nfp _range >>= \ case
85
+ Nothing -> pure (InL [] )
86
+ Just fileDiags -> do
87
+ actions <- lift $ mapM (generateAction recorder plId uri decls) fileDiags
88
+ pure (InL (catMaybes actions))
45
89
46
90
getDecls :: MonadIO m => PluginId -> IdeState -> NormalizedFilePath -> ExceptT PluginError m [LHsDecl GhcPs ]
47
91
getDecls (PluginId changeTypeSignatureId) state =
@@ -67,39 +111,104 @@ data ChangeSignature = ChangeSignature {
67
111
-- | the location of the declaration signature
68
112
, declSrcSpan :: RealSrcSpan
69
113
-- | the diagnostic to solve
70
- , diagnostic :: Diagnostic
114
+ , diagnostic :: FileDiagnostic
71
115
}
72
116
73
117
-- | Create a CodeAction from a Diagnostic
74
- generateAction :: PluginId -> Uri -> [LHsDecl GhcPs ] -> Diagnostic -> Maybe (Command |? CodeAction )
75
- generateAction plId uri decls diag = changeSigToCodeAction plId uri <$> diagnosticToChangeSig decls diag
118
+ generateAction
119
+ :: Recorder (WithPriority Log )
120
+ -> PluginId
121
+ -> Uri
122
+ -> [LHsDecl GhcPs ]
123
+ -> FileDiagnostic
124
+ -> HandlerM Config (Maybe (Command |? CodeAction ))
125
+ generateAction recorder plId uri decls fileDiag = do
126
+ changeSig <- diagnosticToChangeSig recorder decls fileDiag
127
+ pure $
128
+ changeSigToCodeAction plId uri <$> changeSig
76
129
77
130
-- | Convert a diagnostic into a ChangeSignature and add the proper SrcSpan
78
- diagnosticToChangeSig :: [LHsDecl GhcPs ] -> Diagnostic -> Maybe ChangeSignature
79
- diagnosticToChangeSig decls diagnostic = do
80
- -- regex match on the GHC Error Message
81
- (expectedType, actualType, declName) <- matchingDiagnostic diagnostic
82
- -- Find the definition and it's location
83
- declSrcSpan <- findSigLocOfStringDecl decls expectedType (T. unpack declName)
84
- pure $ ChangeSignature {.. }
131
+ diagnosticToChangeSig
132
+ :: Recorder (WithPriority Log )
133
+ -> [LHsDecl GhcPs ]
134
+ -> FileDiagnostic
135
+ -> HandlerM Config (Maybe ChangeSignature )
136
+ diagnosticToChangeSig recorder decls diagnostic = runMaybeT $ do
137
+ -- Extract expected, actual, and extra error info
138
+ (expectedType, actualType, errInfo) <- hoistMaybe $ do
139
+ msg <- diagnostic ^. fdStructuredMessageL ^? _SomeStructuredMessage
140
+ tcRnMsg <- msg ^. msgEnvelopeErrorL ^? _TcRnMessage
141
+ (solverReport, errInfo) <- findSolverReport tcRnMsg
142
+ mismatch <- findMismatchMessage solverReport
143
+ (expectedType', actualType') <- findTypeEqMismatch mismatch
144
+ errInfo' <- errInfo
145
+
146
+ pure (showType expectedType', showType actualType', errInfo')
147
+
148
+ logWith recorder Debug (LogErrInfoCtxt errInfo)
149
+
150
+ -- Extract the declName from the extra error text
151
+ declName <- hoistMaybe (matchingDiagnostic errInfo)
85
152
153
+ -- Look up location of declName. If it fails, log it
154
+ declSrcSpan <-
155
+ case findSigLocOfStringDecl decls expectedType (T. unpack declName) of
156
+ Just x -> pure x
157
+ Nothing -> do
158
+ logWith recorder Debug (LogFindSigLocFailure declName)
159
+ hoistMaybe Nothing
160
+
161
+ pure ChangeSignature {.. }
162
+ where
163
+ showType :: Type -> Text
164
+ showType = T. pack . showSDocUnsafe . pprTidiedType
165
+
166
+ -- TODO: Make this a prism?
167
+ findSolverReport :: TcRnMessage -> Maybe (TcSolverReportMsg , Maybe ErrInfo )
168
+ findSolverReport (TcRnMessageWithInfo _ (TcRnMessageDetailed errInfo msg)) =
169
+ case findSolverReport msg of
170
+ Just (mismatch, _) -> Just (mismatch, Just errInfo)
171
+ _ -> Nothing
172
+ #if MIN_VERSION_ghc(9,10,0)
173
+ findSolverReport (TcRnSolverReport (SolverReportWithCtxt _ mismatch) _) =
174
+ Just (mismatch, Nothing )
175
+ #else
176
+ findSolverReport (TcRnSolverReport (SolverReportWithCtxt _ mismatch) _ _) =
177
+ Just (mismatch, Nothing )
178
+ #endif
179
+ findSolverReport _ = Nothing
180
+
181
+ -- TODO: Make this a prism?
182
+ findMismatchMessage :: TcSolverReportMsg -> Maybe MismatchMsg
183
+ findMismatchMessage (Mismatch m _ _ _) = Just m
184
+ findMismatchMessage (CannotUnifyVariable m _) = Just m
185
+ findMismatchMessage _ = Nothing
186
+
187
+ -- TODO: Make this a prism?
188
+ findTypeEqMismatch :: MismatchMsg -> Maybe (Type , Type )
189
+ #if MIN_VERSION_ghc(9,12,0)
190
+ findTypeEqMismatch (TypeEqMismatch _ _ _ expected actual _ _) = Just (expected, actual)
191
+ #else
192
+ findTypeEqMismatch (TypeEqMismatch _ _ _ _ expected actual _ _) = Just (expected, actual)
193
+ #endif
194
+ findTypeEqMismatch _ = Nothing
86
195
87
196
-- | If a diagnostic has the proper message create a ChangeSignature from it
88
- matchingDiagnostic :: Diagnostic -> Maybe (ExpectedSig , ActualSig , DeclName )
89
- matchingDiagnostic Diagnostic {_message} = asum $ map (unwrapMatch . (=~) _message) errorMessageRegexes
197
+ matchingDiagnostic :: ErrInfo -> Maybe DeclName
198
+ matchingDiagnostic ErrInfo {errInfoContext} =
199
+ asum $ map (unwrapMatch . (=~) errInfoTxt) errorMessageRegexes
90
200
where
91
- unwrapMatch :: (Text , Text , Text , [Text ]) -> Maybe (ExpectedSig , ActualSig , DeclName )
92
- -- due to using (.|\n) in regex we have to drop the erroneous, but necessary ("." doesn't match newlines), match
93
- unwrapMatch (_, _, _, [expect, actual, _, name]) = Just (expect, actual, name)
94
- unwrapMatch _ = Nothing
201
+ unwrapMatch :: (Text , Text , Text , [Text ]) -> Maybe DeclName
202
+ unwrapMatch (_, _, _, [name]) = Just name
203
+ unwrapMatch _ = Nothing
204
+
205
+ errInfoTxt = printOutputable errInfoContext
95
206
96
207
-- | List of regexes that match various Error Messages
97
208
errorMessageRegexes :: [Text ]
98
209
errorMessageRegexes = [ -- be sure to add new Error Messages Regexes at the bottom to not fail any existing tests
99
- " Expected type: (.+)\n +Actual type: (.+)\n (.|\n )+In an equation for ‘(.+)’"
100
- , " Couldn't match expected type ‘(.+)’ with actual type ‘(.+)’\n (.|\n )+In an equation for ‘(.+)’"
101
- -- GHC >9.2 version of the first error regex
102
- , " Expected: (.+)\n +Actual: (.+)\n (.|\n )+In an equation for ‘(.+)’"
210
+ " In an equation for ‘(.+)’:" -- TODO: Check if this is useful only for tests
211
+ , " In an equation for `(.+)':"
103
212
]
104
213
105
214
-- | Given a String with the name of a declaration, GHC's "Expected Type", find the declaration that matches
@@ -147,7 +256,7 @@ changeSigToCodeAction :: PluginId -> Uri -> ChangeSignature -> Command |? CodeAc
147
256
changeSigToCodeAction (PluginId changeTypeSignatureId) uri ChangeSignature {.. } =
148
257
InR CodeAction { _title = mkChangeSigTitle declName actualType
149
258
, _kind = Just (CodeActionKind_Custom (" quickfix." <> changeTypeSignatureId))
150
- , _diagnostics = Just [diagnostic]
259
+ , _diagnostics = Just [diagnostic ^. fdLspDiagnosticL ]
151
260
, _isPreferred = Nothing
152
261
, _disabled = Nothing
153
262
, _edit = Just $ mkChangeSigEdit uri declSrcSpan (mkNewSignature declName actualType)
0 commit comments