Skip to content

Commit 12bcb50

Browse files
authored
Be much more intelligent about splitting matches (#1543)
* Be much more intelligent about splitting matches * Add some tests * Hlint makes my life worse * I fixed the build * Correctly case split on let bindings * Fix a missing space on grafting let..in * Add view pattern layout test * Add haddock to ExactPrint * Remove a call to undefined * Add test for pattern synonym splits * Fix subtly broken old tests * Insert a line before the first match instead of the decl * Fix a bug in newline placement for middle matches * Add note on monotonicity * Slightly adjust the wording on largestM
1 parent ac14ad2 commit 12bcb50

23 files changed

+314
-186
lines changed

ghcide/src/Development/IDE/GHC/ExactPrint.hs

+149-71
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
{-# LANGUAGE CPP #-}
2-
{-# LANGUAGE DerivingStrategies #-}
3-
{-# LANGUAGE GADTs #-}
4-
{-# LANGUAGE OverloadedStrings #-}
5-
{-# LANGUAGE RankNTypes #-}
6-
{-# LANGUAGE TypeFamilies #-}
1+
{-# LANGUAGE CPP #-}
2+
{-# LANGUAGE DerivingVia #-}
3+
{-# LANGUAGE GADTs #-}
4+
{-# LANGUAGE RankNTypes #-}
5+
{-# LANGUAGE TypeFamilies #-}
76

87
module Development.IDE.GHC.ExactPrint
98
( Graft(..),
109
graftDecls,
1110
graftDeclsWithM,
1211
annotate,
12+
annotateDecl,
1313
hoistGraft,
1414
graftWithM,
15-
graftWithSmallestM,
16-
graftSmallestDecls,
15+
genericGraftWithSmallestM,
16+
genericGraftWithLargestM,
1717
graftSmallestDeclsWithM,
1818
transform,
1919
transformM,
@@ -27,6 +27,9 @@ module Development.IDE.GHC.ExactPrint
2727
TransformT,
2828
Anns,
2929
Annotate,
30+
mkBindListT,
31+
setPrecedingLinesT,
32+
everywhereM',
3033
)
3134
where
3235

@@ -63,7 +66,8 @@ import Parser (parseIdentifier)
6366
import Data.Traversable (for)
6467
import Data.Foldable (Foldable(fold))
6568
import Data.Bool (bool)
66-
import Data.Monoid (All(All))
69+
import Data.Monoid (All(All), Any(Any))
70+
import Data.Functor.Compose (Compose(Compose))
6771
#if __GLASGOW_HASKELL__ == 808
6872
import Control.Arrow
6973
#endif
@@ -195,7 +199,7 @@ needsParensSpace ExplicitSum{} = (All False, All False)
195199
needsParensSpace HsCase{} = (All False, All False)
196200
needsParensSpace HsIf{} = (All False, All False)
197201
needsParensSpace HsMultiIf{} = (All False, All False)
198-
needsParensSpace HsLet{} = (All False, All False)
202+
needsParensSpace HsLet{} = (All False, All True)
199203
needsParensSpace HsDo{} = (All False, All False)
200204
needsParensSpace ExplicitList{} = (All False, All False)
201205
needsParensSpace RecordCon{} = (All False, All False)
@@ -286,30 +290,56 @@ graftWithM dst trans = Graft $ \dflags a -> do
286290
)
287291
a
288292

289-
graftWithSmallestM ::
290-
forall ast m a.
291-
(Fail.MonadFail m, Data a, ASTElement ast) =>
293+
-- | A generic query intended to be used for calling 'smallestM' and
294+
-- 'largestM'. If the current node is a 'Located', returns whether or not the
295+
-- given 'SrcSpan' is a subspan. For all other nodes, returns 'Nothing', which
296+
-- indicates uncertainty. The search strategy in 'smallestM' et al. will
297+
-- continue searching uncertain nodes.
298+
genericIsSubspan ::
299+
forall ast.
300+
Typeable ast =>
301+
-- | The type of nodes we'd like to consider.
302+
Proxy (Located ast) ->
292303
SrcSpan ->
293-
(Located ast -> TransformT m (Maybe (Located ast))) ->
304+
GenericQ (Maybe Bool)
305+
genericIsSubspan _ dst = mkQ Nothing $ \case
306+
(L span _ :: Located ast) -> Just $ dst `isSubspanOf` span
307+
308+
-- | Run the given transformation only on the smallest node in the tree that
309+
-- contains the 'SrcSpan'.
310+
genericGraftWithSmallestM ::
311+
forall m a ast.
312+
(Monad m, Data a, Typeable ast) =>
313+
-- | The type of nodes we'd like to consider when finding the smallest.
314+
Proxy (Located ast) ->
315+
SrcSpan ->
316+
(DynFlags -> GenericM (TransformT m)) ->
294317
Graft m a
295-
graftWithSmallestM dst trans = Graft $ \dflags a -> do
296-
everywhereM'
297-
( mkM $
298-
\case
299-
val@(L src _ :: Located ast)
300-
| dst `isSubspanOf` src -> do
301-
mval <- trans val
302-
case mval of
303-
Just val' -> do
304-
(anns, val'') <-
305-
hoistTransform (either Fail.fail pure) $
306-
annotate dflags True $ maybeParensAST val'
307-
modifyAnnsT $ mappend anns
308-
pure val''
309-
Nothing -> pure val
310-
l -> pure l
311-
)
312-
a
318+
genericGraftWithSmallestM proxy dst trans = Graft $ \dflags ->
319+
smallestM (genericIsSubspan proxy dst) (trans dflags)
320+
321+
-- | Run the given transformation only on the largest node in the tree that
322+
-- contains the 'SrcSpan'.
323+
genericGraftWithLargestM ::
324+
forall m a ast.
325+
(Monad m, Data a, Typeable ast) =>
326+
-- | The type of nodes we'd like to consider when finding the largest.
327+
Proxy (Located ast) ->
328+
SrcSpan ->
329+
(DynFlags -> GenericM (TransformT m)) ->
330+
Graft m a
331+
genericGraftWithLargestM proxy dst trans = Graft $ \dflags ->
332+
largestM (genericIsSubspan proxy dst) (trans dflags)
333+
334+
335+
-- | Lift a function that replaces a value with several values into a generic
336+
-- function. The result doesn't perform any searching, so should be driven via
337+
-- 'everywhereM' or friends.
338+
--
339+
-- The 'Int' argument is the index in the list being bound.
340+
mkBindListT :: forall b m. (Typeable b, Data b, Monad m) => (Int -> b -> m [b]) -> GenericM m
341+
mkBindListT f = mkM $ fmap join . traverse (uncurry f) . zip [0..]
342+
313343

314344
graftDecls ::
315345
forall a.
@@ -319,32 +349,13 @@ graftDecls ::
319349
Graft (Either String) a
320350
graftDecls dst decs0 = Graft $ \dflags a -> do
321351
decs <- forM decs0 $ \decl -> do
322-
(anns, decl') <- annotateDecl dflags decl
323-
modifyAnnsT $ mappend anns
324-
pure decl'
352+
annotateDecl dflags decl
325353
let go [] = DL.empty
326354
go (L src e : rest)
327355
| src == dst = DL.fromList decs <> DL.fromList rest
328356
| otherwise = DL.singleton (L src e) <> go rest
329357
modifyDeclsT (pure . DL.toList . go) a
330358

331-
graftSmallestDecls ::
332-
forall a.
333-
(HasDecls a) =>
334-
SrcSpan ->
335-
[LHsDecl GhcPs] ->
336-
Graft (Either String) a
337-
graftSmallestDecls dst decs0 = Graft $ \dflags a -> do
338-
decs <- forM decs0 $ \decl -> do
339-
(anns, decl') <- annotateDecl dflags decl
340-
modifyAnnsT $ mappend anns
341-
pure decl'
342-
let go [] = DL.empty
343-
go (L src e : rest)
344-
| dst `isSubspanOf` src = DL.fromList decs <> DL.fromList rest
345-
| otherwise = DL.singleton (L src e) <> go rest
346-
modifyDeclsT (pure . DL.toList . go) a
347-
348359
graftSmallestDeclsWithM ::
349360
forall a.
350361
(HasDecls a) =>
@@ -356,11 +367,8 @@ graftSmallestDeclsWithM dst toDecls = Graft $ \dflags a -> do
356367
go (e@(L src _) : rest)
357368
| dst `isSubspanOf` src = toDecls e >>= \case
358369
Just decs0 -> do
359-
decs <- forM decs0 $ \decl -> do
360-
(anns, decl') <-
361-
annotateDecl dflags decl
362-
modifyAnnsT $ mappend anns
363-
pure decl'
370+
decs <- forM decs0 $ \decl ->
371+
annotateDecl dflags decl
364372
pure $ DL.fromList decs <> DL.fromList rest
365373
Nothing -> (DL.singleton e <>) <$> go rest
366374
| otherwise = (DL.singleton e <>) <$> go rest
@@ -377,12 +385,9 @@ graftDeclsWithM dst toDecls = Graft $ \dflags a -> do
377385
go (e@(L src _) : rest)
378386
| src == dst = toDecls e >>= \case
379387
Just decs0 -> do
380-
decs <- forM decs0 $ \decl -> do
381-
(anns, decl') <-
382-
hoistTransform (either Fail.fail pure) $
383-
annotateDecl dflags decl
384-
modifyAnnsT $ mappend anns
385-
pure decl'
388+
decs <- forM decs0 $ \decl ->
389+
hoistTransform (either Fail.fail pure) $
390+
annotateDecl dflags decl
386391
pure $ DL.fromList decs <> DL.fromList rest
387392
Nothing -> (DL.singleton e <>) <$> go rest
388393
| otherwise = (DL.singleton e <>) <$> go rest
@@ -461,7 +466,7 @@ annotate dflags needs_space ast = do
461466
pure (anns', expr')
462467

463468
-- | Given an 'LHsDecl', compute its exactprint annotations.
464-
annotateDecl :: DynFlags -> LHsDecl GhcPs -> TransformT (Either String) (Anns, LHsDecl GhcPs)
469+
annotateDecl :: DynFlags -> LHsDecl GhcPs -> TransformT (Either String) (LHsDecl GhcPs)
465470
-- The 'parseDecl' function fails to parse 'FunBind' 'ValD's which contain
466471
-- multiple matches. To work around this, we split the single
467472
-- 'FunBind'-of-multiple-'Match'es into multiple 'FunBind's-of-one-'Match',
@@ -474,24 +479,23 @@ annotateDecl dflags
474479
let set_matches matches =
475480
ValD ext fb { fun_matches = mg { mg_alts = L alt_src matches }}
476481

477-
(anns', alts') <- fmap unzip $ for (zip [0..] alts) $ \(ix :: Int, alt) -> do
482+
(anns', alts') <- fmap unzip $ for alts $ \alt -> do
478483
uniq <- show <$> uniqueSrcSpanT
479484
let rendered = render dflags $ set_matches [alt]
480485
lift (mapLeft show $ parseDecl dflags uniq rendered) >>= \case
481486
(ann, L _ (ValD _ FunBind { fun_matches = MG { mg_alts = L _ [alt']}}))
482-
-> pure (bool id (setPrecedingLines alt' 1 0) (ix /= 0) ann, alt')
487+
-> pure (setPrecedingLines alt' 1 0 ann, alt')
483488
_ -> lift $ Left "annotateDecl: didn't parse a single FunBind match"
484489

485-
let expr' = L src $ set_matches alts'
486-
anns'' = setPrecedingLines expr' 1 0 $ fold anns'
487-
488-
pure (anns'', expr')
490+
modifyAnnsT $ mappend $ fold anns'
491+
pure $ L src $ set_matches alts'
489492
annotateDecl dflags ast = do
490493
uniq <- show <$> uniqueSrcSpanT
491494
let rendered = render dflags ast
492495
(anns, expr') <- lift $ mapLeft show $ parseDecl dflags uniq rendered
493496
let anns' = setPrecedingLines expr' 1 0 anns
494-
pure (anns', expr')
497+
modifyAnnsT $ mappend anns'
498+
pure expr'
495499

496500
------------------------------------------------------------------------------
497501

@@ -504,3 +508,77 @@ render dflags = showSDoc dflags . ppr
504508
-- | Put parentheses around an expression if required.
505509
parenthesize :: LHsExpr GhcPs -> LHsExpr GhcPs
506510
parenthesize = parenthesizeHsExpr appPrec
511+
512+
513+
------------------------------------------------------------------------------
514+
-- Custom SYB machinery
515+
------------------------------------------------------------------------------
516+
517+
-- | Generic monadic transformations that return side-channel data.
518+
type GenericMQ r m = forall a. Data a => a -> m (r, a)
519+
520+
------------------------------------------------------------------------------
521+
-- | Apply the given 'GenericM' at all every node whose children fail the
522+
-- 'GenericQ', but which passes the query itself.
523+
--
524+
-- The query must be a monotonic function when it returns 'Just'. That is, if
525+
-- @s@ is a subtree of @t@, @q t@ should return @Just True@ if @q s@ does. It
526+
-- is the True-to-false edge of the query that triggers the transformation.
527+
--
528+
-- Why is the query a @Maybe Bool@? The GHC AST intersperses 'Located' nodes
529+
-- with data nodes, so for any given node we can only definitely return an
530+
-- answer if it's a 'Located'. See 'genericIsSubspan' for how this parameter is
531+
-- used.
532+
smallestM :: forall m. Monad m => GenericQ (Maybe Bool) -> GenericM m -> GenericM m
533+
smallestM q f = fmap snd . go
534+
where
535+
go :: GenericMQ Any m
536+
go x = do
537+
case q x of
538+
Nothing -> gmapMQ go x
539+
Just True -> do
540+
it@(r, x') <- gmapMQ go x
541+
case r of
542+
Any True -> pure it
543+
Any False -> fmap (Any True,) $ f x'
544+
Just False -> pure (mempty, x)
545+
546+
------------------------------------------------------------------------------
547+
-- | Apply the given 'GenericM' at every node that passes the 'GenericQ', but
548+
-- don't descend into children if the query matches. Because this traversal is
549+
-- root-first, this policy will find the largest subtrees for which the query
550+
-- holds true.
551+
--
552+
-- Why is the query a @Maybe Bool@? The GHC AST intersperses 'Located' nodes
553+
-- with data nodes, so for any given node we can only definitely return an
554+
-- answer if it's a 'Located'. See 'genericIsSubspan' for how this parameter is
555+
-- used.
556+
largestM :: forall m. Monad m => GenericQ (Maybe Bool) -> GenericM m -> GenericM m
557+
largestM q f = go
558+
where
559+
go :: GenericM m
560+
go x = do
561+
case q x of
562+
Just True -> f x
563+
Just False -> pure x
564+
Nothing -> gmapM go x
565+
566+
newtype MonadicQuery r m a = MonadicQuery
567+
{ runMonadicQuery :: m (r, a)
568+
}
569+
deriving stock (Functor)
570+
deriving Applicative via Compose m ((,) r)
571+
572+
573+
------------------------------------------------------------------------------
574+
-- | Like 'gmapM', but also returns side-channel data.
575+
gmapMQ ::
576+
forall f r a. (Monoid r, Data a, Applicative f) =>
577+
(forall d. Data d => d -> f (r, d)) ->
578+
a ->
579+
f (r, a)
580+
gmapMQ f = runMonadicQuery . gfoldl k pure
581+
where
582+
k :: Data d => MonadicQuery r f (d -> b) -> d -> MonadicQuery r f b
583+
k c x = c <*> MonadicQuery (f x)
584+

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

+9-4
Original file line numberDiff line numberDiff line change
@@ -176,17 +176,22 @@ allOccNames = everything (<>) $ mkQ mempty $ \case
176176
a -> S.singleton a
177177

178178

179+
------------------------------------------------------------------------------
180+
-- | Unpack the relevant parts of a 'Match'
181+
pattern AMatch :: HsMatchContext (NameOrRdrName (IdP GhcPs)) -> [Pat GhcPs] -> HsExpr GhcPs -> Match GhcPs (LHsExpr GhcPs)
182+
pattern AMatch ctx pats body <-
183+
Match { m_ctxt = ctx
184+
, m_pats = fmap fromPatCompatPs -> pats
185+
, m_grhss = UnguardedRHSs body
186+
}
179187

180188

181189
------------------------------------------------------------------------------
182190
-- | A pattern over the otherwise (extremely) messy AST for lambdas.
183191
pattern Lambda :: [Pat GhcPs] -> HsExpr GhcPs -> HsExpr GhcPs
184192
pattern Lambda pats body <-
185193
HsLam _
186-
(MG {mg_alts = L _ [L _
187-
(Match { m_pats = fmap fromPatCompatPs -> pats
188-
, m_grhss = UnguardedRHSs body
189-
})]})
194+
(MG {mg_alts = L _ [L _ (AMatch _ pats body) ]})
190195
where
191196
-- If there are no patterns to bind, just stick in the body
192197
Lambda [] body = body

0 commit comments

Comments
 (0)