Skip to content

Commit b5c6dd0

Browse files
authored
Wingman idioms (#2607)
* Allow idiom brackets * Allow record construction idioms * expected result * Fix noExtField
1 parent 2558035 commit b5c6dd0

File tree

10 files changed

+135
-2
lines changed

10 files changed

+135
-2
lines changed

plugins/hls-tactics-plugin/COMMANDS.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,30 @@ case e of
310310
Right b -> Right (_ :: y)
311311
```
312312

313+
## idiom
314+
315+
arguments: tactic.
316+
deterministic.
317+
318+
> Lift a tactic into idiom brackets.
319+
320+
321+
### Example
322+
323+
Given:
324+
325+
```haskell
326+
f :: a -> b -> Int
327+
328+
_ :: Maybe Int
329+
```
330+
331+
running `idiom (apply f)` will produce:
332+
333+
```haskell
334+
f <$> (_ :: Maybe a) <*> (_ :: Maybe b)
335+
```
336+
313337
## intro
314338

315339
arguments: single binding.

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,3 +331,16 @@ nonrecLet occjdgs jdg = do
331331
(zip (fmap fst occjdgs) occexts)
332332
<*> fmap unLoc ext
333333

334+
335+
------------------------------------------------------------------------------
336+
-- | Converts a function application into applicative form
337+
idiomize :: LHsExpr GhcPs -> LHsExpr GhcPs
338+
idiomize x = noLoc $ case unLoc x of
339+
HsApp _ (L _ (HsVar _ (L _ x))) gshgp3 ->
340+
op (bvar' $ occName x) "<$>" (unLoc gshgp3)
341+
HsApp _ gsigp gshgp3 ->
342+
op (unLoc $ idiomize gsigp) "<*>" (unLoc gshgp3)
343+
RecordCon _ con flds ->
344+
unLoc $ idiomize $ noLoc $ foldl' (@@) (HsVar noExtField con) $ fmap unLoc flds
345+
y -> y
346+

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ isSplitWhitelisted = _jWhitelistSplit
6666
withNewGoal :: a -> Judgement' a -> Judgement' a
6767
withNewGoal t = field @"_jGoal" .~ t
6868

69+
------------------------------------------------------------------------------
70+
-- | Like 'withNewGoal' but allows you to modify the goal rather than replacing
71+
-- it.
72+
withModifiedGoal :: (a -> a) -> Judgement' a -> Judgement' a
73+
withModifiedGoal f = field @"_jGoal" %~ f
74+
6975

7076
------------------------------------------------------------------------------
7177
-- | Add some new type equalities to the local judgement.

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,17 @@ commands =
9898
"\\x y z -> (_ :: d)"
9999
]
100100

101+
, command "idiom" Deterministic Tactic
102+
"Lift a tactic into idiom brackets."
103+
(pure . idiom)
104+
[ Example
105+
Nothing
106+
["(apply f)"]
107+
[EHI "f" "a -> b -> Int"]
108+
(Just "Maybe Int")
109+
"f <$> (_ :: Maybe a) <*> (_ :: Maybe b)"
110+
]
111+
101112
, command "intro" Deterministic (Bind One)
102113
"Construct a lambda expression, binding an argument with the given name."
103114
(pure . intros' . IntroduceOnlyNamed . pure)
@@ -415,7 +426,7 @@ oneTactic =
415426

416427

417428
tactic :: Parser (TacticsM ())
418-
tactic = P.makeExprParser oneTactic operators
429+
tactic = P.makeExprParser oneTactic operators
419430

420431
operators :: [[P.Operator Parser (TacticsM ())]]
421432
operators =

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

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ module Wingman.Tactics
99
import Control.Applicative (Alternative(empty), (<|>))
1010
import Control.Lens ((&), (%~), (<>~))
1111
import Control.Monad (filterM, unless)
12+
import Control.Monad (when)
1213
import Control.Monad.Extra (anyM)
1314
import Control.Monad.Reader.Class (MonadReader (ask))
14-
import Control.Monad.State.Strict (StateT(..), runStateT)
15+
import Control.Monad.State.Strict (StateT(..), runStateT, execStateT)
1516
import Data.Bool (bool)
1617
import Data.Foldable
1718
import Data.Functor ((<&>))
@@ -640,3 +641,51 @@ hyDiff m = do
640641
g' <- unHypothesis . jEntireHypothesis <$> goal
641642
pure $ Hypothesis $ take (length g' - g_len) g'
642643

644+
645+
------------------------------------------------------------------------------
646+
-- | Attempt to run the given tactic in "idiom bracket" mode. For example, if
647+
-- the current goal is
648+
--
649+
-- (_ :: [r])
650+
--
651+
-- then @idiom apply@ will remove the applicative context, resulting in a hole:
652+
--
653+
-- (_ :: r)
654+
--
655+
-- and then use @apply@ to solve it. Let's say this results in:
656+
--
657+
-- (f (_ :: a) (_ :: b))
658+
--
659+
-- Finally, @idiom@ lifts this back into the original applicative:
660+
--
661+
-- (f <$> (_ :: [a]) <*> (_ :: [b]))
662+
--
663+
-- Idiom will fail fast if the current goal doesn't have an applicative
664+
-- instance.
665+
idiom :: TacticsM () -> TacticsM ()
666+
idiom m = do
667+
jdg <- goal
668+
let hole = unCType $ jGoal jdg
669+
when (isFunction hole) $
670+
failure $ GoalMismatch "idiom" $ jGoal jdg
671+
case splitAppTy_maybe hole of
672+
Just (applic, ty) -> do
673+
minst <- getKnownInstance (mkClsOcc "Applicative")
674+
. pure
675+
$ applic
676+
case minst of
677+
Nothing -> failure $ GoalMismatch "idiom" $ CType applic
678+
Just (_, _) -> do
679+
rule $ \jdg -> do
680+
expr <- subgoalWith (withNewGoal (CType ty) jdg) m
681+
case unLoc $ syn_val expr of
682+
HsApp{} -> pure $ fmap idiomize expr
683+
RecordCon{} -> pure $ fmap idiomize expr
684+
_ -> unsolvable $ GoalMismatch "idiom" $ jGoal jdg
685+
rule $ newSubgoal . withModifiedGoal (CType . mkAppTy applic . unCType)
686+
Nothing ->
687+
failure $ GoalMismatch "idiom" $ jGoal jdg
688+
689+
subgoalWith :: Judgement -> TacticsM () -> RuleM (Synthesized (LHsExpr GhcPs))
690+
subgoalWith jdg t = RuleT $ flip execStateT jdg $ unTacticT t
691+

plugins/hls-tactics-plugin/test/CodeAction/RunMetaprogramSpec.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ spec = do
4040
metaTest 7 53 "MetaDeepOf"
4141
metaTest 2 34 "MetaWithArg"
4242
metaTest 2 18 "MetaLetSimple"
43+
metaTest 5 9 "MetaIdiom"
44+
metaTest 7 9 "MetaIdiomRecord"
4345

4446
metaTest 2 12 "IntrosTooMany"
4547

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
foo :: Int -> Int -> Int
2+
foo = undefined
3+
4+
test :: Maybe Int
5+
test = (foo <$> _w0) <*> _w1
6+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
foo :: Int -> Int -> Int
2+
foo = undefined
3+
4+
test :: Maybe Int
5+
test = [wingman| idiom (use foo) |]
6+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
data Rec = Rec
2+
{ a :: Int
3+
, b :: Bool
4+
}
5+
6+
test :: Maybe Rec
7+
test = (Rec <$> _w0) <*> _w1
8+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
data Rec = Rec
2+
{ a :: Int
3+
, b :: Bool
4+
}
5+
6+
test :: Maybe Rec
7+
test = [wingman| idiom (ctor Rec) |]
8+

0 commit comments

Comments
 (0)