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
87module 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 )
3134where
3235
@@ -63,7 +66,8 @@ import Parser (parseIdentifier)
6366import Data.Traversable (for )
6467import Data.Foldable (Foldable (fold ))
6568import 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
6872import Control.Arrow
6973#endif
@@ -195,7 +199,7 @@ needsParensSpace ExplicitSum{} = (All False, All False)
195199needsParensSpace HsCase {} = (All False , All False )
196200needsParensSpace HsIf {} = (All False , All False )
197201needsParensSpace HsMultiIf {} = (All False , All False )
198- needsParensSpace HsLet {} = (All False , All False )
202+ needsParensSpace HsLet {} = (All False , All True )
199203needsParensSpace HsDo {} = (All False , All False )
200204needsParensSpace ExplicitList {} = (All False , All False )
201205needsParensSpace 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
314344graftDecls ::
315345 forall a .
@@ -319,32 +349,13 @@ graftDecls ::
319349 Graft (Either String ) a
320350graftDecls 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-
348359graftSmallestDeclsWithM ::
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'
489492annotateDecl 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.
505509parenthesize :: LHsExpr GhcPs -> LHsExpr GhcPs
506510parenthesize = 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+
0 commit comments