Skip to content

Commit 8d7555c

Browse files
joyfulmantisfendor
andauthored
Better plugin error infrastructure (#3717)
--------- Co-authored-by: Fendor <[email protected]>
1 parent 47cf162 commit 8d7555c

File tree

102 files changed

+5845
-4708
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+5845
-4708
lines changed

.hlint.yaml

+13
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@
117117
- Wingman.Judgements
118118
- Wingman.Machinery
119119
- Wingman.Tactics
120+
- CompletionTests #Previously part of GHCIDE Main tests
121+
- DiagnosticTests #Previously part of GHCIDE Main tests
122+
- FindDefinitionAndHoverTests #Previously part of GHCIDE Main tests
123+
- TestUtils #Previously part of GHCIDE Main tests
124+
- CodeLensTests #Previously part of GHCIDE Main tests
120125

121126
- name: [Prelude.tail, Data.List.tail]
122127
within:
@@ -126,6 +131,7 @@
126131
- Development.IDE.Plugin.CodeAction.ExactPrint
127132
- Development.IDE.Session
128133
- UnificationSpec
134+
- WatchedFileTests #Previously part of GHCIDE Main tests
129135

130136
- name: [Prelude.last, Data.List.last]
131137
within:
@@ -137,6 +143,7 @@
137143
- Ide.PluginUtils
138144
- Ide.Plugin.Eval.Parse.Comments
139145
- Ide.Plugin.Eval.CodeLens
146+
- FindDefinitionAndHoverTests #Previously part of GHCIDE Main tests
140147

141148
- name: [Prelude.init, Data.List.init]
142149
within:
@@ -146,6 +153,9 @@
146153
- Wingman.Metaprogramming.Parser
147154
- Development.Benchmark.Rules
148155
- ErrorGivenPartialSignature
156+
- IfaceTests #Previously part of GHCIDE Main tests
157+
- THTests #Previously part of GHCIDE Main tests
158+
- WatchedFileTests #Previously part of GHCIDE Main tests
149159

150160
- name: Data.List.foldl1'
151161
within: []
@@ -164,6 +174,8 @@
164174
- TErrorGivenPartialSignature
165175
- Wingman.CaseSplit
166176
- Wingman.Simplify
177+
- InitializeResponseTests #Previously part of GHCIDE Main tests
178+
- PositionMappingTests #Previously part of GHCIDE Main tests
167179

168180
- name: Data.Text.head
169181
within:
@@ -194,6 +206,7 @@
194206
- Development.IDE.Graph.Internal.Profile
195207
- Development.IDE.Graph.Internal.Rules
196208
- Ide.Plugin.Class
209+
- CodeLensTests #Previously part of GHCIDE Main tests
197210

198211
- name: "Data.Map.!"
199212
within:

ghcide/ghcide.cabal

+35
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ library
154154
Development.IDE.Core.FileUtils
155155
Development.IDE.Core.IdeConfiguration
156156
Development.IDE.Core.OfInterest
157+
Development.IDE.Core.PluginUtils
157158
Development.IDE.Core.PositionMapping
158159
Development.IDE.Core.Preprocessor
159160
Development.IDE.Core.ProgressReporting
@@ -346,6 +347,7 @@ test-suite ghcide-tests
346347
lens,
347348
list-t,
348349
lsp-test ^>= 0.15,
350+
mtl,
349351
monoid-subclasses,
350352
network-uri,
351353
QuickCheck,
@@ -382,6 +384,39 @@ test-suite ghcide-tests
382384
HieDbRetry
383385
Development.IDE.Test
384386
Development.IDE.Test.Diagnostic
387+
ExceptionTests
388+
-- Tests that have been pulled out of the main file
389+
BootTests
390+
CodeLensTests
391+
CompletionTests
392+
CPPTests
393+
CradleTests
394+
DependentFileTest
395+
DiagnosticTests
396+
FindDefinitionAndHoverTests
397+
HaddockTests
398+
HighlightTests
399+
IfaceTests
400+
InitializeResponseTests
401+
LogType
402+
NonLspCommandLine
403+
OutlineTests
404+
PluginParsedResultTests
405+
PluginSimpleTests
406+
PositionMappingTests
407+
PreprocessorTests
408+
RootUriTests
409+
SafeTests
410+
SymlinkTests
411+
TestUtils
412+
THTests
413+
UnitTests
414+
WatchedFileTests
415+
AsyncTests
416+
ClientSettingsTests
417+
ReferenceTests
418+
GarbageCollectionTests
419+
OpenCloseTest
385420
default-extensions:
386421
BangPatterns
387422
DeriveFunctor

ghcide/src/Development/IDE.hs

+2-4
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ module Development.IDE
88

99
import Development.IDE.Core.Actions as X (getAtPoint,
1010
getDefinition,
11-
getTypeDefinition,
12-
useE, useNoFileE,
13-
usesE)
11+
getTypeDefinition)
1412
import Development.IDE.Core.FileExists as X (getFileExists)
1513
import Development.IDE.Core.FileStore as X (getFileContents)
1614
import Development.IDE.Core.IdeConfiguration as X (IdeConfiguration (..),
@@ -55,4 +53,4 @@ import Development.IDE.Types.HscEnvEq as X (HscEnvEq (..),
5553
hscEnv,
5654
hscEnvWithImportPaths)
5755
import Development.IDE.Types.Location as X
58-
import Ide.Logger as X
56+
import Ide.Logger as X

ghcide/src/Development/IDE/Core/Actions.hs

+10-23
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ module Development.IDE.Core.Actions
66
, getTypeDefinition
77
, highlightAtPoint
88
, refsAtPoint
9-
, useE
10-
, useNoFileE
11-
, usesE
129
, workspaceSymbols
1310
, lookupMod
1411
) where
@@ -21,6 +18,7 @@ import Data.Maybe
2118
import qualified Data.Text as T
2219
import Data.Tuple.Extra
2320
import Development.IDE.Core.OfInterest
21+
import Development.IDE.Core.PluginUtils
2422
import Development.IDE.Core.PositionMapping
2523
import Development.IDE.Core.RuleTypes
2624
import Development.IDE.Core.Service
@@ -49,7 +47,7 @@ lookupMod
4947
lookupMod _dbchan _hie_f _mod _uid _boot = MaybeT $ pure Nothing
5048

5149

52-
-- IMPORTANT NOTE : make sure all rules `useE`d by these have a "Persistent Stale" rule defined,
50+
-- IMPORTANT NOTE : make sure all rules `useWithStaleFastMT`d by these have a "Persistent Stale" rule defined,
5351
-- so we can quickly answer as soon as the IDE is opened
5452
-- Even if we don't have persistent information on disk for these rules, the persistent rule
5553
-- should just return an empty result
@@ -62,9 +60,9 @@ getAtPoint file pos = runMaybeT $ do
6260
ide <- ask
6361
opts <- liftIO $ getIdeOptionsIO ide
6462

65-
(hf, mapping) <- useE GetHieAst file
66-
env <- hscEnv . fst <$> useE GhcSession file
67-
dkMap <- lift $ maybe (DKMap mempty mempty) fst <$> runMaybeT (useE GetDocMap file)
63+
(hf, mapping) <- useWithStaleFastMT GetHieAst file
64+
env <- hscEnv . fst <$> useWithStaleFastMT GhcSession file
65+
dkMap <- lift $ maybe (DKMap mempty mempty) fst <$> runMaybeT (useWithStaleFastMT GetDocMap file)
6866

6967
!pos' <- MaybeT (return $ fromCurrentPosition mapping pos)
7068
MaybeT $ pure $ first (toCurrentRange mapping =<<) <$> AtPoint.atPoint opts hf dkMap env pos'
@@ -94,30 +92,19 @@ toCurrentLocations mapping file = mapMaybeM go
9492
else do
9593
otherLocationMapping <- fmap (fmap snd) $ runMaybeT $ do
9694
otherLocationFile <- MaybeT $ pure $ uriToNormalizedFilePath nUri
97-
useE GetHieAst otherLocationFile
95+
useWithStaleFastMT GetHieAst otherLocationFile
9896
pure $ Location uri <$> (flip toCurrentRange range =<< otherLocationMapping)
9997
where
10098
nUri :: NormalizedUri
10199
nUri = toNormalizedUri uri
102100

103-
-- | useE is useful to implement functions that aren’t rules but need shortcircuiting
104-
-- e.g. getDefinition.
105-
useE :: IdeRule k v => k -> NormalizedFilePath -> MaybeT IdeAction (v, PositionMapping)
106-
useE k = MaybeT . useWithStaleFast k
107-
108-
useNoFileE :: IdeRule k v => IdeState -> k -> MaybeT IdeAction v
109-
useNoFileE _ide k = fst <$> useE k emptyFilePath
110-
111-
usesE :: IdeRule k v => k -> [NormalizedFilePath] -> MaybeT IdeAction [(v,PositionMapping)]
112-
usesE k = MaybeT . fmap sequence . mapM (useWithStaleFast k)
113-
114101
-- | Goto Definition.
115102
getDefinition :: NormalizedFilePath -> Position -> IdeAction (Maybe [Location])
116103
getDefinition file pos = runMaybeT $ do
117104
ide@ShakeExtras{ withHieDb, hiedbWriter } <- ask
118105
opts <- liftIO $ getIdeOptionsIO ide
119-
(HAR _ hf _ _ _, mapping) <- useE GetHieAst file
120-
(ImportMap imports, _) <- useE GetImportMap file
106+
(HAR _ hf _ _ _, mapping) <- useWithStaleFastMT GetHieAst file
107+
(ImportMap imports, _) <- useWithStaleFastMT GetImportMap file
121108
!pos' <- MaybeT (pure $ fromCurrentPosition mapping pos)
122109
locations <- AtPoint.gotoDefinition withHieDb (lookupMod hiedbWriter) opts imports hf pos'
123110
MaybeT $ Just <$> toCurrentLocations mapping file locations
@@ -126,14 +113,14 @@ getTypeDefinition :: NormalizedFilePath -> Position -> IdeAction (Maybe [Locatio
126113
getTypeDefinition file pos = runMaybeT $ do
127114
ide@ShakeExtras{ withHieDb, hiedbWriter } <- ask
128115
opts <- liftIO $ getIdeOptionsIO ide
129-
(hf, mapping) <- useE GetHieAst file
116+
(hf, mapping) <- useWithStaleFastMT GetHieAst file
130117
!pos' <- MaybeT (return $ fromCurrentPosition mapping pos)
131118
locations <- AtPoint.gotoTypeDefinition withHieDb (lookupMod hiedbWriter) opts hf pos'
132119
MaybeT $ Just <$> toCurrentLocations mapping file locations
133120

134121
highlightAtPoint :: NormalizedFilePath -> Position -> IdeAction (Maybe [DocumentHighlight])
135122
highlightAtPoint file pos = runMaybeT $ do
136-
(HAR _ hf rf _ _,mapping) <- useE GetHieAst file
123+
(HAR _ hf rf _ _,mapping) <- useWithStaleFastMT GetHieAst file
137124
!pos' <- MaybeT (return $ fromCurrentPosition mapping pos)
138125
let toCurrentHighlight (DocumentHighlight range t) = flip DocumentHighlight t <$> toCurrentRange mapping range
139126
mapMaybe toCurrentHighlight <$>AtPoint.documentHighlight hf rf pos'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
{-# LANGUAGE GADTs #-}
2+
module Development.IDE.Core.PluginUtils where
3+
4+
import Control.Monad.Extra
5+
import Control.Monad.IO.Class
6+
import Control.Monad.Reader (runReaderT)
7+
import Control.Monad.Trans.Except
8+
import Control.Monad.Trans.Maybe
9+
import Data.Functor.Identity
10+
import qualified Data.Text as T
11+
import Development.IDE.Core.PositionMapping
12+
import Development.IDE.Core.Shake (IdeAction, IdeRule,
13+
IdeState (shakeExtras),
14+
mkDelayedAction,
15+
shakeEnqueue)
16+
import qualified Development.IDE.Core.Shake as Shake
17+
import Development.IDE.GHC.Orphans ()
18+
import Development.IDE.Graph hiding (ShakeValue)
19+
import Development.IDE.Types.Location (NormalizedFilePath)
20+
import qualified Development.IDE.Types.Location as Location
21+
import qualified Ide.Logger as Logger
22+
import Ide.Plugin.Error
23+
import qualified Language.LSP.Protocol.Types as LSP
24+
25+
-- ----------------------------------------------------------------------------
26+
-- Action wrappers
27+
-- ----------------------------------------------------------------------------
28+
29+
-- |ExceptT version of `runAction`, takes a ExceptT Action
30+
runActionE :: MonadIO m => String -> IdeState -> ExceptT e Action a -> ExceptT e m a
31+
runActionE herald ide act =
32+
mapExceptT liftIO . ExceptT $
33+
join $ shakeEnqueue (shakeExtras ide) (mkDelayedAction herald Logger.Debug $ runExceptT act)
34+
35+
-- |MaybeT version of `runAction`, takes a MaybeT Action
36+
runActionMT :: MonadIO m => String -> IdeState -> MaybeT Action a -> MaybeT m a
37+
runActionMT herald ide act =
38+
mapMaybeT liftIO . MaybeT $
39+
join $ shakeEnqueue (shakeExtras ide) (mkDelayedAction herald Logger.Debug $ runMaybeT act)
40+
41+
-- |ExceptT version of `use` that throws a PluginRuleFailed upon failure
42+
useE :: IdeRule k v => k -> NormalizedFilePath -> ExceptT PluginError Action v
43+
useE k = maybeToExceptT (PluginRuleFailed (T.pack $ show k)) . useMT k
44+
45+
-- |MaybeT version of `use`
46+
useMT :: IdeRule k v => k -> NormalizedFilePath -> MaybeT Action v
47+
useMT k = MaybeT . Shake.use k
48+
49+
-- |ExceptT version of `uses` that throws a PluginRuleFailed upon failure
50+
usesE :: (Traversable f, IdeRule k v) => k -> f NormalizedFilePath -> ExceptT PluginError Action (f v)
51+
usesE k = maybeToExceptT (PluginRuleFailed (T.pack $ show k)) . usesMT k
52+
53+
-- |MaybeT version of `uses`
54+
usesMT :: (Traversable f, IdeRule k v) => k -> f NormalizedFilePath -> MaybeT Action (f v)
55+
usesMT k xs = MaybeT $ sequence <$> Shake.uses k xs
56+
57+
-- |ExceptT version of `useWithStale` that throws a PluginRuleFailed upon
58+
-- failure
59+
useWithStaleE :: IdeRule k v
60+
=> k -> NormalizedFilePath -> ExceptT PluginError Action (v, PositionMapping)
61+
useWithStaleE key = maybeToExceptT (PluginRuleFailed (T.pack $ show key)) . useWithStaleMT key
62+
63+
-- |MaybeT version of `useWithStale`
64+
useWithStaleMT :: IdeRule k v
65+
=> k -> NormalizedFilePath -> MaybeT Action (v, PositionMapping)
66+
useWithStaleMT key file = MaybeT $ runIdentity <$> Shake.usesWithStale key (Identity file)
67+
68+
-- ----------------------------------------------------------------------------
69+
-- IdeAction wrappers
70+
-- ----------------------------------------------------------------------------
71+
72+
-- |ExceptT version of `runIdeAction`, takes a ExceptT IdeAction
73+
runIdeActionE :: MonadIO m => String -> Shake.ShakeExtras -> ExceptT e IdeAction a -> ExceptT e m a
74+
runIdeActionE _herald s i = ExceptT $ liftIO $ runReaderT (Shake.runIdeActionT $ runExceptT i) s
75+
76+
-- |MaybeT version of `runIdeAction`, takes a MaybeT IdeAction
77+
runIdeActionMT :: MonadIO m => String -> Shake.ShakeExtras -> MaybeT IdeAction a -> MaybeT m a
78+
runIdeActionMT _herald s i = MaybeT $ liftIO $ runReaderT (Shake.runIdeActionT $ runMaybeT i) s
79+
80+
-- |ExceptT version of `useWithStaleFast` that throws a PluginRuleFailed upon
81+
-- failure
82+
useWithStaleFastE :: IdeRule k v => k -> NormalizedFilePath -> ExceptT PluginError IdeAction (v, PositionMapping)
83+
useWithStaleFastE k = maybeToExceptT (PluginRuleFailed (T.pack $ show k)) . useWithStaleFastMT k
84+
85+
-- |MaybeT version of `useWithStaleFast`
86+
useWithStaleFastMT :: IdeRule k v => k -> NormalizedFilePath -> MaybeT IdeAction (v, PositionMapping)
87+
useWithStaleFastMT k = MaybeT . Shake.useWithStaleFast k
88+
89+
-- ----------------------------------------------------------------------------
90+
-- Location wrappers
91+
-- ----------------------------------------------------------------------------
92+
93+
-- |ExceptT version of `uriToFilePath` that throws a PluginInvalidParams upon
94+
-- failure
95+
uriToFilePathE :: Monad m => LSP.Uri -> ExceptT PluginError m FilePath
96+
uriToFilePathE uri = maybeToExceptT (PluginInvalidParams (T.pack $ "uriToFilePath' failed. Uri:" <> show uri)) $ uriToFilePathMT uri
97+
98+
-- |MaybeT version of `uriToFilePath`
99+
uriToFilePathMT :: Monad m => LSP.Uri -> MaybeT m FilePath
100+
uriToFilePathMT = MaybeT . pure . Location.uriToFilePath'
101+
102+
-- ----------------------------------------------------------------------------
103+
-- PositionMapping wrappers
104+
-- ----------------------------------------------------------------------------
105+
106+
-- |ExceptT version of `toCurrentPosition` that throws a PluginInvalidUserState
107+
-- upon failure
108+
toCurrentPositionE :: Monad m => PositionMapping -> LSP.Position -> ExceptT PluginError m LSP.Position
109+
toCurrentPositionE mapping = maybeToExceptT (PluginInvalidUserState "toCurrentPosition"). toCurrentPositionMT mapping
110+
111+
-- |MaybeT version of `toCurrentPosition`
112+
toCurrentPositionMT :: Monad m => PositionMapping -> LSP.Position -> MaybeT m LSP.Position
113+
toCurrentPositionMT mapping = MaybeT . pure . toCurrentPosition mapping
114+
115+
-- |ExceptT version of `fromCurrentPosition` that throws a
116+
-- PluginInvalidUserState upon failure
117+
fromCurrentPositionE :: Monad m => PositionMapping -> LSP.Position -> ExceptT PluginError m LSP.Position
118+
fromCurrentPositionE mapping = maybeToExceptT (PluginInvalidUserState "fromCurrentPosition") . fromCurrentPositionMT mapping
119+
120+
-- |MaybeT version of `fromCurrentPosition`
121+
fromCurrentPositionMT :: Monad m => PositionMapping -> LSP.Position -> MaybeT m LSP.Position
122+
fromCurrentPositionMT mapping = MaybeT . pure . fromCurrentPosition mapping
123+
124+
-- |ExceptT version of `toCurrentRange` that throws a PluginInvalidUserState
125+
-- upon failure
126+
toCurrentRangeE :: Monad m => PositionMapping -> LSP.Range -> ExceptT PluginError m LSP.Range
127+
toCurrentRangeE mapping = maybeToExceptT (PluginInvalidUserState "toCurrentRange") . toCurrentRangeMT mapping
128+
129+
-- |MaybeT version of `toCurrentRange`
130+
toCurrentRangeMT :: Monad m => PositionMapping -> LSP.Range -> MaybeT m LSP.Range
131+
toCurrentRangeMT mapping = MaybeT . pure . toCurrentRange mapping
132+
133+
-- |ExceptT version of `fromCurrentRange` that throws a PluginInvalidUserState
134+
-- upon failure
135+
fromCurrentRangeE :: Monad m => PositionMapping -> LSP.Range -> ExceptT PluginError m LSP.Range
136+
fromCurrentRangeE mapping = maybeToExceptT (PluginInvalidUserState "fromCurrentRange") . fromCurrentRangeMT mapping
137+
138+
-- |MaybeT version of `fromCurrentRange`
139+
fromCurrentRangeMT :: Monad m => PositionMapping -> LSP.Range -> MaybeT m LSP.Range
140+
fromCurrentRangeMT mapping = MaybeT . pure . fromCurrentRange mapping

0 commit comments

Comments
 (0)