Skip to content

Commit 84d47cd

Browse files
committed
Lambda case destruct tactics
1 parent cc5fcbd commit 84d47cd

File tree

5 files changed

+117
-52
lines changed

5 files changed

+117
-52
lines changed

plugins/tactics/src/Ide/Plugin/Tactic.hs

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ import Development.IDE.Core.Service (runAction)
3131
import Development.IDE.Core.Shake (useWithStale, IdeState (..))
3232
import Development.IDE.GHC.Compat
3333
import Development.IDE.GHC.Error (realSrcSpanToRange)
34-
import Development.IDE.GHC.Util (hscEnv)
3534
import Development.Shake (Action)
35+
import DynFlags (xopt)
3636
import qualified FastString
3737
import GHC.Generics (Generic)
38-
import HscTypes (hsc_dflags)
38+
import GHC.LanguageExtensions.Type (Extension (LambdaCase))
3939
import Ide.Plugin (mkLspCommand)
4040
import Ide.Plugin.Tactic.BindSites
4141
import Ide.Plugin.Tactic.Context
@@ -59,22 +59,17 @@ descriptor plId = (defaultPluginDescriptor plId)
5959
(tcCommandId tc)
6060
(tacticDesc $ tcCommandName tc)
6161
(tacticCmd $ commandTactic tc))
62-
enabledTactics
62+
[minBound .. maxBound]
6363
, pluginCodeActionProvider = Just codeActionProvider
6464
}
6565

6666
tacticDesc :: T.Text -> T.Text
6767
tacticDesc name = "fill the hole using the " <> name <> " tactic"
6868

69-
------------------------------------------------------------------------------
70-
71-
enabledTactics :: [TacticCommand]
72-
enabledTactics = [Intros, Destruct, Homomorphism, Auto]
73-
7469
------------------------------------------------------------------------------
7570
-- | A 'TacticProvider' is a way of giving context-sensitive actions to the LS
7671
-- UI.
77-
type TacticProvider = PluginId -> Uri -> Range -> Judgement -> IO [CAResult]
72+
type TacticProvider = DynFlags -> PluginId -> Uri -> Range -> Judgement -> IO [CAResult]
7873

7974

8075
------------------------------------------------------------------------------
@@ -93,10 +88,6 @@ tcCommandName = T.pack . show
9388
-- 'filterGoalType' and 'filterBindingType' for the nitty gritty.
9489
commandProvider :: TacticCommand -> TacticProvider
9590
commandProvider Auto = provide Auto ""
96-
commandProvider Split = provide Split ""
97-
commandProvider Intro =
98-
filterGoalType isFunction $
99-
provide Intro ""
10091
commandProvider Intros =
10192
filterGoalType isFunction $
10293
provide Intros ""
@@ -106,17 +97,25 @@ commandProvider Destruct =
10697
commandProvider Homomorphism =
10798
filterBindingType homoFilter $ \occ _ ->
10899
provide Homomorphism $ T.pack $ occNameString occ
100+
commandProvider DestructLambdaCase =
101+
requireExtension LambdaCase $
102+
filterGoalType (isJust . lambdaCaseable) $
103+
provide DestructLambdaCase ""
104+
commandProvider HomomorphismLambdaCase =
105+
requireExtension LambdaCase $
106+
filterGoalType ((== Just True) . lambdaCaseable) $
107+
provide HomomorphismLambdaCase ""
109108

110109

111110
------------------------------------------------------------------------------
112111
-- | A mapping from tactic commands to actual tactics for refinery.
113112
commandTactic :: TacticCommand -> OccName -> TacticsM ()
114113
commandTactic Auto = const auto
115-
commandTactic Split = const split
116-
commandTactic Intro = const intro
117114
commandTactic Intros = const intros
118115
commandTactic Destruct = destruct
119116
commandTactic Homomorphism = homo
117+
commandTactic DestructLambdaCase = const destructLambdaCase
118+
commandTactic HomomorphismLambdaCase = const homoLambdaCase
120119

121120

122121
------------------------------------------------------------------------------
@@ -143,10 +142,11 @@ codeActionProvider :: CodeActionProvider
143142
codeActionProvider _conf state plId (TextDocumentIdentifier uri) range _ctx
144143
| Just nfp <- uriToNormalizedFilePath $ toNormalizedUri uri =
145144
fromMaybeT (Right $ List []) $ do
146-
(_, _, span, jdg) <- MaybeT $ judgementForHole state nfp range
145+
(_, _, span, jdg, dflags) <- MaybeT $ judgementForHole state nfp range
147146
actions <- lift $
148147
-- This foldMap is over the function monoid.
149-
foldMap commandProvider enabledTactics
148+
foldMap commandProvider [minBound .. maxBound]
149+
dflags
150150
plId
151151
uri
152152
span
@@ -163,7 +163,7 @@ codeActions = List . fmap CACodeAction
163163
-- | Terminal constructor for providing context-sensitive tactics. Tactics
164164
-- given by 'provide' are always available.
165165
provide :: TacticCommand -> T.Text -> TacticProvider
166-
provide tc name plId uri range _ = do
166+
provide tc name _ plId uri range _ = do
167167
let title = tacticTitle tc name
168168
params = TacticParams { file = uri , range = range , var_name = name }
169169
cmd <- mkLspCommand plId (tcCommandId tc) title (Just [toJSON params])
@@ -174,13 +174,23 @@ provide tc name plId uri range _ = do
174174
$ Just cmd
175175

176176

177+
------------------------------------------------------------------------------
178+
-- | Restrict a 'TacticProvider', making sure it appears only when the given
179+
-- predicate holds for the goal.
180+
requireExtension :: Extension -> TacticProvider -> TacticProvider
181+
requireExtension ext tp dflags plId uri range jdg =
182+
case xopt ext dflags of
183+
True -> tp dflags plId uri range jdg
184+
False -> pure []
185+
186+
177187
------------------------------------------------------------------------------
178188
-- | Restrict a 'TacticProvider', making sure it appears only when the given
179189
-- predicate holds for the goal.
180190
filterGoalType :: (Type -> Bool) -> TacticProvider -> TacticProvider
181-
filterGoalType p tp plId uri range jdg =
191+
filterGoalType p tp dflags plId uri range jdg =
182192
case p $ unCType $ jGoal jdg of
183-
True -> tp plId uri range jdg
193+
True -> tp dflags plId uri range jdg
184194
False -> pure []
185195

186196

@@ -191,12 +201,12 @@ filterBindingType
191201
:: (Type -> Type -> Bool) -- ^ Goal and then binding types.
192202
-> (OccName -> Type -> TacticProvider)
193203
-> TacticProvider
194-
filterBindingType p tp plId uri range jdg =
204+
filterBindingType p tp dflags plId uri range jdg =
195205
let hy = jHypothesis jdg
196206
g = jGoal jdg
197207
in fmap join $ for (M.toList hy) $ \(occ, CType ty) ->
198208
case p (unCType g) ty of
199-
True -> tp occ ty plId uri range jdg
209+
True -> tp occ ty dflags plId uri range jdg
200210
False -> pure []
201211

202212

@@ -215,14 +225,19 @@ judgementForHole
215225
:: IdeState
216226
-> NormalizedFilePath
217227
-> Range
218-
-> IO (Maybe (PositionMapping, BindSites, Range, Judgement))
228+
-> IO (Maybe (PositionMapping, BindSites, Range, Judgement, DynFlags))
219229
judgementForHole state nfp range = runMaybeT $ do
220230
(asts, amapping) <- MaybeT $ runIde state $ useWithStale GetHieAst nfp
221231
range' <- liftMaybe $ fromCurrentRange amapping range
222232

223233
(binds, _) <- MaybeT $ runIde state $ useWithStale GetBindings nfp
224234
let b2 = bindSites $ refMap asts
225235

236+
-- Ok to use the stale 'ModIface', since all we need is its 'DynFlags'
237+
-- which don't change very often.
238+
(modsum, _) <- MaybeT $ runIde state $ useWithStale GetModSummaryWithoutTimestamps nfp
239+
let dflags = ms_hspp_opts modsum
240+
226241
(rss, goal) <- liftMaybe $ join $ listToMaybe $ M.elems $ flip M.mapWithKey (getAsts $ hieAst asts) $ \fs ast ->
227242
case selectSmallestContaining (rangeToRealSrcSpan (FastString.unpackFS fs) range') ast of
228243
Nothing -> Nothing
@@ -235,24 +250,20 @@ judgementForHole state nfp range = runMaybeT $ do
235250
resulting_range <- liftMaybe $ toCurrentRange amapping $ realSrcSpanToRange rss
236251

237252
let hyps = hypothesisFromBindings rss binds
238-
pure (amapping, b2, resulting_range, mkFirstJudgement hyps goal)
253+
pure (amapping, b2, resulting_range, mkFirstJudgement hyps goal, dflags)
239254

240255

241256

242257
tacticCmd :: (OccName -> TacticsM ()) -> CommandFunction TacticParams
243258
tacticCmd tac lf state (TacticParams uri range var_name)
244259
| Just nfp <- uriToNormalizedFilePath $ toNormalizedUri uri =
245260
fromMaybeT (Right Null, Nothing) $ do
246-
(pos, b2, _, jdg) <- MaybeT $ judgementForHole state nfp range
247-
-- Ok to use the stale 'ModIface', since all we need is its 'DynFlags'
248-
-- which don't change very often.
249-
(hscenv, _) <- MaybeT $ runIde state $ useWithStale GhcSession nfp
261+
(pos, b2, _, jdg, dflags) <- MaybeT $ judgementForHole state nfp range
250262
range' <- liftMaybe $ toCurrentRange pos range
251263
(tcmod, _) <- MaybeT $ runIde state $ useWithStale TypeCheck nfp
252264
let span = rangeToRealSrcSpan (fromNormalizedFilePath nfp) range'
253265
-- TODO(sandy): unclear if this span is correct; might be
254266
-- pointing to the wrong version of the file
255-
dflags = hsc_dflags $ hscEnv hscenv
256267
tcg = fst $ tm_internals_ $ tmrModule tcmod
257268
ctx = mkContext
258269
(mapMaybe (sequenceA . (occName *** coerce)) $ getDefiningBindings b2 span)

plugins/tactics/src/Ide/Plugin/Tactic/CodeGen.hs

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE FlexibleContexts #-}
12
module Ide.Plugin.Tactic.CodeGen where
23

34
import Control.Monad.Except
@@ -18,35 +19,62 @@ import Name
1819
import Type hiding (Var)
1920

2021

22+
destructMatches
23+
:: (DataCon -> Judgement -> Rule)
24+
-- ^ How to construct each match
25+
-> (Judgement -> Judgement)
26+
-- ^ How to derive each match judgement
27+
-> CType
28+
-- ^ Type being destructed
29+
-> Judgement
30+
-> RuleM [RawMatch]
31+
destructMatches f f2 t jdg = do
32+
let hy = jHypothesis jdg
33+
g = jGoal jdg
34+
case splitTyConApp_maybe $ unCType t of
35+
Nothing -> throwError $ GoalMismatch "destruct" g
36+
Just (tc, apps) -> do
37+
let dcs = tyConDataCons tc
38+
case dcs of
39+
[] -> throwError $ GoalMismatch "destruct" g
40+
_ -> for dcs $ \dc -> do
41+
let args = dataConInstArgTys dc apps
42+
names <- mkManyGoodNames hy args
43+
44+
let pat :: Pat GhcPs
45+
pat = conP (fromString $ occNameString $ nameOccName $ dataConName dc)
46+
$ fmap bvar' names
47+
j = f2
48+
$ introducingPat (zip names $ coerce args)
49+
$ withNewGoal g jdg
50+
sg <- f dc j
51+
pure $ match [pat] $ unLoc sg
52+
53+
2154
------------------------------------------------------------------------------
2255
-- | Combinator for performign case splitting, and running sub-rules on the
2356
-- resulting matches.
2457
destruct' :: (DataCon -> Judgement -> Rule) -> OccName -> Judgement -> Rule
2558
destruct' f term jdg = do
2659
let hy = jHypothesis jdg
27-
g = jGoal jdg
2860
case find ((== term) . fst) $ toList hy of
2961
Nothing -> throwError $ UndefinedHypothesis term
3062
Just (_, t) ->
31-
case splitTyConApp_maybe $ unCType t of
32-
Nothing -> throwError $ GoalMismatch "destruct" g
33-
Just (tc, apps) ->
34-
fmap noLoc $ case' (var' term) <$> do
35-
let dcs = tyConDataCons tc
36-
case dcs of
37-
[] -> throwError $ GoalMismatch "destruct" g
38-
_ -> for dcs $ \dc -> do
39-
let args = dataConInstArgTys dc apps
40-
names <- mkManyGoodNames hy args
63+
fmap noLoc $ case' (var' term) <$>
64+
destructMatches f (destructing term) t jdg
65+
4166

42-
let pat :: Pat GhcPs
43-
pat = conP (fromString $ occNameString $ nameOccName $ dataConName dc)
44-
$ fmap bvar' names
45-
j = destructing term
46-
$ introducingPat (zip names $ coerce args)
47-
$ withNewGoal g jdg
48-
sg <- f dc j
49-
pure $ match [pat] $ unLoc sg
67+
------------------------------------------------------------------------------
68+
-- | Combinator for performign case splitting, and running sub-rules on the
69+
-- resulting matches.
70+
destructLambdaCase' :: (DataCon -> Judgement -> Rule) -> Judgement -> Rule
71+
destructLambdaCase' f jdg = do
72+
let g = jGoal jdg
73+
case splitFunTy_maybe (unCType g) of
74+
Just (arg, _) | isAlgType arg ->
75+
fmap noLoc $ lambdaCase <$>
76+
destructMatches f id (CType arg) jdg
77+
_ -> throwError $ GoalMismatch "destructLambdaCase'" g
5078

5179

5280
------------------------------------------------------------------------------

plugins/tactics/src/Ide/Plugin/Tactic/GHC.hs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
module Ide.Plugin.Tactic.GHC where
44

5+
import Data.Maybe (isJust)
56
import TcType
67
import TyCoRep
78
import TyCon
@@ -54,3 +55,15 @@ algebraicTyCon (splitTyConApp_maybe -> Just (tycon, _))
5455
| otherwise = Just tycon
5556
algebraicTyCon _ = Nothing
5657

58+
------------------------------------------------------------------------------
59+
-- | Can ths type be lambda-cased?
60+
--
61+
-- Return: 'Nothing' if no
62+
-- @Just False@ if it can't be homomorphic
63+
-- @Just True@ if it can
64+
lambdaCaseable :: Type -> Maybe Bool
65+
lambdaCaseable (splitFunTy_maybe -> Just (arg, res))
66+
| isJust (algebraicTyCon arg)
67+
= Just $ isJust $ algebraicTyCon res
68+
lambdaCaseable _ = Nothing
69+

plugins/tactics/src/Ide/Plugin/Tactic/Tactics.hs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,19 @@ homo = rule . destruct' (\dc jdg ->
110110
buildDataCon jdg dc $ snd $ splitAppTys $ unCType $ jGoal jdg)
111111

112112

113+
------------------------------------------------------------------------------
114+
-- | LambdaCase split, and leave holes in the matches.
115+
destructLambdaCase :: TacticsM ()
116+
destructLambdaCase = rule $ destructLambdaCase' (const subgoal)
117+
118+
119+
------------------------------------------------------------------------------
120+
-- | LambdaCase split, using the same data constructor in the matches.
121+
homoLambdaCase :: TacticsM ()
122+
homoLambdaCase = rule $ destructLambdaCase' (\dc jdg ->
123+
buildDataCon jdg dc $ snd $ splitAppTys $ unCType $ jGoal jdg)
124+
125+
113126
apply' :: OccName -> TacticsM ()
114127
apply' func = rule $ \jdg -> do
115128
let hy = jHypothesis jdg

plugins/tactics/src/Ide/Plugin/Tactic/Types.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,21 @@ import Type
3737
-- editor via 'commandProvider'.
3838
data TacticCommand
3939
= Auto
40-
| Split
41-
| Intro
4240
| Intros
4341
| Destruct
4442
| Homomorphism
43+
| DestructLambdaCase
44+
| HomomorphismLambdaCase
4545
deriving (Eq, Ord, Show, Enum, Bounded)
4646

4747
-- | Generate a title for the command.
4848
tacticTitle :: TacticCommand -> T.Text -> T.Text
4949
tacticTitle Auto _ = "Auto"
50-
tacticTitle Split _ = "Auto"
51-
tacticTitle Intro _ = "Intro"
5250
tacticTitle Intros _ = "Introduce lambda"
5351
tacticTitle Destruct var = "Case split on " <> var
5452
tacticTitle Homomorphism var = "Homomorphic case split on " <> var
53+
tacticTitle DestructLambdaCase _ = "Lambda case split"
54+
tacticTitle HomomorphismLambdaCase _ = "Homomorphic lambda case split"
5555

5656
------------------------------------------------------------------------------
5757
-- | A wrapper around 'Type' which supports equality and ordering.

0 commit comments

Comments
 (0)