Skip to content

Commit de4e387

Browse files
authored
Use infix notation for destructing and splitting infix data cons in tactics (#519)
The tactics plugin is a bit stupid when working with infix-defined datacons, both in expressions and patterns. For example it will produce (,) a b and (:) a as rather than the more natural (a, b) and a : as. This PR makes it do the right thing. The solution is to inspect the data con when building an expression or pattern. Unfortunately tuples are extra special in GHC, so this introduces a special case for tuples, and another for everyday infix things (like list). There's a bit of annoying fiddling in order to build the infix pattern. The logic is in infixifyPatIfNecessary, which is the only thing I'm not super comfortable with in the diff. Fixes #468
1 parent 1a869ad commit de4e387

File tree

8 files changed

+61
-14
lines changed

8 files changed

+61
-14
lines changed

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

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
{-# LANGUAGE TupleSections #-}
21
{-# LANGUAGE FlexibleContexts #-}
2+
{-# LANGUAGE TupleSections #-}
3+
{-# LANGUAGE ViewPatterns #-}
4+
35
module Ide.Plugin.Tactic.CodeGen where
46

57
import Control.Monad.Except
@@ -12,6 +14,7 @@ import Data.Traversable
1214
import DataCon
1315
import Development.IDE.GHC.Compat
1416
import GHC.Exts
17+
import GHC.SourceGen (RdrNameStr)
1518
import GHC.SourceGen.Binds
1619
import GHC.SourceGen.Expr
1720
import GHC.SourceGen.Overloaded
@@ -55,10 +58,7 @@ destructMatches f f2 t jdg = do
5558
let hy' = zip names $ coerce args
5659
dcon_name = nameOccName $ dataConName dc
5760

58-
let pat :: Pat GhcPs
59-
pat = conP (fromString $ occNameString dcon_name)
60-
$ fmap bvar' names
61-
j = f2 hy'
61+
let j = f2 hy'
6262
$ withPositionMapping dcon_name names
6363
$ introducingPat hy'
6464
$ withNewGoal g jdg
@@ -67,10 +67,36 @@ destructMatches f f2 t jdg = do
6767
pure ( rose ("match " <> show dc <> " {" <>
6868
intercalate ", " (fmap show names) <> "}")
6969
$ pure tr
70-
, match [pat] $ unLoc sg
70+
, match [mkDestructPat dc names] $ unLoc sg
7171
)
7272

7373

74+
------------------------------------------------------------------------------
75+
-- | Produces a pattern for a data con and the names of its fields.
76+
mkDestructPat :: DataCon -> [OccName] -> Pat GhcPs
77+
mkDestructPat dcon names
78+
| isTupleDataCon dcon =
79+
tuple pat_args
80+
| otherwise =
81+
infixifyPatIfNecessary dcon $
82+
conP
83+
(coerceName $ dataConName dcon)
84+
pat_args
85+
where
86+
pat_args = fmap bvar' names
87+
88+
89+
infixifyPatIfNecessary :: DataCon -> Pat GhcPs -> Pat GhcPs
90+
infixifyPatIfNecessary dcon x
91+
| dataConIsInfix dcon =
92+
case x of
93+
ConPatIn op (PrefixCon [lhs, rhs]) ->
94+
ConPatIn op $ InfixCon lhs rhs
95+
y -> y
96+
| otherwise = x
97+
98+
99+
74100
unzipTrace :: [(Trace, a)] -> (Trace, [a])
75101
unzipTrace l =
76102
let (trs, as) = unzip l
@@ -144,10 +170,26 @@ buildDataCon jdg dc apps = do
144170
) $ zip args [0..]
145171
pure
146172
. (rose (show dc) $ pure tr,)
147-
. noLoc
148-
. foldl' (@@)
149-
(HsVar noExtField $ noLoc $ Unqual $ nameOccName $ dataConName dc)
150-
$ fmap unLoc sgs
173+
$ mkCon dc sgs
174+
175+
176+
mkCon :: DataCon -> [LHsExpr GhcPs] -> LHsExpr GhcPs
177+
mkCon dcon (fmap unLoc -> args)
178+
| isTupleDataCon dcon =
179+
noLoc $ tuple args
180+
| dataConIsInfix dcon
181+
, (lhs : rhs : args') <- args =
182+
noLoc $ foldl' (@@) (op lhs (coerceName dcon_name) rhs) args'
183+
| otherwise =
184+
noLoc $ foldl' (@@) (bvar' $ occName $ dcon_name) args
185+
where
186+
dcon_name = dataConName dcon
187+
188+
189+
190+
coerceName :: HasOccName a => a -> RdrNameStr
191+
coerceName = fromString . occNameString . occName
192+
151193

152194

153195
------------------------------------------------------------------------------

test/functional/Tactic.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ tests = testGroup
100100
, goldenTest "GoldenFmapTree.hs" 4 11 Auto ""
101101
, goldenTest "GoldenGADTDestruct.hs" 7 17 Destruct "gadt"
102102
, goldenTest "GoldenGADTAuto.hs" 7 13 Auto ""
103+
, goldenTest "GoldenSwapMany.hs" 2 12 Auto ""
103104
]
104105

105106

test/testdata/tactic/GoldenFoldr.hs.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ foldr2 :: (a -> b -> b) -> b -> [a] -> b
22
foldr2 = (\ f_b b l_a
33
-> case l_a of
44
[] -> b
5-
((:) a l_a4) -> f_b a (foldr2 f_b b l_a4))
5+
(a : l_a4) -> f_b a (foldr2 f_b b l_a4))

test/testdata/tactic/GoldenListFmap.hs.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ fmapList :: (a -> b) -> [a] -> [b]
22
fmapList = (\ fab l_a
33
-> case l_a of
44
[] -> []
5-
((:) a l_a3) -> (:) (fab a) (fmapList fab l_a3))
5+
(a : l_a3) -> fab a : fmapList fab l_a3)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
pureList :: a -> [a]
2-
pureList = (\ a -> (:) a [])
2+
pureList = (\ a -> a : [])
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
swap :: (a, b) -> (b, a)
2-
swap = (\ p_ab -> case p_ab of { ((,) a b) -> (,) b a })
2+
swap = (\ p_ab -> case p_ab of { (a, b) -> (b, a) })
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
swapMany :: (a, b, c, d, e) -> (e, d, c, b, a)
2+
swapMany = _
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
swapMany :: (a, b, c, d, e) -> (e, d, c, b, a)
2+
swapMany = (\ pabcde -> case pabcde of { (a, b, c, d, e) -> (e, d, c, b, a) })

0 commit comments

Comments
 (0)