Skip to content

Commit d7a745e

Browse files
Wingman: streaming tactic solutions (#2102)
* WIP abstract LSP, take the pain out of writing LSP stuff * Finish making commands * Separate code lenses and actions * Pull out types * Finalize the abstract API * Bug fix in JSON; first connected abstract handler * Add ContinuationResult for better control over how edits work * Remove IO from TacticProviders; use LspEnv instead * installInteractions * Pull TacticCodeActions into their own file * Misc cleanup * Haddock * Fix bug in codelens * Port EmptyCase to Interaction * Rename makeTacticCodeAction -> makeTacticInteraction * Support for partial timeouts in upcoming refinery v5 * asum instead of choice for assumption * Don't count it as using a term if you only destruct it * Let interactions return multiple results --- aka also info messages * Update refinery lower bounds * Revert "Update refinery lower bounds" This reverts commit 53199b3. * Pull refinery from the future * Fix tests * Add -XNumDecimals * Fix AutoTypeLevel test * Continue to emit errors Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent ed37b61 commit d7a745e

File tree

11 files changed

+267
-85
lines changed

11 files changed

+267
-85
lines changed

plugins/hls-tactics-plugin/hls-tactics-plugin.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ library
2727
hs-source-dirs: src
2828
exposed-modules:
2929
Ide.Plugin.Tactic
30+
Refinery.Future
3031
Wingman.AbstractLSP
3132
Wingman.AbstractLSP.TacticActions
3233
Wingman.AbstractLSP.Types
@@ -93,6 +94,7 @@ library
9394
, refinery ^>=0.4
9495
, retrie >=0.1.1.0
9596
, syb
97+
, unagi-chan
9698
, text
9799
, transformers
98100
, unordered-containers
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
{-# LANGUAGE RankNTypes #-}
2+
3+
------------------------------------------------------------------------------
4+
-- | Things that belong in the future release of refinery v5.
5+
module Refinery.Future
6+
( runStreamingTacticT
7+
, hoistListT
8+
, consume
9+
) where
10+
11+
import Control.Applicative
12+
import Control.Monad (ap, (>=>))
13+
import Control.Monad.State.Lazy (runStateT)
14+
import Control.Monad.Trans
15+
import Data.Either (isRight)
16+
import Data.Functor ((<&>))
17+
import Data.Tuple (swap)
18+
import Refinery.ProofState
19+
import Refinery.Tactic.Internal
20+
21+
22+
23+
hoistElem :: Functor m => (forall x. m x -> n x) -> Elem m a -> Elem n a
24+
hoistElem _ Done = Done
25+
hoistElem f (Next a lt) = Next a $ hoistListT f lt
26+
27+
28+
hoistListT :: Functor m => (forall x. m x -> n x) -> ListT m a -> ListT n a
29+
hoistListT f t = ListT $ f $ fmap (hoistElem f) $ unListT t
30+
31+
32+
consume :: Monad m => ListT m a -> (a -> m ()) -> m ()
33+
consume lt f = unListT lt >>= \case
34+
Done -> pure ()
35+
Next a lt' -> f a >> consume lt' f
36+
37+
38+
newHole :: MonadExtract meta ext err s m => s -> m (s, (meta, ext))
39+
newHole = fmap swap . runStateT hole
40+
41+
runStreamingTacticT :: (MonadExtract meta ext err s m) => TacticT jdg ext err s m () -> jdg -> s -> ListT m (Either err (Proof s meta jdg ext))
42+
runStreamingTacticT t j s = streamProofs s $ fmap snd $ proofState t j
43+
44+
data Elem m a
45+
= Done
46+
| Next a (ListT m a)
47+
deriving stock Functor
48+
49+
50+
point :: Applicative m => a -> Elem m a
51+
point a = Next a $ ListT $ pure Done
52+
53+
newtype ListT m a = ListT { unListT :: m (Elem m a) }
54+
55+
cons :: (Applicative m) => a -> ListT m a -> ListT m a
56+
cons x xs = ListT $ pure $ Next x xs
57+
58+
instance Functor m => Functor (ListT m) where
59+
fmap f (ListT xs) = ListT $ xs <&> \case
60+
Done -> Done
61+
Next a xs -> Next (f a) (fmap f xs)
62+
63+
instance (Monad m) => Applicative (ListT m) where
64+
pure = return
65+
(<*>) = ap
66+
67+
instance (Monad m) => Alternative (ListT m) where
68+
empty = ListT $ pure Done
69+
(ListT xs) <|> (ListT ys) =
70+
ListT $ xs >>= \case
71+
Done -> ys
72+
Next x xs -> pure (Next x (xs <|> ListT ys))
73+
74+
instance (Monad m) => Monad (ListT m) where
75+
return a = cons a empty
76+
(ListT xs) >>= k =
77+
ListT $ xs >>= \case
78+
Done -> pure Done
79+
Next x xs -> unListT $ k x <|> (xs >>= k)
80+
81+
82+
instance MonadTrans ListT where
83+
lift m = ListT $ fmap (\x -> Next x empty) m
84+
85+
86+
interleaveT :: (Monad m) => Elem m a -> Elem m a -> Elem m a
87+
interleaveT xs ys =
88+
case xs of
89+
Done -> ys
90+
Next x xs -> Next x $ ListT $ fmap (interleaveT ys) $ unListT xs
91+
92+
-- ys <&> \case
93+
-- Done -> Next x xs
94+
-- Next y ys -> Next x (cons y (interleaveT xs ys))
95+
96+
force :: (Monad m) => Elem m a -> m [a]
97+
force = \case
98+
Done -> pure []
99+
Next x xs' -> (x:) <$> (unListT xs' >>= force)
100+
101+
ofList :: Monad m => [a] -> Elem m a
102+
ofList [] = Done
103+
ofList (x:xs) = Next x $ ListT $ pure $ ofList xs
104+
105+
streamProofs :: forall ext err s m goal meta. (MonadExtract meta ext err s m) => s -> ProofStateT ext ext err s m goal -> ListT m (Either err (Proof s meta goal ext))
106+
streamProofs s p = ListT $ go s [] pure p
107+
where
108+
go :: s -> [(meta, goal)] -> (err -> m err) -> ProofStateT ext ext err s m goal -> m (Elem m (Either err (Proof s meta goal ext)))
109+
go s goals _ (Subgoal goal k) = do
110+
(s', (meta, h)) <- newHole s
111+
-- Note [Handler Reset]:
112+
-- We reset the handler stack to avoid the handlers leaking across subgoals.
113+
-- This would happen when we had a handler that wasn't followed by an error call.
114+
-- pair >> goal >>= \g -> (handler_ $ \_ -> traceM $ "Handling " <> show g) <|> failure "Error"
115+
-- We would see the "Handling a" message when solving for b.
116+
(go s' (goals ++ [(meta, goal)]) pure $ k h)
117+
go s goals handlers (Effect m) = m >>= go s goals handlers
118+
go s goals handlers (Stateful f) =
119+
let (s', p) = f s
120+
in go s' goals handlers p
121+
go s goals handlers (Alt p1 p2) =
122+
unListT $ ListT (go s goals handlers p1) <|> ListT (go s goals handlers p2)
123+
go s goals handlers (Interleave p1 p2) =
124+
interleaveT <$> (go s goals handlers p1) <*> (go s goals handlers p2)
125+
go s goals handlers (Commit p1 p2) = do
126+
solns <- force =<< go s goals handlers p1
127+
if (any isRight solns) then pure $ ofList solns else go s goals handlers p2
128+
go _ _ _ Empty = pure Done
129+
go _ _ handlers (Failure err _) = do
130+
annErr <- handlers err
131+
pure $ point $ Left annErr
132+
go s goals handlers (Handle p h) =
133+
-- Note [Handler ordering]:
134+
-- If we have multiple handlers in scope, then we want the handlers closer to the error site to
135+
-- run /first/. This allows the handlers up the stack to add their annotations on top of the
136+
-- ones lower down, which is the behavior that we desire.
137+
-- IE: for @handler f >> handler g >> failure err@, @g@ ought to be run before @f@.
138+
go s goals (h >=> handlers) p
139+
go s goals _ (Axiom ext) = pure $ point $ Right (Proof ext s goals)
140+

plugins/hls-tactics-plugin/src/Wingman/AbstractLSP.hs

+27-20
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ import Control.Monad.IO.Class
1313
import Control.Monad.Trans (lift)
1414
import Control.Monad.Trans.Maybe (MaybeT, mapMaybeT)
1515
import qualified Data.Aeson as A
16+
import Data.Coerce
1617
import Data.Foldable (traverse_)
18+
import Data.Monoid (Last (..))
1719
import qualified Data.Text as T
20+
import Data.Traversable (for)
1821
import Data.Tuple.Extra (uncurry3)
1922
import Development.IDE (IdeState)
2023
import Development.IDE.Core.UseStale
@@ -93,26 +96,30 @@ runContinuation plId cont state (fc, b) = do
9396
env@LspEnv{..} <- buildEnv state plId fc
9497
let stale a = runStaleIde "runContinuation" state (fc_nfp le_fileContext) a
9598
args <- fetchTargetArgs @a env
96-
c_runCommand cont env args fc b >>= \case
97-
ErrorMessages errs -> do
98-
traverse_ showUserFacingMessage errs
99-
pure $ Right A.Null
100-
RawEdit edits -> do
101-
sendEdits edits
102-
pure $ Right A.Null
103-
GraftEdit gr -> do
104-
ccs <- lift getClientCapabilities
105-
TrackedStale pm _ <- mapMaybeT liftIO $ stale GetAnnotatedParsedSource
106-
case mkWorkspaceEdits le_dflags ccs (fc_uri le_fileContext) (unTrack pm) gr of
107-
Left errs ->
108-
pure $ Left $ ResponseError
109-
{ _code = InternalError
110-
, _message = T.pack $ show errs
111-
, _xdata = Nothing
112-
}
113-
Right edits -> do
114-
sendEdits edits
115-
pure $ Right A.Null
99+
res <- c_runCommand cont env args fc b
100+
101+
-- This block returns a maybe error.
102+
fmap (maybe (Right $ A.Null) Left . coerce . foldMap Last) $
103+
for res $ \case
104+
ErrorMessages errs -> do
105+
traverse_ showUserFacingMessage errs
106+
pure Nothing
107+
RawEdit edits -> do
108+
sendEdits edits
109+
pure Nothing
110+
GraftEdit gr -> do
111+
ccs <- lift getClientCapabilities
112+
TrackedStale pm _ <- mapMaybeT liftIO $ stale GetAnnotatedParsedSource
113+
case mkWorkspaceEdits le_dflags ccs (fc_uri le_fileContext) (unTrack pm) gr of
114+
Left errs ->
115+
pure $ Just $ ResponseError
116+
{ _code = InternalError
117+
, _message = T.pack $ show errs
118+
, _xdata = Nothing
119+
}
120+
Right edits -> do
121+
sendEdits edits
122+
pure $ Nothing
116123

117124

118125
------------------------------------------------------------------------------

plugins/hls-tactics-plugin/src/Wingman/AbstractLSP/TacticActions.hs

+33-17
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,38 @@ makeTacticInteraction cmd =
5252
pm_span <- liftMaybe $ mapAgeFrom pmmap span
5353
let t = commandTactic cmd var_name
5454

55-
res <- liftIO $ timeout (cfg_timeout_seconds le_config * seconds) $ do
56-
runTactic hj_ctx hj_jdg t >>= \case
57-
Left err -> pure $ ErrorMessages $ pure $ mkUserFacingMessage err
58-
Right rtr ->
59-
case rtr_extract rtr of
60-
L _ (HsVar _ (L _ rdr)) | isHole (occName rdr) ->
61-
pure $ ErrorMessages [NothingToDo]
62-
_ -> do
63-
for_ (rtr_other_solns rtr) $ \soln -> do
64-
traceMX "other solution" $ syn_val soln
65-
traceMX "with score" $ scoreSolution soln (rtr_jdg rtr) []
66-
traceMX "solution" $ rtr_extract rtr
67-
pure $ GraftEdit $ graftHole (RealSrcSpan $ unTrack pm_span) rtr
68-
69-
pure $ case res of
70-
Nothing -> ErrorMessages $ pure TimedOut
71-
Just c -> c
55+
liftIO $ runTactic (cfg_timeout_seconds le_config * seconds) hj_ctx hj_jdg t >>= \case
56+
Left err ->
57+
pure
58+
$ pure
59+
$ ErrorMessages
60+
$ pure
61+
$ mkUserFacingMessage err
62+
Right rtr ->
63+
case rtr_extract rtr of
64+
L _ (HsVar _ (L _ rdr)) | isHole (occName rdr) ->
65+
pure
66+
$ addTimeoutMessage rtr
67+
$ pure
68+
$ ErrorMessages
69+
$ pure NothingToDo
70+
_ -> do
71+
for_ (rtr_other_solns rtr) $ \soln -> do
72+
traceMX "other solution" $ syn_val soln
73+
traceMX "with score" $ scoreSolution soln (rtr_jdg rtr) []
74+
traceMX "solution" $ rtr_extract rtr
75+
pure
76+
$ addTimeoutMessage rtr
77+
$ pure
78+
$ GraftEdit
79+
$ graftHole (RealSrcSpan $ unTrack pm_span) rtr
80+
81+
82+
addTimeoutMessage :: RunTacticResults -> [ContinuationResult] -> [ContinuationResult]
83+
addTimeoutMessage rtr = mappend
84+
[ ErrorMessages $ pure TimedOut
85+
| rtr_timed_out rtr
86+
]
7287

7388

7489
------------------------------------------------------------------------------
@@ -82,6 +97,7 @@ seconds = 1e6
8297
mkUserFacingMessage :: [TacticError] -> UserFacingMessage
8398
mkUserFacingMessage errs
8499
| elem OutOfGas errs = NotEnoughGas
100+
mkUserFacingMessage [] = NothingToDo
85101
mkUserFacingMessage _ = TacticErrors
86102

87103

plugins/hls-tactics-plugin/src/Wingman/AbstractLSP/Types.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ data Continuation sort target payload = Continuation
113113
-> TargetArgs target
114114
-> FileContext
115115
-> payload
116-
-> MaybeT (LspM Plugin.Config) ContinuationResult
116+
-> MaybeT (LspM Plugin.Config) [ContinuationResult]
117117
}
118118

119119

plugins/hls-tactics-plugin/src/Wingman/EmptyCase.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ emptyCaseInteraction = Interaction $
8484
, edits
8585
)
8686
)
87-
$ (\ _ _ _ we -> pure $ RawEdit we)
87+
$ (\ _ _ _ we -> pure $ pure $ RawEdit we)
8888

8989

9090
scrutinzedType :: EmptyCaseSort Type -> Maybe Type

0 commit comments

Comments
 (0)