packages feed

retrie 1.0.0.0 → 1.1.0.0

raw patch · 9 files changed

+240/−71 lines, 9 filesdep +list-tdep ~bytestringdep ~optparse-applicativedep ~textnew-component:exe:demo-retrienew-uploaderPVP ok

version bump matches the API change (PVP)

Dependencies added: list-t

Dependency ranges changed: bytestring, optparse-applicative, text

API changes (from Hackage documentation)

+ Retrie: [elaborations] :: Options_ rewrites imports -> rewrites
+ Retrie: [noDefaultElaborations] :: Options_ rewrites imports -> Bool
+ Retrie.Elaborate: defaultElaborations :: [RewriteSpec]
+ Retrie.Elaborate: elaborateRewritesInternal :: FixityEnv -> [Rewrite Universe] -> [Rewrite Universe] -> IO [Rewrite Universe]
+ Retrie.Expr: getUnparened :: Data k => k -> k
+ Retrie.Options: GrepCommands :: [FilePath] -> [CommandLine] -> GrepCommands
+ Retrie.Options: [commandChain] :: GrepCommands -> [CommandLine]
+ Retrie.Options: [elaborations] :: Options_ rewrites imports -> rewrites
+ Retrie.Options: [initialFileSet] :: GrepCommands -> [FilePath]
+ Retrie.Options: [noDefaultElaborations] :: Options_ rewrites imports -> Bool
+ Retrie.Options: data GrepCommands
+ Retrie.Options: instance GHC.Classes.Eq Retrie.Options.GrepCommands
+ Retrie.Options: instance GHC.Show.Show Retrie.Options.GrepCommands
- Retrie: Options :: imports -> ColoriseFun -> ExecutionMode -> [FilePath] -> FixityEnv -> Int -> Bool -> rewrites -> [RoundTrip] -> Bool -> FilePath -> [FilePath] -> Verbosity -> Options_ rewrites imports
+ Retrie: Options :: imports -> ColoriseFun -> rewrites -> ExecutionMode -> [FilePath] -> FixityEnv -> Int -> Bool -> Bool -> rewrites -> [RoundTrip] -> Bool -> FilePath -> [FilePath] -> Verbosity -> Options_ rewrites imports
- Retrie.Options: Options :: imports -> ColoriseFun -> ExecutionMode -> [FilePath] -> FixityEnv -> Int -> Bool -> rewrites -> [RoundTrip] -> Bool -> FilePath -> [FilePath] -> Verbosity -> Options_ rewrites imports
+ Retrie.Options: Options :: imports -> ColoriseFun -> rewrites -> ExecutionMode -> [FilePath] -> FixityEnv -> Int -> Bool -> Bool -> rewrites -> [RoundTrip] -> Bool -> FilePath -> [FilePath] -> Verbosity -> Options_ rewrites imports
- Retrie.Options: buildGrepChain :: FilePath -> HashSet String -> [FilePath] -> Either [FilePath] (String, [String])
+ Retrie.Options: buildGrepChain :: FilePath -> HashSet String -> [FilePath] -> GrepCommands

Files

CHANGELOG.md view
@@ -1,3 +1,8 @@++1.1.0.0 (November 13, 2021)+* Remove dependency on xargs (#31)+* Allow rewrite elaboration+ 1.0.0.0 (April 9, 2021)  * Added --adhoc-type flag (#13)
+ Retrie/Elaborate.hs view
@@ -0,0 +1,115 @@+-- 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 FlexibleContexts #-}+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeApplications #-}+module Retrie.Elaborate+  ( defaultElaborations+  , elaborateRewritesInternal+  ) where++import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.Trans.Class+import "list-t" ListT+import Data.Maybe++import Retrie.Context+import Retrie.ExactPrint+import Retrie.Expr+import Retrie.Fixity+import Retrie.GHC+import Retrie.Quantifiers+import Retrie.Rewrites+import Retrie.Subst+import Retrie.Substitution+import Retrie.SYB+import Retrie.Types+import Retrie.Universe++defaultElaborations :: [RewriteSpec]+defaultElaborations =+  [ Adhoc "forall f x. f $ x = f (x)"+  ]++elaborateRewritesInternal+  :: FixityEnv+  -> [Rewrite Universe]+  -> [Rewrite Universe]+  -> IO [Rewrite Universe]+elaborateRewritesInternal _ [] rewrites = return rewrites+elaborateRewritesInternal fixityEnv elaborations rewrites =+  concat <$> mapM (elaborateOne fixityEnv elaborator) rewrites+  where+    elaborator = foldMap mkRewriter elaborations++elaborateOne :: FixityEnv -> Rewriter -> Rewrite Universe -> IO [Rewrite Universe]+elaborateOne fixityEnv elaborator rr = do+  patterns <-+    transformA (qPattern rr) $ toList .+      everywhereMWithContextBut topDown+        (const False) (\c i x -> lift $ updateContext c i x) elaborate ctxt+  return [ rr { qPattern = pattern } | pattern <- sequenceA patterns ]+  where+    ctxt = emptyContext fixityEnv elaborator mempty++elaborate+  :: (Data a, MonadIO m) => Context -> a -> ListT (TransformT m) a+elaborate c =+  mkM (elaborateImpl @(HsExpr GhcPs) c)+    `extM` (elaborateImpl @(Stmt GhcPs (LHsExpr GhcPs)) c)+    `extM` (elaborateImpl @(HsType GhcPs) c)+    `extM` (elaboratePat c)++elaboratePat :: MonadIO m => Context -> LPat GhcPs -> ListT (TransformT m) (LPat GhcPs)+-- We need to ensure we have a location available at the top level so we can+-- transfer annotations. This ensures we don't try to rewrite a naked Pat.+elaboratePat c p+  | Just lp <- dLPat p = cLPat <$> elaborateImpl c lp+  | otherwise = return p++elaborateImpl+  :: forall ast m. (Annotate ast, Matchable (Located ast), MonadIO m)+  => Context -> Located ast -> ListT (TransformT m) (Located ast)+elaborateImpl ctxt e = do+  elaborations <- lift $ do+    matches <- runMatcher ctxt (ctxtRewriter ctxt) (getUnparened e)+    validMatches <- allMatches ctxt matches+    forM [ (sub, tmpl) | MatchResult sub tmpl <- validMatches ] $ \(sub, Template{..}) -> do+      -- graft template into target+      t' <- graftA tTemplate+      -- substitute for quantifiers in grafted template+      r <- subst sub ctxt t'+      -- copy appropriate annotations from old expression to template+      addAllAnnsT e r+      -- add parens to template if needed+      (mkM (parenify ctxt) `extM` parenifyT ctxt `extM` parenifyP ctxt) r++  fromFoldable (e : elaborations)++-- | Find the first 'valid' match.+-- Runs the user's 'MatchResultTransformer' and sanity checks the result.+allMatches+  :: (Matchable ast, MonadIO m)+  => Context+  -> [(Substitution, RewriterResult Universe)]+  -> TransformT m [MatchResult ast]+allMatches _ [] = return []+allMatches ctxt matchResults = do+  results <-+    forM matchResults $ \(sub, RewriterResult{..}) -> do+      result <- lift $ liftIO $ rrTransformer ctxt $ MatchResult sub rrTemplate+      return (rrQuantifiers, result)+  return+    [ project <$> result+    | (quantifiers, result@(MatchResult sub' _)) <- results+      -- Check that all quantifiers from the original rewrite have mappings+      -- in the resulting substitution. This is mostly to prevent a bad+      -- user-defined MatchResultTransformer from causing havok.+    , isJust $ sequence [ lookupSubst q sub' | q <- qList quantifiers ]+    ]
Retrie/Expr.hs view
@@ -10,6 +10,7 @@ {-# LANGUAGE ViewPatterns #-} module Retrie.Expr   ( bitraverseHsConDetails+  , getUnparened   , grhsToExpr   , mkApps   , mkConPatIn@@ -263,6 +264,9 @@     needed NeverParen _ = False     needed _ Nothing = True     needed _ _ = False++getUnparened :: Data k => k -> k+getUnparened = mkT unparen `extT` unparenT `extT` unparenP  unparen :: LHsExpr GhcPs -> LHsExpr GhcPs unparen (L _ (HsPar _ e)) = e
Retrie/Options.hs view
@@ -23,10 +23,11 @@   , parseVerbosity   , ProtoOptions   , resolveOptions+  , GrepCommands(..)   ) where  import Control.Concurrent.Async (mapConcurrently)-import Control.Monad (when)+import Control.Monad (when, foldM) import Data.Bool import Data.Char (isAlphaNum, isSpace) import Data.Default as D@@ -44,6 +45,7 @@  import Retrie.CPP import Retrie.Debug+import Retrie.Elaborate import Retrie.ExactPrint import Retrie.Fixity import Retrie.GroundTerms@@ -87,6 +89,8 @@     -- ^ Imports specified by the command-line flag '--import'.   , colorise :: ColoriseFun     -- ^ Function used to colorize results of certain execution modes.+  , elaborations :: rewrites+    -- ^ Rewrites which are applied to the left-hand side of the actual rewrites.   , executionMode :: ExecutionMode     -- ^ Controls behavior of 'apply'. See 'ExecutionMode'.   , extraIgnores :: [FilePath]@@ -99,6 +103,8 @@     -- ^ Iterate the given rewrites or 'Retrie' computation up to this many     -- times. Iteration may stop before the limit if no changes are made during     -- a given iteration.+  , noDefaultElaborations :: Bool+    -- ^ Do not apply any of the built in elaborations in 'defaultElaborations'.   , randomOrder :: Bool     -- ^ Whether to randomize the order of target modules before rewriting them.   , rewrites :: rewrites@@ -125,10 +131,12 @@ defaultOptions fp = Options   { additionalImports = D.def   , colorise = noColor+  , elaborations = D.def   , executionMode = ExecRewrite   , extraIgnores = []   , fixityEnv = mempty   , iterateN = 1+  , noDefaultElaborations = False   , randomOrder = False   , rewrites = D.def   , roundtrips = []@@ -184,6 +192,11 @@     [ long "color"     , help "Highlight matches with color."     ]+  noDefaultElaborations <- switch $ mconcat+    [ long "no-default-elaborations"+    , showDefault+    , help "Don't apply any of the default elaborations to rewrites."+    ]   randomOrder <- switch $ mconcat     [ long "random-order"     , help "Randomize the order of targeted modules."@@ -198,9 +211,29 @@    executionMode <- parseMode   rewrites <- parseRewriteSpecOptions+  elaborations <- parseElaborations   roundtrips <- parseRoundtrips   return Options{ fixityEnv = fixityEnv dOpts, ..} +parseElaborations :: Parser [RewriteSpec]+parseElaborations = concat <$> traverse many+  [ fmap Adhoc $ option str $ mconcat+    [ long "elaborate"+    , metavar "EQUATION"+    , help "Elaborate the left-hand side of rewrites using the given equation."+    ]+  , fmap AdhocType $ option str $ mconcat+    [ long "elaborate-type"+    , metavar "EQUATION"+    , help "Elaborate the left-hand side of rewrites using the given equation."+    ]+  , fmap AdhocPattern $ option str $ mconcat+    [ long "elaborate-pattern"+    , metavar "EQUATION"+    , help "Elaborate the left-hand side of rewrites using the given equation."+    ]+  ]+ parseRewriteSpecOptions :: Parser [RewriteSpec] parseRewriteSpecOptions = concat <$> traverse many   [ fmap Unfold $ option str $ mconcat@@ -328,9 +361,14 @@       anns <- getAnnsT       return $ map (`exactPrint` anns) imps   rrs <- parseRewritesInternal opts rewrites+  es <- parseRewritesInternal opts $+    (if noDefaultElaborations then [] else defaultElaborations) +++    elaborations+  elaborated <- elaborateRewritesInternal fixityEnv es rrs   return Options     { additionalImports = parsedImports-    , rewrites = rrs+    , elaborations = es+    , rewrites = elaborated     , singleThreaded = singleThreaded || verbosity == Loud     , ..     }@@ -373,10 +411,7 @@   let ignore fp = ignorePred fp || extraIgnorePred fp   fpSets <- forM (dedup gtss) $ \ gts -> do     -- See Note [Ground Terms]-    fps <--      case buildGrepChain targetDir gts targetFiles of-        Left fs -> return fs-        Right (stdin, cmd) -> doCmd targetDir verbosity stdin (unwords cmd)+    fps <- runGrepChain targetDir verbosity (buildGrepChain targetDir gts targetFiles)      let       r = filter (not . ignore)@@ -395,19 +430,34 @@         putStrLn "Reading VCS ignore failed! Continuing without ignoring."       return $ const False --- | Either returns an exact list of target paths, or a command for finding--- them.+-- | Return a chain of grep commands to find files with relevant groundTerms+-- If filesGiven is empty, use all *.hs files under targetDir buildGrepChain   :: FilePath   -> HashSet String   -> [FilePath]-  -> Either [FilePath] (String, [String])-buildGrepChain targetDir gts =-  -- Limit the size of the shell command we build by only selecting-  -- up to 10 ground terms. The goal is to filter file list down to-  -- a manageable size. It doesn't have to be exact.-  filterFiles (take 10 $ filter p $ HashSet.toList gts)+  -> GrepCommands+buildGrepChain targetDir gts filesGiven = GrepCommands {initialFileSet=filesGiven, commandChain=commands}   where+    commands = if null filesGiven+               then commandsWithoutFiles+               else commandsWithFiles++    commandsWithFiles = case terms of+        [] -> [] -- no processing+        gs -> map normalGrep gs+    commandsWithoutFiles = case terms of+        [] -> [findCmd] -- all .hs files+        g:gs -> recursiveGrep g : map normalGrep gs -- start with recursive grep++    findCmd = unwords ["find", quotePath (addTrailingPathSeparator targetDir), "-iname", hsExtension]+    recursiveGrep g = unwords ["grep", "-R", "--include=" ++ hsExtension, "-l", esc g, quotePath targetDir]+    normalGrep gt = unwords ["grep", "-l", esc gt]++   -- Limit the number of the shell command we build by only selecting+   -- up to 10 ground terms. The goal is to filter file list down to+   -- a manageable size. It doesn't have to be exact.+    terms = take 10 $ filter p $ HashSet.toList gts     p [] = False     p (c:cs)       | isSpace c = p cs@@ -415,28 +465,35 @@      hsExtension = "\"*.hs\"" -    filterFiles [] [] = Right ("", findCmd) -- all .hs files-    filterFiles [] fs = Left fs -- targetFiles-    -- start with all .hs files and filter-    filterFiles (g:gs) [] =-      Right ("", intercalate ["|"] $ firstCmd g : filterChain gs)-    -- start with targetFiles and filter-    filterFiles gs fs =-      Right (unlines fs, intercalate ["|"] $ filterChain gs)+    esc s = "'" ++ intercalate "[[:space:]]\\+" (words $ escChars s) ++ "'"+    escChars = concatMap escChar+    escChar c+      | c `elem` magicChars = "\\" <> [c]+      | otherwise  = [c]+    magicChars :: [Char]+    magicChars = "*?[#˜=%\\" -    findCmd = ["find", addTrailingPathSeparator targetDir, "-iname", hsExtension] -    firstCmd g =-      ["grep", "-R", "--include=" ++ hsExtension, "-l", esc g, targetDir]+type CommandLine = String+data GrepCommands = GrepCommands { initialFileSet :: [FilePath], commandChain :: [CommandLine] }+  deriving (Eq, Show) -    filterChain gs = [ ["xargs", "grep", "-l", esc gt] | gt <- gs ]+runGrepChain :: FilePath -> Verbosity -> GrepCommands -> IO [FilePath]+runGrepChain targetDir verbosity GrepCommands{..} = foldM (commandStep targetDir verbosity)  initialFileSet commandChain -    esc s = "'" ++ intercalate "[[:space:]]\\+" (words s) ++ "'"+-- | run a command with a list of files as quoted arguments+commandStep :: FilePath -> Verbosity -> [FilePath]-> CommandLine -> IO [FilePath]+commandStep targetDir verbosity files cmd = doCmd targetDir verbosity (cmd <> formatPaths files)+ where+    formatPaths [] = ""+    formatPaths xs = " " <> unwords (map quotePath xs)+quotePath :: FilePath -> FilePath+quotePath x = "'" <> x <> "'" -doCmd :: FilePath -> Verbosity -> String -> String -> IO [FilePath]-doCmd targetDir verbosity inp shellCmd = do-  debugPrint verbosity "stdin:" [inp]++doCmd :: FilePath -> Verbosity -> String -> IO [FilePath]+doCmd targetDir verbosity shellCmd = do   debugPrint verbosity "shellCmd:" [shellCmd]   let cmd = (shell shellCmd) { cwd = Just targetDir }-  (_ec, fps, _) <- readCreateProcessWithExitCode cmd inp+  (_ec, fps, _) <- readCreateProcessWithExitCode cmd ""   return $ lines fps
Retrie/PatternMap/Instances.hs view
@@ -265,17 +265,6 @@ -- Statement lists bind to the right, so we need to extend the environment -- as we move down it. Thus we cannot simply store them as ListMap SMap a. --- Note [Dollar Fork]--- When 'f $ x' appears in the pattern, we insert two things in the EMap--- instead of just one:------ * The original infix application of ($).--- * The expression transformed into a normal application with parens around---   the right argument to ($). i.e. f (x)------ This allows us to put ($) in the LHS of rewrites and match both literal ($)--- applications and the parenthesized equivalent.- data EMap a   = EMEmpty   | EM { emHole  :: Map RdrName a -- See Note [Holes]@@ -347,13 +336,6 @@   mAlter env vs e f EMEmpty = mAlter env vs e f emptyEMapWrapper   mAlter env vs e f m@EM{} = go (unLoc e)     where-      -- See Note [Dollar Fork]-      dollarFork v@HsVar{} l r-        | Just (L _ rdr) <- varRdrName v-        , occNameString (occName rdr) == "$" =-          go (HsApp noExtField l (noLoc (HsPar noExtField r)))-      dollarFork _ _ _ = m-       go (HsVar _ v)         | unLoc v `isQ` vs = m { emHole  = mAlter env vs (unLoc v) f (emHole m) }         | otherwise        = m { emVar   = mAlter env vs (unLoc v) f (emVar m) }@@ -379,8 +361,8 @@       go (HsOverLit _ ol) = m { emOverLit = mAlter env vs (ol_val ol) f (emOverLit m) }       go (NegApp _ e' _) = m { emNegApp = mAlter env vs e' f (emNegApp m) }       go (HsPar _ e') = m { emPar  = mAlter env vs e' f (emPar m) }-      go (OpApp _ l o r) = (dollarFork (unLoc o) l r)-        { emOpApp = mAlter env vs o (toA (mAlter env vs l (toA (mAlter env vs r f)))) (emOpApp m) }+      go (OpApp _ l o r) =+        m { emOpApp = mAlter env vs o (toA (mAlter env vs l (toA (mAlter env vs r f)))) (emOpApp m) }       go (RecordCon _ v fs) =         m { emRecordCon = mAlter env vs (unLoc v) (toA (mAlter env vs (fieldsToRdrNames $ rec_flds fs) f)) (emRecordCon m) }       go (RecordUpd _ e' fs) =
Retrie/Replace.hs view
@@ -65,6 +65,11 @@       , any (`elemFVs` fvs) (ctxtBinders c) = NoMatch       | otherwise = match +  -- We want to match through HsPar so we can make a decision+  -- about whether to keep the parens or not based on the+  -- resulting expression, but we need to know the entry location+  -- of the parens, not the inner expression, so we have to+  -- keep both expressions around.   match <- runRewriter f c (ctxtRewriter c) (getUnparened e)    case match of@@ -108,14 +113,6 @@   mappend other        NoChange     = other   mappend (Change rs1 is1) (Change rs2 is2) =     Change (rs1 <> rs2) (is1 <> is2)---- We want to match through HsPar so we can make a decision--- about whether to keep the parens or not based on the--- resulting expression, but we need to know the entry location--- of the parens, not the inner expression, so we have to--- keep both expressions around.-getUnparened :: Data k => k -> k-getUnparened = mkT unparen `extT` unparenT `extT` unparenP  -- The location of 'e' accurately points to the first non-space character -- of 'e', but when we exactprint 'e', we might get some leading spaces (if
retrie.cabal view
@@ -4,7 +4,7 @@ -- LICENSE file in the root directory of this source tree. -- name: retrie-version: 1.0.0.0+version: 1.1.0.0 synopsis: A powerful, easy-to-use codemodding tool for Haskell. homepage: https://github.com/facebookincubator/retrie bug-reports: https://github.com/facebookincubator/retrie/issues@@ -44,6 +44,7 @@     Retrie.CPP,     Retrie.Context,     Retrie.Debug,+    Retrie.Elaborate,     Retrie.ExactPrint,     Retrie.ExactPrint.Annotated,     Retrie.Expr,@@ -84,6 +85,7 @@     filepath >= 1.4.2 && < 1.5,     ghc >= 8.8 && < 9.2,     ghc-exactprint >= 0.6.2 && < 0.7,+    list-t >= 1.0.4 && < 1.1,     mtl >= 2.2.2 && < 2.3,     optparse-applicative >= 0.15.1 && < 0.17,     process >= 1.6.3 && < 1.7,@@ -114,7 +116,7 @@     haskell-src-exts >= 1.23.0 && < 1.24   default-language: Haskell2010 -executable demo+executable demo-retrie   if flag(BuildExecutable)     Buildable: True   else
tests/GroundTerms.hs view
@@ -28,29 +28,39 @@ groundTermsTest = TestLabel "ground terms" $ TestList   [ gtTest "map"       ""+      []       [Adhoc "forall f g xs. map f (map g xs) = map (f . g) xs"]       [["map"]]-      [("", "grep -R --include=\"*.hs\" -l 'map' ~/si_sigma")]+      [GrepCommands [] ["grep -R --include=\"*.hs\" -l 'map' '~/si_sigma'"]]   , gtTest "isSpace"       ""+      []       [Adhoc "forall xs. or (map isSpace xs) = any isSpace xs"]       [["or", "map isSpace"]]-      [("", "grep -R --include=\"*.hs\" -l 'or' ~/si_sigma | xargs grep -l 'map[[:space:]]\\+isSpace'")]+      [GrepCommands [] ["grep -R --include=\"*.hs\" -l 'or' '~/si_sigma'", "grep -l 'map[[:space:]]\\+isSpace'"]]   , gtTest "MyType"       "type MyType a = MyOtherType a"+      []       [TypeForward "Test.MyType"]       [["MyType"]]-      [("", "grep -R --include=\"*.hs\" -l 'MyType' ~/si_sigma")]+      [GrepCommands [] ["grep -R --include=\"*.hs\" -l 'MyType' '~/si_sigma'"]]+  , gtTest "isSpace with file"+      ""+      ["Test.hs", "Test2.hs"]+      [Adhoc "forall xs. or (map isSpace xs) = any isSpace xs"]+      [["or", "map isSpace"]]+      [GrepCommands ["Test.hs", "Test2.hs"] ["grep -l 'or'", "grep -l 'map[[:space:]]\\+isSpace'"]]   ]  gtTest   :: String   -> Text+  -> [FilePath]   -> [RewriteSpec]   -> [[String]]-  -> [(String, String)]+  -> [GrepCommands]   -> Test-gtTest lbl contents specs expected expectedCmds =+gtTest lbl contents targFiles specs expected expectedCmds =   TestLabel ("groundTerms: " ++ lbl) $ TestCase $ do     -- since we 'zip' below     assertEqual "length of specs and expected ground terms"@@ -72,12 +82,9 @@       (HashSet.fromList gtss) -- compare hashsets to avoid ordering issues      forM_ (zip gtss expectedCmds) $ \(gts, expectedCmd) ->-      case buildGrepChain "~/si_sigma" gts [] of-        Left _ -> assertFailure "gtTest: Left"-        Right (i, c) ->           assertEqual "buildGrepChain did not give expected command"             expectedCmd-            (i, unwords c)+            (buildGrepChain "~/si_sigma" gts targFiles)  getFocusTests :: IO [Test] getFocusTests = do
tests/Targets.hs view
@@ -28,7 +28,7 @@       gts = [HashSet.fromList ["Groundterm"]]       targetFps = retrieTargetFiles       -- 'withFakeHgRepo' creates every file with its own name as the contents-      expectedFps = ["targeted/Groundterm.hs"]+      expectedFps = ["targeted" </> "Groundterm.hs"]  assertFileListEqual   :: [FilePath]