retrie-1.2.1: A powerful, easy-to-use codemodding tool for Haskell.
Safe HaskellSafe-Inferred
LanguageHaskell2010

Retrie

Description

This module provides the external interface for using Retrie as a library. All other modules should be considered internal, with APIs that are subject to change without notice.

Synopsis

Scripting

runScript :: LibDir -> (Options -> IO (Retrie ())) -> IO () Source #

Define a custom refactoring script. A script is an IO action that defines a Retrie computation. The IO action is run once, and the resulting Retrie computation is run once for each target file. Typically, rewrite parsing/construction is done in the IO action, so it is performed only once. Example:

module Main where

main :: IO ()
main = runScript $ \opts -> do
  rr <- parseRewrites opts ["forall f g xs. map f (map g xs) = map (f . g) xs"]
  return $ apply rr

To run the script, compile the program and execute it.

runScriptWithModifiedOptions :: LibDir -> (Options -> IO (Options, Retrie ())) -> IO () Source #

Define a custom refactoring script and run it with modified options. This is the same as runScript, but the returned Options will be used during rewriting.

Parsing Rewrites

Imports

parseImports :: LibDir -> [String] -> IO AnnotatedImports Source #

Parse import statements. Each string must be a full import statement, including the keyword 'import'. Supports full import syntax.

Queries

parseQueries :: LibDir -> Options -> [(Quantifiers, QuerySpec, v)] -> IO [Query Universe v] Source #

Create Querys from string specifications of expressionstypesstatements.

data QuerySpec Source #

Specifies which parser to use in parseQueries.

Rewrites

parseRewrites :: LibDir -> Options -> [RewriteSpec] -> IO [Rewrite Universe] Source #

Create Rewrites from string specifications of rewrites.

data RewriteSpec Source #

Possible ways to specify rewrites to parseRewrites.

Constructors

Adhoc String

Equation in RULES-format. (e.g. "forall x. succ (pred x) = x") Will be applied left-to-right.

AdhocPattern String

Equation in pattern-synonym format, _without_ the keyword pattern.

AdhocType String

Equation in type-synonym format, _without_ the keyword 'type'.

Fold QualifiedName

Fold a function definition. The inverse of unfolding/inlining. Replaces instances of the function body with calls to the function.

RuleBackward QualifiedName

Apply a GHC RULE right-to-left.

RuleForward QualifiedName

Apply a GHC RULE left-to-right.

TypeBackward QualifiedName

Apply a type synonym right-to-left.

TypeForward QualifiedName

Apply a type synonym left-to-right.

Unfold QualifiedName

Unfold, or inline, a function definition.

PatternForward QualifiedName

Unfold a pattern synonym

PatternBackward QualifiedName

Fold a pattern synonym, replacing instances of the rhs with the synonym

type QualifiedName = String Source #

A qualified name. (e.g. "Module.Name.functionName")

Retrie Computations

data Retrie a Source #

The Retrie monad is essentially IO, plus state containing the module that is being transformed. It is run once per target file.

It is special because it also allows Retrie to extract a set of GroundTerms from the Retrie computation without evaluating it.

Retrie uses the ground terms to select which files to target. This is the key optimization that allows Retrie to handle large codebases.

Note: Due to technical limitations, we cannot extract ground terms if you use liftIO before calling one of apply, focus, or query at least once. This will cause Retrie to attempt to parse every module in the target directory. In this case, please add a call to focus before the call to liftIO.

Instances

Instances details
MonadIO Retrie Source # 
Instance details

Defined in Retrie.Monad

Methods

liftIO :: IO a -> Retrie a #

Applicative Retrie Source # 
Instance details

Defined in Retrie.Monad

Methods

pure :: a -> Retrie a #

(<*>) :: Retrie (a -> b) -> Retrie a -> Retrie b #

liftA2 :: (a -> b -> c) -> Retrie a -> Retrie b -> Retrie c #

(*>) :: Retrie a -> Retrie b -> Retrie b #

(<*) :: Retrie a -> Retrie b -> Retrie a #

Functor Retrie Source # 
Instance details

Defined in Retrie.Monad

Methods

fmap :: (a -> b) -> Retrie a -> Retrie b #

(<$) :: a -> Retrie b -> Retrie a #

Monad Retrie Source # 
Instance details

Defined in Retrie.Monad

Methods

(>>=) :: Retrie a -> (a -> Retrie b) -> Retrie b #

(>>) :: Retrie a -> Retrie b -> Retrie b #

return :: a -> Retrie a #

Applying Rewrites

apply :: [Rewrite Universe] -> Retrie () Source #

Apply a set of rewrites. By default, rewrites are applied top-down, pruning the traversal at successfully changed AST nodes. See topDownPrune.

applyWithStrategy :: Strategy (TransformT (WriterT Change IO)) -> [Rewrite Universe] -> Retrie () Source #

Apply a set of rewrites with a custom traversal strategy.

applyWithUpdate :: ContextUpdater -> [Rewrite Universe] -> Retrie () Source #

Apply a set of rewrites with a custom context-update function.

applyWithUpdateAndStrategy :: ContextUpdater -> Strategy (TransformT (WriterT Change IO)) -> [Rewrite Universe] -> Retrie () Source #

Apply a set of rewrites with custom context-update and traversal strategy.

addImports :: AnnotatedImports -> Retrie () Source #

Add imports to the module.

Control Flow

ifChanged :: Retrie () -> Retrie () -> Retrie () Source #

If the first Retrie computation makes a change to the module, run the second Retrie computation.

iterateR :: Int -> Retrie () -> Retrie () Source #

Iterate given Retrie computation until it no longer makes changes, or N times, whichever happens first.

Focusing

focus :: Data k => [Query k v] -> Retrie () Source #

Use the given queries/rewrites to select files for rewriting. Does not actually perform matching. This is useful if the queries/rewrites which best determine which files to target are not the first ones you run, and when you need to liftIO before running any queries/rewrites.

Querying the AST

query :: [Query Universe v] -> Retrie [(Context, Substitution, v)] Source #

Query the AST. Each match returns the context of the match, a substitution mapping quantifiers to matched subtrees, and the query's value.

queryWithUpdate :: ContextUpdater -> [Query Universe v] -> Retrie [(Context, Substitution, v)] Source #

Query the AST with a custom context update function.

Traversal Strategies

bottomUp :: Strategy m Source #

Perform a bottom-up traversal.

topDown :: Strategy m Source #

Perform a top-down traversal.

topDownPrune :: Monad m => Strategy (TransformT (WriterT Change m)) Source #

Top-down traversal that does not descend into changed AST nodes. Default strategy used by apply.

Advanced Scripting

For advanced rewriting, Retrie provides the notion of a MatchResultTransformer. This is a callback function that is provided the result of matching the left-hand side of an equation. Whatever the callback returns is used to perform the actual rewrite.

The callback has access to the Context of the match, the generated Substitution, and IO. Helper libraries such as Annotated and subst make it possible to define complex transformers without too much tedium.

Transformers can check side conditions by examining the MatchResult and returning NoMatch when conditions do not hold.

type MatchResultTransformer = Context -> MatchResult Universe -> IO (MatchResult Universe) Source #

A MatchResultTransformer allows the user to specify custom logic to modify the result of matching the left-hand side of a rewrite (the MatchResult). The MatchResult generated by this function is used to effect the resulting AST rewrite.

For example, this transformer looks at the matched expression to build the resulting expression:

fancyMigration :: MatchResultTransformer
fancyMigration ctxt matchResult
  | MatchResult sub t <- matchResult
  , HoleExpr e <- lookupSubst sub "x" = do
    e' <- ... some fancy IO computation using 'e' ...
    return $ MatchResult (extendSubst sub "x" (HoleExpr e')) t
  | otherwise = NoMatch

main :: IO ()
main = runScript $ \opts -> do
  rrs <- parseRewrites opts [Adhoc "forall x. ... = ..."]
  return $ apply
    [ setRewriteTransformer fancyMigration rr | rr <- rrs ]

Since the MatchResultTransformer can also modify the Template, you can construct an entirely novel right-hand side, add additional imports, or inject new dependent rewrites.

defaultTransformer :: MatchResultTransformer Source #

The default transformer. Returns the MatchResult unchanged.

data MatchResult ast Source #

The result of matching the left-hand side of a Rewrite.

Instances

Instances details
Functor MatchResult Source # 
Instance details

Defined in Retrie.Types

Methods

fmap :: (a -> b) -> MatchResult a -> MatchResult b #

(<$) :: a -> MatchResult b -> MatchResult a #

Annotated ASTs

data Annotated ast Source #

Annotated packages an AST fragment with the annotations necessary to exactPrint or transform that AST.

Instances

Instances details
Foldable Annotated Source # 
Instance details

Defined in Retrie.ExactPrint.Annotated

Methods

fold :: Monoid m => Annotated m -> m #

foldMap :: Monoid m => (a -> m) -> Annotated a -> m #

foldMap' :: Monoid m => (a -> m) -> Annotated a -> m #

foldr :: (a -> b -> b) -> b -> Annotated a -> b #

foldr' :: (a -> b -> b) -> b -> Annotated a -> b #

foldl :: (b -> a -> b) -> b -> Annotated a -> b #

foldl' :: (b -> a -> b) -> b -> Annotated a -> b #

foldr1 :: (a -> a -> a) -> Annotated a -> a #

foldl1 :: (a -> a -> a) -> Annotated a -> a #

toList :: Annotated a -> [a] #

null :: Annotated a -> Bool #

length :: Annotated a -> Int #

elem :: Eq a => a -> Annotated a -> Bool #

maximum :: Ord a => Annotated a -> a #

minimum :: Ord a => Annotated a -> a #

sum :: Num a => Annotated a -> a #

product :: Num a => Annotated a -> a #

Traversable Annotated Source # 
Instance details

Defined in Retrie.ExactPrint.Annotated

Methods

traverse :: Applicative f => (a -> f b) -> Annotated a -> f (Annotated b) #

sequenceA :: Applicative f => Annotated (f a) -> f (Annotated a) #

mapM :: Monad m => (a -> m b) -> Annotated a -> m (Annotated b) #

sequence :: Monad m => Annotated (m a) -> m (Annotated a) #

Functor Annotated Source # 
Instance details

Defined in Retrie.ExactPrint.Annotated

Methods

fmap :: (a -> b) -> Annotated a -> Annotated b #

(<$) :: a -> Annotated b -> Annotated a #

Data ast => Data (Annotated ast) Source # 
Instance details

Defined in Retrie.ExactPrint.Annotated

Methods

gfoldl :: (forall d b. Data d => c (d -> b) -> d -> c b) -> (forall g. g -> c g) -> Annotated ast -> c (Annotated ast) #

gunfold :: (forall b r. Data b => c (b -> r) -> c r) -> (forall r. r -> c r) -> Constr -> c (Annotated ast) #

toConstr :: Annotated ast -> Constr #

dataTypeOf :: Annotated ast -> DataType #

dataCast1 :: Typeable t => (forall d. Data d => c (t d)) -> Maybe (c (Annotated ast)) #

dataCast2 :: Typeable t => (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c (Annotated ast)) #

gmapT :: (forall b. Data b => b -> b) -> Annotated ast -> Annotated ast #

gmapQl :: (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Annotated ast -> r #

gmapQr :: forall r r'. (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Annotated ast -> r #

gmapQ :: (forall d. Data d => d -> u) -> Annotated ast -> [u] #

gmapQi :: Int -> (forall d. Data d => d -> u) -> Annotated ast -> u #

gmapM :: Monad m => (forall d. Data d => d -> m d) -> Annotated ast -> m (Annotated ast) #

gmapMp :: MonadPlus m => (forall d. Data d => d -> m d) -> Annotated ast -> m (Annotated ast) #

gmapMo :: MonadPlus m => (forall d. Data d => d -> m d) -> Annotated ast -> m (Annotated ast) #

(Data ast, Monoid ast) => Monoid (Annotated ast) Source # 
Instance details

Defined in Retrie.ExactPrint.Annotated

Methods

mempty :: Annotated ast #

mappend :: Annotated ast -> Annotated ast -> Annotated ast #

mconcat :: [Annotated ast] -> Annotated ast #

(Data ast, Monoid ast) => Semigroup (Annotated ast) Source # 
Instance details

Defined in Retrie.ExactPrint.Annotated

Methods

(<>) :: Annotated ast -> Annotated ast -> Annotated ast #

sconcat :: NonEmpty (Annotated ast) -> Annotated ast #

stimes :: Integral b => b -> Annotated ast -> Annotated ast #

Default ast => Default (Annotated ast) Source # 
Instance details

Defined in Retrie.ExactPrint.Annotated

Methods

def :: Annotated ast #

astA :: Annotated ast -> ast Source #

Examine the actual AST.

Type Synonyms

Parsing

Note: These parsers do not re-associate infix operators. To do so, use fix. For example:

do
  expr <- parseExpr "f <$> x <*> y"
  e <- transformA expr (fix (fixityEnv opts))

parseDecl :: LibDir -> String -> IO AnnotatedHsDecl Source #

Parse a top-level HsDecl.

Operations

transformA :: Monad m => Annotated ast1 -> (ast1 -> TransformT m ast2) -> m (Annotated ast2) Source #

Transform an Annotated thing.

graftA :: (Data ast, Monad m) => Annotated ast -> TransformT m ast Source #

Graft an Annotated thing into the current transformation. The resulting AST will have proper annotations within the TransformT computation. For example:

mkDeclList :: IO (Annotated [LHsDecl GhcPs])
mkDeclList = do
  ad1 <- parseDecl "myId :: a -> a"
  ad2 <- parseDecl "myId x = x"
  transformA ad1 $ \ d1 -> do
    d2 <- graftA ad2
    return [d1, d2]

pruneA :: (Data ast, Monad m) => ast -> TransformT m (Annotated ast) Source #

Encapsulate something in the current transformation into an Annotated thing. This is the inverse of graftT. For example:

splitHead :: Monad m => Annotated [a] -> m (Annotated a, Annotated [a])
splitHead l = fmap astA $ transformA l $ \(x:xs) -> do
  y <- pruneA x
  ys <- pruneA xs
  return (y, ys)

trimA :: Data ast => Annotated ast -> Annotated ast Source #

Trim the annotation data to only include annotations for ast. (Usually, the annotation data is a superset of what is necessary.) Also freshens all source locations, so filename information in annotation keys is discarded.

Note: not commonly needed, but useful if you want to inspect the annotation data directly and don't want to wade through a mountain of output.

printA :: (Data ast, ExactPrint ast) => Annotated ast -> String Source #

Exactprint an Annotated thing.

Util

Collection of miscellaneous helpers for manipulating the GHC AST.

Types

Context

data Context Source #

Context maintained by AST traversals.

Constructors

Context 

Fields

type ContextUpdater = forall m. MonadIO m => GenericCU (TransformT m) Context Source #

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.

updateContext :: forall m. MonadIO m => GenericCU (TransformT m) Context Source #

Default context update function.

Options

type Options = Options_ [Rewrite Universe] AnnotatedImports Source #

Command-line options.

data Options_ rewrites imports Source #

Constructors

Options 

Fields

data Verbosity Source #

Constructors

Silent 
Normal 
Loud 

Instances

Instances details
Show Verbosity Source # 
Instance details

Defined in Retrie.Util

Eq Verbosity Source # 
Instance details

Defined in Retrie.Util

Ord Verbosity Source # 
Instance details

Defined in Retrie.Util

Quantifiers

Queries

data Query ast v Source #

Query is the primitive way to specify a matchable pattern (quantifiers and expression). Whenever the pattern is matched, the associated result will be returned.

Constructors

Query 

Instances

Instances details
Bifunctor Query Source # 
Instance details

Defined in Retrie.Types

Methods

bimap :: (a -> b) -> (c -> d) -> Query a c -> Query b d #

first :: (a -> b) -> Query a c -> Query b c #

second :: (b -> c) -> Query a b -> Query a c #

Functor (Query ast) Source # 
Instance details

Defined in Retrie.Types

Methods

fmap :: (a -> b) -> Query ast a -> Query ast b #

(<$) :: a -> Query ast b -> Query ast a #

(Data (Annotated ast), Show ast, Show v) => Show (Query ast v) Source # 
Instance details

Defined in Retrie.Types

Methods

showsPrec :: Int -> Query ast v -> ShowS #

show :: Query ast v -> String #

showList :: [Query ast v] -> ShowS #

Rewrites

type Rewrite ast = Query ast (Template ast, MatchResultTransformer) Source #

A Rewrite is a Query specialized to Template results, which have all the information necessary to replace one expression with another.

data Template ast Source #

The right-hand side of a Rewrite.

Constructors

Template 

Fields

Instances

Instances details
Functor Template Source # 
Instance details

Defined in Retrie.Types

Methods

fmap :: (a -> b) -> Template a -> Template b #

(<$) :: a -> Template b -> Template a #

mkRewrite :: Quantifiers -> Annotated ast -> Annotated ast -> Rewrite ast Source #

Make a Rewrite from given quantifiers and left- and right-hand sides.

ppRewrite :: Rewrite Universe -> String Source #

Pretty-print a Rewrite for debugging.

addRewriteImports :: AnnotatedImports -> Rewrite ast -> Rewrite ast Source #

Add imports to a Rewrite. Whenever the Rewrite successfully rewrites an expression, the imports are inserted into the enclosing module.

Substitution

subst :: (MonadIO m, Data ast) => Substitution -> Context -> ast -> TransformT m ast Source #

Perform the given Substitution on an AST, avoiding variable capture by alpha-renaming binders as needed.

Universe

data Universe Source #

A sum type to collect all possible top-level rewritable types.

Instances

Instances details
Data Universe Source # 
Instance details

Defined in Retrie.Universe

Methods

gfoldl :: (forall d b. Data d => c (d -> b) -> d -> c b) -> (forall g. g -> c g) -> Universe -> c Universe #

gunfold :: (forall b r. Data b => c (b -> r) -> c r) -> (forall r. r -> c r) -> Constr -> c Universe #

toConstr :: Universe -> Constr #

dataTypeOf :: Universe -> DataType #

dataCast1 :: Typeable t => (forall d. Data d => c (t d)) -> Maybe (c Universe) #

dataCast2 :: Typeable t => (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c Universe) #

gmapT :: (forall b. Data b => b -> b) -> Universe -> Universe #

gmapQl :: (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Universe -> r #

gmapQr :: forall r r'. (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Universe -> r #

gmapQ :: (forall d. Data d => d -> u) -> Universe -> [u] #

gmapQi :: Int -> (forall d. Data d => d -> u) -> Universe -> u #

gmapM :: Monad m => (forall d. Data d => d -> m d) -> Universe -> m Universe #

gmapMp :: MonadPlus m => (forall d. Data d => d -> m d) -> Universe -> m Universe #

gmapMo :: MonadPlus m => (forall d. Data d => d -> m d) -> Universe -> m Universe #

Matchable Universe Source # 
Instance details

Defined in Retrie.Universe

class Matchable ast where Source #

Class of types which can be injected into the Universe type.

Methods

inject :: ast -> Universe Source #

Inject an AST into Universe

project :: Universe -> ast Source #

Project an AST from a Universe. Can fail if universe contains the wrong type.

getOrigin :: ast -> SrcSpan Source #

Get the original location of the AST.

toURewrite :: Matchable ast => Rewrite ast -> Rewrite Universe Source #

Inject a type-specific rewrite into the universal type.

fromURewrite :: Matchable ast => Rewrite Universe -> Rewrite ast Source #

Project a type-specific rewrite from the universal type.

GHC API

Retrie.GHC re-exports the GHC API, with some helpers for consistency across versions.

module Retrie.GHC