Skip to content

Commit 463d804

Browse files
authored
Wingman: "Intro and destruct" code action (#2077)
* Make intros' more configurable * Add a new "introduce and destruct" code action * Add tests * Add provider tests
1 parent 0a6c872 commit 463d804

13 files changed

+121
-8
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ test-suite tests
129129
CodeAction.DestructPunSpec
130130
CodeAction.DestructSpec
131131
CodeAction.IntrosSpec
132+
CodeAction.IntroDestructSpec
132133
CodeAction.RefineSpec
133134
CodeAction.RunMetaprogramSpec
134135
CodeAction.UseDataConSpec

plugins/hls-tactics-plugin/src/Wingman/LanguageServer/TacticProviders.hs

+8-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import Wingman.Types
4747
commandTactic :: TacticCommand -> T.Text -> TacticsM ()
4848
commandTactic Auto = const auto
4949
commandTactic Intros = const intros
50+
commandTactic IntroAndDestruct = const introAndDestruct
5051
commandTactic Destruct = useNameFromHypothesis destruct . mkVarOcc . T.unpack
5152
commandTactic DestructPun = useNameFromHypothesis destructPun . mkVarOcc . T.unpack
5253
commandTactic Homomorphism = useNameFromHypothesis homo . mkVarOcc . T.unpack
@@ -64,6 +65,7 @@ commandTactic RunMetaprogram = parseMetaprogram
6465
tacticKind :: TacticCommand -> T.Text
6566
tacticKind Auto = "fillHole"
6667
tacticKind Intros = "introduceLambda"
68+
tacticKind IntroAndDestruct = "introduceAndDestruct"
6769
tacticKind Destruct = "caseSplit"
6870
tacticKind DestructPun = "caseSplitPun"
6971
tacticKind Homomorphism = "homomorphicCaseSplit"
@@ -82,9 +84,10 @@ tacticKind RunMetaprogram = "runMetaprogram"
8284
tacticPreferred :: TacticCommand -> Bool
8385
tacticPreferred Auto = True
8486
tacticPreferred Intros = True
87+
tacticPreferred IntroAndDestruct = True
8588
tacticPreferred Destruct = True
8689
tacticPreferred DestructPun = False
87-
tacticPreferred Homomorphism = False
90+
tacticPreferred Homomorphism = True
8891
tacticPreferred DestructLambdaCase = False
8992
tacticPreferred HomomorphismLambdaCase = False
9093
tacticPreferred DestructAll = True
@@ -110,6 +113,10 @@ commandProvider Intros =
110113
requireHoleSort (== Hole) $
111114
filterGoalType isFunction $
112115
provide Intros ""
116+
commandProvider IntroAndDestruct =
117+
requireHoleSort (== Hole) $
118+
filterGoalType (liftLambdaCase False (\_ -> isJust . algebraicTyCon)) $
119+
provide IntroAndDestruct ""
113120
commandProvider Destruct =
114121
requireHoleSort (== Hole) $
115122
filterBindingType destructFilter $ \occ _ ->

plugins/hls-tactics-plugin/src/Wingman/Metaprogramming/Parser.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ commands =
7676
])
7777
(pure . \case
7878
[] -> intros
79-
names -> intros' $ Just names
79+
names -> intros' $ IntroduceOnlyNamed names
8080
)
8181
[ Example
8282
Nothing
@@ -100,7 +100,7 @@ commands =
100100

101101
, command "intro" Deterministic (Bind One)
102102
"Construct a lambda expression, binding an argument with the given name."
103-
(pure . intros' . Just . pure)
103+
(pure . intros' . IntroduceOnlyNamed . pure)
104104
[ Example
105105
Nothing
106106
["aye"]

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

+34-5
Original file line numberDiff line numberDiff line change
@@ -117,21 +117,32 @@ restrictPositionForApplication f app = do
117117
------------------------------------------------------------------------------
118118
-- | Introduce a lambda binding every variable.
119119
intros :: TacticsM ()
120-
intros = intros' Nothing
120+
intros = intros' IntroduceAllUnnamed
121+
122+
123+
data IntroParams
124+
= IntroduceAllUnnamed
125+
| IntroduceOnlyNamed [OccName]
126+
| IntroduceOnlyUnnamed Int
127+
deriving stock (Eq, Ord, Show)
128+
121129

122130
------------------------------------------------------------------------------
123131
-- | Introduce a lambda binding every variable.
124132
intros'
125-
:: Maybe [OccName] -- ^ When 'Nothing', generate a new name for every
126-
-- variable. Otherwise, only bind the variables named.
133+
:: IntroParams
127134
-> TacticsM ()
128-
intros' names = rule $ \jdg -> do
135+
intros' params = rule $ \jdg -> do
129136
let g = jGoal jdg
130137
case tacticsSplitFunTy $ unCType g of
131138
(_, _, [], _) -> cut -- failure $ GoalMismatch "intros" g
132139
(_, _, args, res) -> do
133140
ctx <- ask
134-
let occs = fromMaybe (mkManyGoodNames (hyNamesInScope $ jEntireHypothesis jdg) args) names
141+
let gen_names = mkManyGoodNames (hyNamesInScope $ jEntireHypothesis jdg) args
142+
occs = case params of
143+
IntroduceAllUnnamed -> gen_names
144+
IntroduceOnlyNamed names -> names
145+
IntroduceOnlyUnnamed n -> take n gen_names
135146
num_occs = length occs
136147
top_hole = isTopHole ctx jdg
137148
bindings = zip occs $ coerce args
@@ -148,6 +159,24 @@ intros' names = rule $ \jdg -> do
148159
& #syn_val %~ noLoc . lambda (fmap bvar' bound_occs) . unLoc
149160

150161

162+
------------------------------------------------------------------------------
163+
-- | Introduce a single lambda argument, and immediately destruct it.
164+
introAndDestruct :: TacticsM ()
165+
introAndDestruct = do
166+
hy <- fmap unHypothesis $ hyDiff $ intros' $ IntroduceOnlyUnnamed 1
167+
-- This case should never happen, but I'm validating instead of parsing.
168+
-- Adding a log to be reminded if the invariant ever goes false.
169+
--
170+
-- But note that this isn't a game-ending bug. In the worst case, we'll
171+
-- accidentally bind too many variables, and incorrectly unify between them.
172+
-- Which means some GADT cases that should be eliminated won't be --- not the
173+
-- end of the world.
174+
unless (length hy == 1) $
175+
traceMX "BUG: Introduced too many variables for introAndDestruct! Please report me if you see this! " hy
176+
177+
for_ hy destruct
178+
179+
151180
------------------------------------------------------------------------------
152181
-- | Case split, and leave holes in the matches.
153182
destructAuto :: HyInfo CType -> TacticsM ()

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

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import Data.IORef
5959
data TacticCommand
6060
= Auto
6161
| Intros
62+
| IntroAndDestruct
6263
| Destruct
6364
| DestructPun
6465
| Homomorphism
@@ -77,6 +78,7 @@ tacticTitle = (mappend "Wingman: " .) . go
7778
where
7879
go Auto _ = "Attempt to fill hole"
7980
go Intros _ = "Introduce lambda"
81+
go IntroAndDestruct _ = "Introduce and destruct term"
8082
go Destruct var = "Case split on " <> var
8183
go DestructPun var = "Split on " <> var <> " with NamedFieldPuns"
8284
go Homomorphism var = "Homomorphic case split on " <> var
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
3+
module CodeAction.IntroDestructSpec where
4+
5+
import Wingman.Types
6+
import Test.Hspec
7+
import Utils
8+
9+
10+
spec :: Spec
11+
spec = do
12+
let test l c = goldenTest IntroAndDestruct "" l c
13+
. mappend "IntroDestruct"
14+
15+
describe "golden" $ do
16+
test 4 5 "One"
17+
test 2 5 "Many"
18+
test 4 11 "LetBinding"
19+
20+
describe "provider" $ do
21+
mkTest
22+
"Can intro and destruct an algebraic ty"
23+
"IntroDestructProvider" 2 12
24+
[ (id, IntroAndDestruct, "")
25+
]
26+
mkTest
27+
"Won't intro and destruct a non-algebraic ty"
28+
"IntroDestructProvider" 5 12
29+
[ (not, IntroAndDestruct, "")
30+
]
31+
mkTest
32+
"Can't intro, so no option"
33+
"IntroDestructProvider" 8 17
34+
[ (not, IntroAndDestruct, "")
35+
]
36+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
test :: IO ()
2+
test = do
3+
let x :: Bool -> Int
4+
x False = _w0
5+
x True = _w1
6+
pure ()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
test :: IO ()
2+
test = do
3+
let x :: Bool -> Int
4+
x = _
5+
pure ()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
x :: Bool -> Maybe Int -> String -> Int
2+
x False = _w0
3+
x True = _w1
4+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
x :: Bool -> Maybe Int -> String -> Int
2+
x = _
3+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module Test where
2+
3+
x :: Bool -> Int
4+
x False = _w0
5+
x True = _w1
6+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module Test where
2+
3+
x :: Bool -> Int
4+
x = _
5+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
hasAlgTy :: Maybe Int -> Int
2+
hasAlgTy = _
3+
4+
hasFunTy :: (Int -> Int) -> Int
5+
hasFunTy = _
6+
7+
isSaturated :: Bool -> Int
8+
isSaturated b = _
9+

0 commit comments

Comments
 (0)