1
- -- | A plugin that uses tactics to synthesize code
2
1
{-# LANGUAGE DeriveAnyClass #-}
3
2
{-# LANGUAGE DeriveGeneric #-}
4
3
{-# LANGUAGE OverloadedStrings #-}
5
4
{-# LANGUAGE TupleSections #-}
6
5
{-# LANGUAGE TypeApplications #-}
6
+ {-# LANGUAGE ViewPatterns #-}
7
7
8
+ -- | A plugin that uses tactics to synthesize code
8
9
module Ide.Plugin.Tactic
9
10
( descriptor
10
11
) where
@@ -36,43 +37,103 @@ import Ide.LocalBindings
36
37
import qualified Language.Haskell.LSP.Types as J
37
38
import Language.Haskell.LSP.Types
38
39
40
+ import Data.List (intercalate )
39
41
import OccName
40
42
import HsExpr
41
43
import GHC
42
44
import DynFlags
43
45
import Type
46
+ import System.IO
44
47
45
48
46
49
descriptor :: PluginId -> PluginDescriptor
47
50
descriptor plId = (defaultPluginDescriptor plId)
48
51
{ pluginCommands
49
- = fmap (\ (name, tac) ->
52
+ = fmap (\ tc ->
50
53
PluginCommand
51
- (coerce $ name <> " Command " )
52
- (tacticDesc name )
53
- (tacticCmd tac ))
54
- ( Map. toList registeredCommands)
54
+ (tcCommandId tc )
55
+ (tacticDesc $ tcCommandName tc )
56
+ (tacticCmd $ commandTactic tc ))
57
+ [ minBound .. maxBound ]
55
58
, pluginCodeActionProvider = Just codeActionProvider
56
59
}
57
60
58
61
tacticDesc :: T. Text -> T. Text
59
62
tacticDesc name = " fill the hole using the " <> name <> " tactic"
60
63
61
- registeredCommands :: Map. Map T. Text (OccName -> TacticsM () )
62
- registeredCommands = Map. fromList
63
- [ (" auto" , const auto)
64
- , (" split" , const split)
65
- , (" intro" , const intro)
66
- , (" destruct" , destruct)
67
- , (" homo" , homo)
68
- ]
69
-
70
- alwaysCommands :: [T. Text ]
71
- alwaysCommands =
72
- [ " auto"
73
- , " split"
74
- , " intro"
75
- ]
64
+ ------------------------------------------------------------------------------
65
+ -- | The list of tactics exposed to the outside world. These are attached to
66
+ -- actual tactics via 'commandTactic' and are contextually provided to the
67
+ -- editor via 'commandProvider'.
68
+ data TacticCommand
69
+ = Auto
70
+ | Split
71
+ | Intro
72
+ | Destruct
73
+ | Homo
74
+ deriving (Eq , Ord , Show , Enum , Bounded )
75
+
76
+
77
+ ------------------------------------------------------------------------------
78
+ -- | A 'TacticProvider' is a way of giving context-sensitive actions to the LS
79
+ -- UI.
80
+ type TacticProvider = PluginId -> Uri -> Range -> Judgement -> IO [CAResult ]
81
+
82
+
83
+ ------------------------------------------------------------------------------
84
+ -- | Construct a 'CommandId'
85
+ tcCommandId :: TacticCommand -> CommandId
86
+ tcCommandId c = coerce $ T. pack $ " tactics" <> show c <> " Command"
87
+
88
+
89
+ ------------------------------------------------------------------------------
90
+ -- | The name of the command for the LS.
91
+ tcCommandName :: TacticCommand -> T. Text
92
+ tcCommandName = T. pack . show
93
+
94
+ ------------------------------------------------------------------------------
95
+ -- | Construct a title for a command.
96
+ tcCommandTitle :: TacticCommand -> OccName -> T. Text
97
+ tcCommandTitle tc occ = T. pack $ show tc <> " " <> occNameString occ
98
+
99
+ ------------------------------------------------------------------------------
100
+ -- | Mapping from tactic commands to their contextual providers. See 'provide',
101
+ -- 'filterGoalType' and 'filterBindingType' for the nitty gritty.
102
+ commandProvider :: TacticCommand -> TacticProvider
103
+ commandProvider Auto = provide Auto " Auto" " "
104
+ commandProvider Split = provide Split " Split" " "
105
+ commandProvider Intro =
106
+ filterGoalType isFunction $
107
+ provide Intro " Intro" " "
108
+ commandProvider Destruct =
109
+ filterBindingType destructFilter $ \ occ _ ->
110
+ provide Destruct (tcCommandTitle Destruct occ) $ T. pack $ occNameString occ
111
+ commandProvider Homo =
112
+ filterBindingType homoFilter $ \ occ _ ->
113
+ provide Homo (tcCommandTitle Homo occ) $ T. pack $ occNameString occ
114
+
115
+ ------------------------------------------------------------------------------
116
+ -- | A mapping from tactic commands to actual tactics for refinery.
117
+ commandTactic :: TacticCommand -> OccName -> TacticsM ()
118
+ commandTactic Auto = const auto
119
+ commandTactic Split = const split
120
+ commandTactic Intro = const intro
121
+ commandTactic Destruct = autoIfPossible . destruct
122
+ commandTactic Homo = autoIfPossible . homo
123
+
124
+ ------------------------------------------------------------------------------
125
+ -- | We should show homos only when the goal type is the same as the binding
126
+ -- type, and that both are usual algebraic types.
127
+ homoFilter :: Type -> Type -> Bool
128
+ homoFilter (algebraicTyCon -> Just t1) (algebraicTyCon -> Just t2) = t1 == t2
129
+ homoFilter _ _ = False
130
+
131
+ ------------------------------------------------------------------------------
132
+ -- | We should show destruct for bindings only when those bindings have usual
133
+ -- algebraic types.
134
+ destructFilter :: Type -> Type -> Bool
135
+ destructFilter _ (algebraicTyCon -> Just _) = True
136
+ destructFilter _ _ = False
76
137
77
138
runIde :: IdeState -> Action a -> IO a
78
139
runIde state = runAction " tactic" state
@@ -82,40 +143,18 @@ codeActionProvider _conf state plId (TextDocumentIdentifier uri) range _ctx
82
143
| Just nfp <- uriToNormalizedFilePath $ toNormalizedUri uri = do
83
144
(pos, mss, jdg) <- judgmentForHole state nfp range
84
145
case mss of
85
- -- FIXME For some reason we get an HsVar instead of an
86
- -- HsUnboundVar. We should check if this is a hole somehow??
87
- L span' (HsVar _ (L _ var)) -> do
146
+ L span' (HsUnboundVar _ _) -> do
88
147
let resulting_range
89
148
= fromMaybe (error " that is not great" )
90
149
$ toCurrentRange pos =<< srcSpanToRange span'
91
- let params = TacticParams
92
- { file = uri
93
- , range = resulting_range
94
- , var_name = T. pack $ occNameString $ occName var
95
- }
96
- let names = alwaysCommands
97
- actions <- for names $ \ name -> do
98
- cmd <-
99
- mkLspCommand
100
- plId
101
- (coerce $ name <> " Command" )
102
- name
103
- (Just [toJSON params])
104
- pure
105
- $ CACodeAction
106
- $ CodeAction
107
- name
108
- (Just CodeActionQuickFix )
109
- Nothing
110
- Nothing
111
- $ Just cmd
112
- split_actions <- mkSplits plId uri resulting_range jdg
113
- homo_actions <- mkHomos plId uri resulting_range jdg
114
- pure $ Right $ List $ mconcat
115
- [ actions
116
- , split_actions
117
- , homo_actions
118
- ]
150
+ actions <-
151
+ -- This foldMap is over the function monoid.
152
+ foldMap commandProvider [minBound .. maxBound ]
153
+ plId
154
+ uri
155
+ resulting_range
156
+ jdg
157
+ pure $ Right $ List actions
119
158
_ -> pure $ Right $ codeActions []
120
159
codeActionProvider _ _ _ _ _ _ = pure $ Right $ codeActions []
121
160
@@ -124,69 +163,43 @@ codeActions :: [CodeAction] -> List CAResult
124
163
codeActions = List . fmap CACodeAction
125
164
126
165
127
- mkSplits :: PluginId -> Uri -> Range -> Judgement -> IO [CAResult ]
128
- mkSplits plId uri range (Judgement hys _) =
129
- fmap join $ for (Map. toList hys) $ \ (occ, CType ty) ->
130
- case algebraicTyCon ty of
131
- Nothing -> pure []
132
- Just _ -> do
133
- let name = T. pack $ occNameString occ
134
- let params = TacticParams
135
- { file = uri
136
- , range = range
137
- , var_name = name
138
- }
139
- cmd <-
140
- mkLspCommand
141
- plId
142
- (" destructCommand" )
143
- name
144
- (Just [toJSON params])
145
- pure
146
- $ pure
147
- $ CACodeAction
148
- $ CodeAction
149
- (" destruct " <> name)
150
- (Just CodeActionQuickFix )
151
- Nothing
152
- Nothing
153
- $ Just cmd
154
-
155
-
156
- mkHomos :: PluginId -> Uri -> Range -> Judgement -> IO [CAResult ]
157
- mkHomos plId uri range (Judgement hys (CType g)) =
158
- case algebraicTyCon g of
159
- Nothing -> pure []
160
- Just tycon ->
161
- fmap join $ for (Map. toList hys) $ \ (occ, CType ty) ->
162
- case algebraicTyCon ty of
163
- Just tycon2 | tycon == tycon2 -> do
164
- let name = T. pack $ occNameString occ
165
- let params = TacticParams
166
- { file = uri
167
- , range = range
168
- , var_name = name
169
- }
170
- cmd <-
171
- mkLspCommand
172
- plId
173
- (" homoCommand" )
174
- name
175
- (Just [toJSON params])
176
- pure
177
- $ pure
178
- $ CACodeAction
179
- $ CodeAction
180
- (" homo " <> name)
181
- (Just CodeActionQuickFix )
182
- Nothing
183
- Nothing
184
- $ Just cmd
185
- _ -> pure []
166
+ ------------------------------------------------------------------------------
167
+ -- | Terminal constructor for providing context-sensitive tactics. Tactics
168
+ -- given by 'provide' are always available.
169
+ provide :: TacticCommand -> T. Text -> T. Text -> TacticProvider
170
+ provide tc title name plId uri range _ = do
171
+ let params = TacticParams { file = uri , range = range , var_name = name }
172
+ cmd <- mkLspCommand plId (tcCommandId tc) title (Just [toJSON params])
173
+ pure
174
+ $ pure
175
+ $ CACodeAction
176
+ $ CodeAction title (Just CodeActionQuickFix ) Nothing Nothing
177
+ $ Just cmd
186
178
187
179
180
+ ------------------------------------------------------------------------------
181
+ -- | Restrict a 'TacticProvider', making sure it appears only when the given
182
+ -- predicate holds for the goal.
183
+ filterGoalType :: (Type -> Bool ) -> TacticProvider -> TacticProvider
184
+ filterGoalType p tp plId uri range jdg@ (Judgement _ (CType g)) =
185
+ case p g of
186
+ True -> tp plId uri range jdg
187
+ False -> pure []
188
188
189
189
190
+ ------------------------------------------------------------------------------
191
+ -- | Multiply a 'TacticProvider' for each binding, making sure it appears only
192
+ -- when the given predicate holds over the goal and binding types.
193
+ filterBindingType
194
+ :: (Type -> Type -> Bool ) -- ^ Goal and then binding types.
195
+ -> (OccName -> Type -> TacticProvider )
196
+ -> TacticProvider
197
+ filterBindingType p tp plId uri range jdg@ (Judgement hys (CType g)) =
198
+ fmap join $ for (Map. toList hys) $ \ (occ, CType ty) ->
199
+ case p g ty of
200
+ True -> tp occ ty plId uri range jdg
201
+ False -> pure []
202
+
190
203
191
204
data TacticParams = TacticParams
192
205
{ file :: J. Uri -- ^ Uri of the file to fill the hole in
@@ -196,6 +209,9 @@ data TacticParams = TacticParams
196
209
deriving (Show , Eq , Generics.Generic , ToJSON , FromJSON )
197
210
198
211
212
+ ------------------------------------------------------------------------------
213
+ -- | Find the last typechecked module, and find the most specific span, as well
214
+ -- as the judgement at the given range.
199
215
judgmentForHole
200
216
:: IdeState
201
217
-> NormalizedFilePath
@@ -210,14 +226,16 @@ judgmentForHole state nfp range = do
210
226
Just (mss@ (L span' (HsVar _ (L _ v))))
211
227
= mostSpecificSpan @ _ @ GhcTc span (tm_typechecked_source mod )
212
228
goal = varType v
213
- hyps = hypothesisFromBindings span' $ bindings mod
214
- pure (pos, mss, Judgement hyps $ CType goal)
229
+ binds = bindings mod
230
+ hyps = hypothesisFromBindings span' binds
231
+ pure (pos, holify binds mss, Judgement hyps $ CType goal)
215
232
216
233
217
234
tacticCmd :: (OccName -> TacticsM () ) -> CommandFunction TacticParams
218
235
tacticCmd tac _lf state (TacticParams uri range var_name)
219
236
| Just nfp <- uriToNormalizedFilePath $ toNormalizedUri uri = do
220
237
(pos, _, jdg) <- judgmentForHole state nfp range
238
+ hPutStrLn stderr $ intercalate " ; " $ fmap (\ (occ, CType ty) -> occNameString occ <> " :: " <> render unsafeGlobalDynFlags ty) $ Map. toList $ jHypothesis jdg
221
239
pure $
222
240
case runTactic
223
241
unsafeGlobalDynFlags
0 commit comments