Skip to content

Commit 73daeaa

Browse files
Add "Split all function arguments" code action (#1464)
* Add DestructAll tactic * Don't use guard in IO Missing features were accidentally blocking all code actions * Write a better provider and add tests * Haddock Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 76dadaa commit 73daeaa

File tree

11 files changed

+126
-2
lines changed

11 files changed

+126
-2
lines changed

plugins/hls-tactics-plugin/src/Ide/Plugin/Tactic/FeatureSet.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import qualified Data.Text as T
2222
-- | All the available features. A 'FeatureSet' describes the ones currently
2323
-- available to the user.
2424
data Feature
25-
= FeatureUseDataCon
25+
= FeatureDestructAll
26+
| FeatureUseDataCon
2627
| FeatureRefineHole
2728
deriving (Eq, Ord, Show, Read, Enum, Bounded)
2829

plugins/hls-tactics-plugin/src/Ide/Plugin/Tactic/Judgements.hs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,17 @@ hyNamesInScope :: Hypothesis a -> Set OccName
295295
hyNamesInScope = M.keysSet . hyByName
296296

297297

298+
------------------------------------------------------------------------------
299+
-- | Are there any top-level function argument bindings in this judgement?
300+
jHasBoundArgs :: Judgement' a -> Bool
301+
jHasBoundArgs
302+
= not
303+
. null
304+
. filter (isTopLevel . hi_provenance)
305+
. unHypothesis
306+
. jLocalHypothesis
307+
308+
298309
------------------------------------------------------------------------------
299310
-- | Fold a hypothesis into a single mapping from name to info. This
300311
-- unavoidably will cause duplicate names (things like methods) to shadow one

plugins/hls-tactics-plugin/src/Ide/Plugin/Tactic/LanguageServer/TacticProviders.hs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ commandTactic Destruct = useNameFromHypothesis destruct
5050
commandTactic Homomorphism = useNameFromHypothesis homo
5151
commandTactic DestructLambdaCase = const destructLambdaCase
5252
commandTactic HomomorphismLambdaCase = const homoLambdaCase
53+
commandTactic DestructAll = const destructAll
5354
commandTactic UseDataCon = userSplit
5455
commandTactic Refine = const refine
5556

@@ -76,6 +77,12 @@ commandProvider HomomorphismLambdaCase =
7677
requireExtension LambdaCase $
7778
filterGoalType ((== Just True) . lambdaCaseable) $
7879
provide HomomorphismLambdaCase ""
80+
commandProvider DestructAll =
81+
requireFeature FeatureDestructAll $
82+
withJudgement $ \jdg ->
83+
case _jIsTopHole jdg && jHasBoundArgs jdg of
84+
True -> provide DestructAll ""
85+
False -> mempty
7986
commandProvider UseDataCon =
8087
withConfig $ \cfg ->
8188
requireFeature FeatureUseDataCon $
@@ -153,6 +160,14 @@ filterGoalType p tp dflags cfg plId uri range jdg =
153160
False -> pure []
154161

155162

163+
------------------------------------------------------------------------------
164+
-- | Restrict a 'TacticProvider', making sure it appears only when the given
165+
-- predicate holds for the goal.
166+
withJudgement :: (Judgement -> TacticProvider) -> TacticProvider
167+
withJudgement tp dflags fs plId uri range jdg =
168+
tp jdg dflags fs plId uri range jdg
169+
170+
156171
------------------------------------------------------------------------------
157172
-- | Multiply a 'TacticProvider' for each binding, making sure it appears only
158173
-- when the given predicate holds over the goal and binding types.

plugins/hls-tactics-plugin/src/Ide/Plugin/Tactic/Tactics.hs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,24 @@ splitDataCon dc =
256256

257257

258258
------------------------------------------------------------------------------
259+
-- | Perform a case split on each top-level argument. Used to implement the
260+
-- "Destruct all function arguments" action.
261+
destructAll :: TacticsM ()
262+
destructAll = do
263+
jdg <- goal
264+
let args = fmap fst
265+
$ sortOn (Down . snd)
266+
$ mapMaybe (\(hi, prov) ->
267+
case prov of
268+
TopLevelArgPrv _ idx _ -> pure (hi, idx)
269+
_ -> Nothing
270+
)
271+
$ fmap (\hi -> (hi, hi_provenance hi))
272+
$ unHypothesis
273+
$ jHypothesis jdg
274+
for_ args destruct
275+
276+
--------------------------------------------------------------------------------
259277
-- | User-facing tactic to implement "Use constructor <x>"
260278
userSplit :: OccName -> TacticsM ()
261279
userSplit occ = do

plugins/hls-tactics-plugin/src/Ide/Plugin/Tactic/TestTypes.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ data TacticCommand
1919
| Homomorphism
2020
| DestructLambdaCase
2121
| HomomorphismLambdaCase
22+
| DestructAll
2223
| UseDataCon
2324
| Refine
2425
deriving (Eq, Ord, Show, Enum, Bounded)
@@ -31,6 +32,7 @@ tacticTitle Destruct var = "Case split on " <> var
3132
tacticTitle Homomorphism var = "Homomorphic case split on " <> var
3233
tacticTitle DestructLambdaCase _ = "Lambda case split"
3334
tacticTitle HomomorphismLambdaCase _ = "Homomorphic lambda case split"
35+
tacticTitle DestructAll _ = "Split all function arguments"
3436
tacticTitle UseDataCon dcon = "Use constructor " <> dcon
3537
tacticTitle Refine _ = "Refine hole"
3638

plugins/hls-tactics-plugin/test/GoldenSpec.hs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,34 @@ spec = do
7373

7474
let goldenTest = mkGoldenTest allFeatures
7575

76+
-- test via:
77+
-- stack test hls-tactics-plugin --test-arguments '--match "Golden/destruct all/"'
78+
describe "destruct all" $ do
79+
let destructAllTest = mkGoldenTest allFeatures DestructAll ""
80+
describe "provider" $ do
81+
mkTest
82+
"Requires args on lhs of ="
83+
"DestructAllProvider.hs" 3 21
84+
[ (not, DestructAll, "")
85+
]
86+
mkTest
87+
"Can't be a non-top-hole"
88+
"DestructAllProvider.hs" 8 19
89+
[ (not, DestructAll, "")
90+
, (id, Destruct, "a")
91+
, (id, Destruct, "b")
92+
]
93+
mkTest
94+
"Provides a destruct all otherwise"
95+
"DestructAllProvider.hs" 12 22
96+
[ (id, DestructAll, "")
97+
]
98+
99+
describe "golden" $ do
100+
destructAllTest "DestructAllAnd.hs" 2 11
101+
destructAllTest "DestructAllMany.hs" 4 23
102+
103+
76104
-- test via:
77105
-- stack test hls-tactics-plugin --test-arguments '--match "Golden/use constructor/"'
78106
describe "use constructor" $ do
@@ -115,7 +143,6 @@ spec = do
115143
refineTest "RefineReader.hs" 4 8
116144
refineTest "RefineGADT.hs" 8 8
117145

118-
119146
describe "golden tests" $ do
120147
let autoTest = mkGoldenTest allFeatures Auto ""
121148

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
and :: Bool -> Bool -> Bool
2+
and x y = _
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
and :: Bool -> Bool -> Bool
2+
and False False = _
3+
and True False = _
4+
and False True = _
5+
and True True = _
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
data ABC = A | B | C
2+
3+
many :: () -> Either a b -> Bool -> Maybe ABC -> ABC -> ()
4+
many u e b mabc abc = _
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
data ABC = A | B | C
2+
3+
many :: () -> Either a b -> Bool -> Maybe ABC -> ABC -> ()
4+
many () (Left a) False Nothing A = _
5+
many () (Right b5) False Nothing A = _
6+
many () (Left a) True Nothing A = _
7+
many () (Right b5) True Nothing A = _
8+
many () (Left a6) False (Just a) A = _
9+
many () (Right b6) False (Just a) A = _
10+
many () (Left a6) True (Just a) A = _
11+
many () (Right b6) True (Just a) A = _
12+
many () (Left a) False Nothing B = _
13+
many () (Right b5) False Nothing B = _
14+
many () (Left a) True Nothing B = _
15+
many () (Right b5) True Nothing B = _
16+
many () (Left a6) False (Just a) B = _
17+
many () (Right b6) False (Just a) B = _
18+
many () (Left a6) True (Just a) B = _
19+
many () (Right b6) True (Just a) B = _
20+
many () (Left a) False Nothing C = _
21+
many () (Right b5) False Nothing C = _
22+
many () (Left a) True Nothing C = _
23+
many () (Right b5) True Nothing C = _
24+
many () (Left a6) False (Just a) C = _
25+
many () (Right b6) False (Just a) C = _
26+
many () (Left a6) True (Just a) C = _
27+
many () (Right b6) True (Just a) C = _
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- we need to name the args ourselves first
2+
nothingToDestruct :: [a] -> [a] -> [a]
3+
nothingToDestruct = _
4+
5+
6+
-- can't destruct all for non-top-level holes
7+
notTop :: Bool -> Bool -> Bool
8+
notTop a b = a && _
9+
10+
-- destruct all is ok
11+
canDestructAll :: Bool -> Bool -> Bool
12+
canDestructAll a b = _

0 commit comments

Comments
 (0)