apply-refact-0.9.2.0: src/Refact/Utils.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ViewPatterns #-}
module Refact.Utils ( -- * Synonyms
Module
, Stmt
, Expr
, Decl
, Name
, Pat
, Type
, Import
, FunBind
, AnnKeyMap
, pattern RealSrcLoc'
, pattern RealSrcSpan'
-- * Monad
, M
-- * Utility
, mergeAnns
, modifyAnnKey
, replaceAnnKey
, getAnnSpan
, toGhcSrcSpan
, toGhcSrcSpan'
, annSpanToSrcSpan
, srcSpanToAnnSpan
, setAnnSpanFile
, setSrcSpanFile
, setRealSrcSpanFile
, findParent
) where
import Language.Haskell.GHC.ExactPrint
import Language.Haskell.GHC.ExactPrint.Types
import Data.Bifunctor (bimap)
import Data.Data
import Data.Map.Strict (Map)
#if __GLASGOW_HASKELL__ >= 900
import GHC.Data.FastString (FastString)
import qualified GHC.Data.FastString as GHC
import qualified GHC.Parser.Annotation as GHC
import qualified GHC.Types.Name.Reader as GHC
import GHC.Types.SrcLoc
import qualified GHC.Types.SrcLoc as GHC
#else
import FastString (FastString)
import qualified FastString as GHC
import SrcLoc
import qualified SrcLoc as GHC
import qualified RdrName as GHC
import qualified ApiAnnotation as GHC
#endif
import qualified GHC hiding (parseModule)
#if __GLASGOW_HASKELL__ >= 810
import GHC.Hs.Expr as GHC hiding (Stmt)
import GHC.Hs.ImpExp
#else
import HsExpr as GHC hiding (Stmt)
import HsImpExp
#endif
import Control.Monad.Trans.State
import qualified Data.Map as Map
import Data.Maybe
import qualified Refact.Types as R
import Data.Generics.Schemes
import Unsafe.Coerce
-- Types
--
type M a = StateT (Anns, AnnKeyMap) IO a
type AnnKeyMap = Map AnnKey [AnnKey]
#if __GLASGOW_HASKELL__ >= 900
type Module = (GHC.Located GHC.HsModule)
#else
type Module = (GHC.Located (GHC.HsModule GHC.GhcPs))
#endif
type Expr = GHC.Located (GHC.HsExpr GHC.GhcPs)
type Type = GHC.Located (GHC.HsType GHC.GhcPs)
type Decl = GHC.Located (GHC.HsDecl GHC.GhcPs)
type Pat = GHC.Located (GHC.Pat GHC.GhcPs)
type Name = GHC.Located GHC.RdrName
type Stmt = ExprLStmt GHC.GhcPs
type Import = LImportDecl GHC.GhcPs
#if __GLASGOW_HASKELL__ >= 900
type FunBind = HsMatchContext GHC.GhcPs
#else
type FunBind = HsMatchContext GHC.RdrName
#endif
pattern RealSrcLoc' :: RealSrcLoc -> SrcLoc
#if __GLASGOW_HASKELL__ >= 900
pattern RealSrcLoc' r <- RealSrcLoc r _ where
RealSrcLoc' r = RealSrcLoc r Nothing
#else
pattern RealSrcLoc' r <- RealSrcLoc r where
RealSrcLoc' r = RealSrcLoc r
#endif
{-# COMPLETE RealSrcLoc', UnhelpfulLoc #-}
pattern RealSrcSpan' :: RealSrcSpan -> SrcSpan
#if __GLASGOW_HASKELL__ >= 900
pattern RealSrcSpan' r <- RealSrcSpan r _ where
RealSrcSpan' r = RealSrcSpan r Nothing
#else
pattern RealSrcSpan' r <- RealSrcSpan r where
RealSrcSpan' r = RealSrcSpan r
#endif
{-# COMPLETE RealSrcSpan', UnhelpfulSpan #-}
-- | Replaces an old expression with a new expression
--
-- Note that usually, new, inp and parent are all the same.
replace :: AnnKey -- The thing we are replacing
-> AnnKey -- The thing which has the annotations we need for the new thing
-> AnnKey -- The thing which is going to be inserted
-> AnnKey -- The "parent", the largest thing which has he same SrcSpan
-- Usually the same as inp and new
-> Anns -> Maybe Anns
replace old new inp parent anns = do
oldan <- Map.lookup old anns
newan <- Map.lookup new anns
oldDelta <- annEntryDelta <$> Map.lookup parent anns
return $ Map.insert inp (combine oldDelta new oldan newan) anns
combine :: DeltaPos -> AnnKey -> Annotation -> Annotation -> Annotation
combine oldDelta newkey oldann newann =
Ann { annEntryDelta = newEntryDelta
, annPriorComments = annPriorComments oldann ++ annPriorComments newann
, annFollowingComments = annFollowingComments oldann ++ annFollowingComments newann
, annsDP = removeComma (annsDP newann) ++ extraComma (annsDP oldann)
, annSortKey = annSortKey newann
, annCapturedSpan = annCapturedSpan newann}
where
-- Get rid of structural information when replacing, we assume that the
-- structural information is already there in the new expression.
removeComma = filter (\(kw, _) -> case kw of
G GHC.AnnComma
| AnnKey _ (CN "ArithSeq") <- newkey -> True
| otherwise -> False
AnnSemiSep -> False
_ -> True)
-- Make sure to keep structural information in the template.
extraComma [] = []
extraComma (last -> x) = case fst x of
G GHC.AnnComma -> [x]
AnnSemiSep -> [x]
G GHC.AnnSemi -> [x]
_ -> []
-- Keep the same delta if moving onto a new row
newEntryDelta | deltaRow oldDelta > 0 = oldDelta
| otherwise = annEntryDelta oldann
-- | A parent in this case is an element which has the same SrcSpan
findParent :: Data a => AnnSpan -> Anns -> a -> Maybe AnnKey
findParent ss as = something (findParentWorker ss as)
-- Note that a parent must also have an annotation.
findParentWorker :: forall a . (Data a)
=> AnnSpan -> Anns -> a -> Maybe AnnKey
findParentWorker oldSS as a
| con == typeRepTyCon (typeRep (Proxy :: Proxy (GHC.Located GHC.RdrName))) && x == typeRep (Proxy :: Proxy AnnSpan)
= if ss == oldSS
&& isJust (Map.lookup (AnnKey ss cn) as)
then Just $ AnnKey ss cn
else Nothing
| otherwise = Nothing
where
(con, ~[x, _]) = splitTyConApp (typeOf a)
ss :: AnnSpan
ss = gmapQi 0 unsafeCoerce a
cn = gmapQi 1 (CN . show . toConstr) a
getAnnSpan :: forall a. Located a -> AnnSpan
getAnnSpan = srcSpanToAnnSpan . getLoc
srcSpanToAnnSpan :: SrcSpan -> AnnSpan
srcSpanToAnnSpan =
#if __GLASGOW_HASKELL__ >= 900
\case GHC.RealSrcSpan l _ -> l; _ -> badRealSrcSpan
#else
id
#endif
annSpanToSrcSpan :: AnnSpan -> SrcSpan
annSpanToSrcSpan =
#if __GLASGOW_HASKELL__ >= 900
flip GHC.RealSrcSpan Nothing
#else
id
#endif
-- | Perform the necessary adjustments to annotations when replacing
-- one Located thing with another Located thing.
--
-- For example, this function will ensure the correct relative position and
-- make sure that any trailing semi colons or commas are transferred.
modifyAnnKey
:: (Data old, Data new, Data mod)
=> mod -> Located old -> Located new -> M (Located new)
modifyAnnKey m e1 e2 = do
as <- gets fst
let parentKey = fromMaybe (mkAnnKey e2) (findParent (getAnnSpan e2) as m)
e2 <$ modify
( bimap
( recoverBackquotes e1 e2
. replaceAnnKey (mkAnnKey e1) (mkAnnKey e2) (mkAnnKey e2) parentKey
)
(Map.insertWith (++) (mkAnnKey e1) [mkAnnKey e2])
)
-- | When the template contains a backquoted substitution variable, but the substitute
-- is not backquoted, we must add the corresponding 'GHC.AnnBackQuote's.
--
-- See tests/examples/Backquotes.hs for an example.
recoverBackquotes :: Located old -> Located new -> Anns -> Anns
recoverBackquotes (getAnnSpan -> old) (getAnnSpan -> new) anns
| Just annOld <- Map.lookup (AnnKey old (CN "Unqual")) anns
, ( (G GHC.AnnBackquote, DP (i, j))
: rest@( (G GHC.AnnVal, _)
: (G GHC.AnnBackquote, _)
: _)
) <- annsDP annOld
= let f annNew = case annsDP annNew of
[(G GHC.AnnVal, DP (i', j'))] ->
annNew {annsDP = (G GHC.AnnBackquote, DP (i + i', j + j')) : rest}
_ -> annNew
in Map.adjust f (AnnKey new (CN "Unqual")) anns
| otherwise = anns
-- | Lower level version of @modifyAnnKey@
replaceAnnKey :: AnnKey -> AnnKey -> AnnKey -> AnnKey -> Anns -> Anns
replaceAnnKey old new inp deltainfo a =
fromMaybe a (replace old new inp deltainfo a)
-- | Convert a @Refact.Types.SrcSpan@ to a @SrcLoc.SrcSpan@
toGhcSrcSpan :: FilePath -> R.SrcSpan -> SrcSpan
toGhcSrcSpan = toGhcSrcSpan' . GHC.mkFastString
-- | Convert a @Refact.Types.SrcSpan@ to a @SrcLoc.SrcSpan@
toGhcSrcSpan' :: FastString -> R.SrcSpan -> SrcSpan
toGhcSrcSpan' file R.SrcSpan{..} = mkSrcSpan (f startLine startCol) (f endLine endCol)
where
f = mkSrcLoc file
setSrcSpanFile :: FastString -> SrcSpan -> SrcSpan
setSrcSpanFile file s
| RealSrcLoc' start <- srcSpanStart s
, RealSrcLoc' end <- srcSpanEnd s
= let start' = mkSrcLoc file (srcLocLine start) (srcLocCol start)
end' = mkSrcLoc file (srcLocLine end) (srcLocCol end)
in mkSrcSpan start' end'
setSrcSpanFile _ s = s
setRealSrcSpanFile :: FastString -> RealSrcSpan -> RealSrcSpan
setRealSrcSpanFile file s = mkRealSrcSpan start' end'
where
start = realSrcSpanStart s
end = realSrcSpanEnd s
start' = mkRealSrcLoc file (srcLocLine start) (srcLocCol start)
end' = mkRealSrcLoc file (srcLocLine end) (srcLocCol end)
setAnnSpanFile :: FastString -> AnnSpan -> AnnSpan
setAnnSpanFile =
#if __GLASGOW_HASKELL__ >= 900
setRealSrcSpanFile
#else
setSrcSpanFile
#endif