retrie-0.1.0.0: Retrie/Context.hs
-- Copyright (c) Facebook, Inc. and its affiliates.
--
-- This source code is licensed under the MIT license found in the
-- LICENSE file in the root directory of this source tree.
--
{-# LANGUAGE CPP #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Retrie.Context
( ContextUpdater
, updateContext
, emptyContext
) where
import Control.Monad.IO.Class
import Data.Char (isDigit)
import Data.Either (partitionEithers)
import Data.Generics hiding (Fixity)
import Data.List
import Data.Maybe
import Retrie.AlphaEnv
import Retrie.ExactPrint
import Retrie.Fixity
import Retrie.FreeVars
import Retrie.GHC
import Retrie.Substitution
import Retrie.SYB
import Retrie.Types
import Retrie.Universe
-------------------------------------------------------------------------------
-- | Type of context update functions for 'apply'.
-- When defining your own 'ContextUpdater', you probably want to extend
-- 'updateContext' using SYB combinators such as 'mkQ' and 'extQ'.
type ContextUpdater = forall m. MonadIO m => GenericCU (TransformT m) Context
-- | Default context update function.
updateContext :: forall m. MonadIO m => GenericCU (TransformT m) Context
updateContext c i =
const (return c)
`extQ` (return . updExp)
`extQ` (return . updType)
#if __GLASGOW_HASKELL__ < 806
`extQ` (return . updTypeList)
#endif
`extQ` (return . updMatch)
`extQ` (return . updGRHSs)
`extQ` (return . updGRHS)
`extQ` (return . updStmt)
`extQ` updStmtList
`extQ` (return . updHsBind)
`extQ` (return . updTyClDecl)
where
neverParen = c { ctxtParentPrec = NeverParen }
updExp :: HsExpr GhcPs -> Context
updExp HsApp{} =
c { ctxtParentPrec = HasPrec $ Fixity (SourceText "HsApp") (10 + i - firstChild) InfixL }
-- Reason for 10 + i: (i is index of child, 0 = left, 1 = right)
-- In left child, prec is 10, so HsApp child will NOT get paren'd
-- In right child, prec is 11, so every child gets paren'd (unless atomic)
#if __GLASGOW_HASKELL__ < 806
updExp (OpApp _ op _ _) = c { ctxtParentPrec = HasPrec $ lookupOp op (ctxtFixityEnv c) }
updExp (HsLet lbs _) = addInScope neverParen $ collectLocalBinders $ unLoc lbs
#else
updExp (OpApp _ _ op _) = c { ctxtParentPrec = HasPrec $ lookupOp op (ctxtFixityEnv c) }
updExp (HsLet _ lbs _) = addInScope neverParen $ collectLocalBinders $ unLoc lbs
#endif
updExp _ = neverParen
updType :: HsType GhcPs -> Context
#if __GLASGOW_HASKELL__ < 806
updType (HsAppsTy _) = c { ctxtParentPrec = IsHsAppsTy }
#else
updType HsAppTy{}
| i > firstChild = c { ctxtParentPrec = IsHsAppsTy }
#endif
updType _ = neverParen
#if __GLASGOW_HASKELL__ < 806
updTypeList :: [LHsAppType GhcPs] -> Context
updTypeList _ =
case ctxtParentPrec c of
IsHsAppsTy
| i > 0 -> c { ctxtParentPrec = HasPrec $ Fixity (SourceText "HsAppsTy") 11 InfixL }
| otherwise -> neverParen
_ -> c -- leave prec as is
#endif
updMatch :: Match GhcPs (LHsExpr GhcPs) -> Context
updMatch = addInScope neverParen . collectPatsBinders . m_pats
updGRHSs :: GRHSs GhcPs (LHsExpr GhcPs) -> Context
updGRHSs = addInScope neverParen . collectLocalBinders . unLoc . grhssLocalBinds
updGRHS :: GRHS GhcPs (LHsExpr GhcPs) -> Context
#if __GLASGOW_HASKELL__ < 806
updGRHS (GRHS gs _)
#else
updGRHS XGRHS{} = neverParen
updGRHS (GRHS _ gs _)
#endif
-- binders are in scope over the body (right child) only
| i > firstChild = addInScope neverParen bs
| otherwise = fst $ updateSubstitution neverParen bs
where
bs = collectLStmtsBinders gs
updStmt :: Stmt GhcPs (LHsExpr GhcPs) -> Context
#if __GLASGOW_HASKELL__ < 806
updStmt (BindStmt p _body _ _ _)
#else
updStmt (BindStmt _ p _body _ _)
#endif
-- i > firstChild == 'for the body'
| i > firstChild = addBinders neverParen (collectPatBinders p)
updStmt _ = neverParen
updStmtList :: [LStmt GhcPs (LHsExpr GhcPs)] -> TransformT m Context
updStmtList [] = return neverParen
updStmtList (ls:_)
-- binders are in scope over tail of list (right child)
| i > 0 = insertDependentRewrites neverParen bs ls
-- lets are recursive in do-blocks
#if __GLASGOW_HASKELL__ < 806
| L _ (LetStmt (L _ bnds)) <- ls =
#else
| L _ (LetStmt _ (L _ bnds)) <- ls =
#endif
return $ addInScope neverParen $ collectLocalBinders bnds
| otherwise = return $ fst $ updateSubstitution neverParen bs
where
bs = collectLStmtBinders ls
updHsBind :: HsBind GhcPs -> Context
updHsBind FunBind{..} =
let rdr = unLoc fun_id
in addBinders (addInScope neverParen [rdr]) [rdr]
updHsBind _ = neverParen
updTyClDecl :: TyClDecl GhcPs -> Context
updTyClDecl SynDecl{..} = addInScope neverParen [unLoc tcdLName]
updTyClDecl DataDecl{..} = addInScope neverParen [unLoc tcdLName]
updTyClDecl ClassDecl{..} = addInScope neverParen [unLoc tcdLName]
updTyClDecl _ = neverParen
-- | Create an empty 'Context' with given 'FixityEnv', rewriter, and dependent
-- rewrite generator.
emptyContext :: FixityEnv -> Rewriter -> Rewriter -> Context
emptyContext ctxtFixityEnv ctxtRewriter ctxtDependents = Context{..}
where
ctxtBinders = []
ctxtInScope = emptyAlphaEnv
ctxtParentPrec = NeverParen
ctxtSubst = Nothing
-- Deal with Trees-That-Grow adding extension points
-- as the first child everywhere.
firstChild :: Int
#if __GLASGOW_HASKELL__ < 806
firstChild = 0
#else
firstChild = 1
#endif
-- | Add dependent rewrites to 'ctxtRewriter' if necessary.
insertDependentRewrites
:: (Matchable k, MonadIO m) => Context -> [RdrName] -> k -> TransformT m Context
insertDependentRewrites c bs x = do
r <- runRewriter id c (ctxtDependents c) x
let
c' = addInScope c bs
case r of
NoMatch -> return c'
MatchResult _ Template{..} -> do
let
rrs = fromMaybe [] tDependents
ds = rewritesWithDependents rrs
f = foldMap (mkLocalRewriter $ ctxtInScope c')
return c'
{ ctxtRewriter = f rrs <> ctxtRewriter c'
, ctxtDependents = f ds <> ctxtDependents c'
}
-- | Add set of binders to 'ctxtInScope'.
addInScope :: Context -> [RdrName] -> Context
addInScope c bs =
c' { ctxtInScope = foldr extendAlphaEnv (ctxtInScope c') bs' }
where
(c', bs') = updateSubstitution c bs
-- | Add set of binders to 'ctxtBinders'.
addBinders :: Context -> [RdrName] -> Context
addBinders c bs = c { ctxtBinders = bs ++ ctxtBinders c }
-- Capture-avoiding substitution
--------------------------------------------------------------------------------
-- | Update the Context's substitution appropriately for a set of binders.
-- Returns a new Context and a potentially alpha-renamed set of binders.
updateSubstitution :: Context -> [RdrName] -> (Context, [RdrName])
updateSubstitution c rdrs =
case ctxtSubst c of
Nothing -> (c, rdrs)
Just sub ->
let
-- This prevents substituting for 'x' under a binding for 'x'.
sub' = deleteSubst sub $ map rdrFS rdrs
-- Compute free vars of substitution that could possibly be captured.
fvs = substFVs sub'
-- Partition binders into noncapturing and capturing.
(noncapturing, capturing) =
partitionEithers $ map (updateBinder fvs) rdrs
-- Extend substitution with alpha-renamings.
alphaSub = foldl' (uncurry . extendSubst) sub'
[ (rdrFS rdr, HoleRdr rdr') | (rdr, rdr') <- capturing ]
-- There are no telescopes in source Haskell, so order doesn't matter.
-- Capturing should be rare, so put it first to avoid quadratic append.
rdrs' = map snd capturing ++ noncapturing
in (c { ctxtSubst = Just alphaSub }, rdrs')
-- | Check if RdrName is in FreeVars.
--
-- If so, return a pair of it and its new name (Right).
-- If not, return it unchanged (Left).
updateBinder :: FreeVars -> RdrName -> Either RdrName (RdrName, RdrName)
updateBinder fvs rdr
| elemFVs rdr fvs = Right (rdr, renameBinder rdr fvs)
| otherwise = Left rdr
-- | Given a RdrName, rename it to something not in given FreeVars.
--
-- x => x1
-- x1 => x2
-- x9 => x10
--
-- etc.
--
-- Only works on unqualified RdrNames. This is fine, as we only use this to
-- rename local binders.
renameBinder :: RdrName -> FreeVars -> RdrName
renameBinder rdr fvs = head
[ rdr'
| i <- [n..]
, let rdr' = mkVarUnqual $ mkFastString $ baseName ++ show i
, not $ rdr' `elemFVs` fvs
]
where
(ds, rest) = span isDigit $ reverse $ occNameString $ occName rdr
baseName = reverse rest
n :: Int
n | null ds = 1
| otherwise = read (reverse ds) + 1