retrie-1.2.3: tests/Annotated.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 ScopedTypeVariables #-}
module Annotated (annotatedTest) where
import Control.Monad.State.Lazy
import Data.Data
import Data.Generics
-- import qualified Data.Map as M
-- import qualified Data.Set as S
-- import Data.Maybe
import qualified GHC.Paths as GHC.Paths
import Test.HUnit
#if MIN_VERSION_mtl(2, 3, 0)
import Control.Monad ((>=>))
#endif
import Retrie.ExactPrint
import Retrie.GHC
annotatedTest :: Test
annotatedTest = TestLabel "Annotated" $ TestList
[ -- increasingSeedTest
-- , elemsPostGraftTest
-- ,
inverseTest
, uniqueSrcSpanTest
-- , trimTest
]
exprs :: [String]
exprs =
[ "3 + x"
, "let _ = \\_ -> 4 in 7"
, "case x of { y -> y }"
, "let x = y in z"
, unlines
[ "case (3, 4) of"
, " (x, 5) -> (5, x)"
, " (_, y) -> (y, y)"
]
, unlines
[ "let f :: Int -> Int"
, " f x = x + 3"
, " y = 4"
, "in f y + f y"
]
]
types :: [String]
types =
[ "Int -> Int"
, "Data a => a -> Int"
, "(Eq a, Eq b) => (a -> b, b -> a)"
]
-- Run test on all ASTs parsed from the above lists
forAst :: (forall a. Data a => Annotated a -> IO ()) -> IO ()
forAst f = do
let libdir = GHC.Paths.libdir
mapM_ (parseExpr libdir >=> f) exprs
mapM_ (parseType libdir >=> f) types
-- Repeat a single transformation multiple times on an ast. The ast returned
-- from the previous transformation is passed to the next transformation.
testChainedTransforms :: (forall a. Data a => a -> TransformT IO a) -> IO ()
testChainedTransforms f = forAst $ \at -> do
_ <- fmap astA $ transformA at (f >=> f >=> f >=> f)
return ()
-- increasingSeedTest :: Test
-- increasingSeedTest = TestLabel "graft increases seed" $ TestCase $
-- testChainedTransforms transform
-- where
-- transform :: Data a => a -> TransformT IO a
-- transform = transformWithSeedIncreaseCheck . (pruneA >=> graftA)
-- Following a graft, the annotation map in the state has the expected elements
-- elemsPostGraftTest :: Test
-- elemsPostGraftTest = TestLabel "Expected elems in map" $ TestCase $
-- testChainedTransforms transform
-- where
-- transform :: Data a => a -> TransformT IO a
-- transform t = do
-- annsPreGraft <- gets fst
-- at <- pruneA t
-- t' <- graftA at
-- annsPostGraft <- gets fst
-- lift $ liftIO $ do
-- assertCountMaintained annsPreGraft t annsPostGraft
-- assertNoOverwrite annsPreGraft annsPostGraft
-- assertExactPrintAnns annsPreGraft annsPostGraft
-- return t'
inverseTest :: Test
inverseTest = TestLabel "graftA and pruneA are inverse" $ TestCase $
testChainedTransforms transform
where
transform :: Data a => a -> TransformT IO a
transform t = do
-- anns <- gets fst
at <- pruneA t
t' <- graftA at
-- anns' <- gets fst
-- lift $ liftIO $
-- assertAstsEqual "ast pre-graft is same as ast post-graft"
-- (anns, t)
-- (anns', t')
return t'
uniqueSrcSpanTest :: Test
uniqueSrcSpanTest = TestLabel "unique src span" $ TestCase $
fmap astA $ transformA (mempty :: Annotated ()) $ \() -> do
ss <- transformWithSeedIncreaseCheck uniqueSrcSpanT
lift $ liftIO $ assertGoodSrcSpan ss
-- trimTest :: Test
-- trimTest = TestLabel "trimA" $ TestCase $
-- forAst $ \at ->
-- let at' = trimA at in
-- assertLocsReplaced (astA at')
transformWithSeedIncreaseCheck :: TransformT IO a -> TransformT IO a
transformWithSeedIncreaseCheck m = do
seed <- get
x <- m
seed' <- get
lift $ liftIO $ assertBool "transform increases seed" (seed' > seed)
return x
-- Creates a query that returns the result of applying q to the argument (if the
-- argument is a GenLocated node containing a SrcSpan), otherwise returning
-- the provided default value.
locatedQ :: forall b. b -> (forall a. Data a => Located a -> b) -> GenericQ b
locatedQ defaultVal q = const defaultVal `ext2Q` query
where
query :: (Data loc, Data a) => GenLocated loc a -> b
query (L l t) =
case cast l :: Maybe SrcSpan of
Nothing -> defaultVal
Just ss -> q (L ss t)
-- -- Structure of HsExpr AST, including constructor names and annotations
-- -- associated with SrcSpan locations.
-- data ConTree = ConNode AnnConName (Maybe Annotation) [ConTree]
-- deriving (Eq, Show)
-- -- Assert ast equality (up to src span location labeling)
-- assertAstsEqual :: Data a => String -> (Anns, a) -> (Anns, a) -> IO ()
-- assertAstsEqual msg (anns1, t1) (anns2, t2) =
-- assertEqual msg (conTree anns1 t1) (conTree anns2 t2)
-- where
-- conTree :: Data a => Anns -> a -> ConTree
-- conTree anns = loop
-- where
-- loop :: Data a => a -> ConTree
-- loop t = ConNode (annGetConstr t) (annQ t) (gmapQ loop t)
-- annQ :: GenericQ (Maybe Annotation)
-- annQ = locatedQ Nothing $ \loc -> M.lookup (mkAnnKey loc) anns
-- Assert that all locations in the updated ast are generated by uniqueSrcSpanT
assertLocsReplaced :: Data a => a -> IO ()
assertLocsReplaced = everything (>>) assertReplaced
where
assertReplaced :: GenericQ (IO ())
assertReplaced = locatedQ (return ()) $ \(L ss _) -> assertGoodSrcSpan ss
-- -- Assert that every location in the ast has been added to the pre-graft
-- -- annotations to form the post-graft annotations.
-- assertCountMaintained :: Data a => Anns -> a -> Anns -> IO ()
-- assertCountMaintained annsPreGraft t annsPostGraft =
-- let numAnnsAdded = everything (+) countIfInAnns t in
-- assertEqual
-- "sum of pre-graft size and # of SrcSpan sites in AST equals post-graft size"
-- (M.size annsPreGraft + numAnnsAdded)
-- (M.size annsPostGraft)
-- where
-- countIfInAnns :: GenericQ Int
-- countIfInAnns = locatedQ 0 $ \loc ->
-- if M.member (mkAnnKey loc) annsPreGraft then 1 else 0
-- -- Check that no data in pre-graft map was overwritten.
-- assertNoOverwrite :: Anns -> Anns -> IO ()
-- assertNoOverwrite annsPreGraft annsPostGraft =
-- assertEqual "pre-graft keys correspond to same data as post-graft"
-- dataPreGraft
-- dataPostGraft
-- where
-- dataPreGraft = M.toList annsPreGraft
-- dataPostGraft = mapMaybe (\(k, _) -> do
-- v <- M.lookup k annsPostGraft
-- return (k, v))
-- dataPreGraft
-- -- Assert that the annotation keys corresponding to newly-added data are of the
-- -- expected form.
-- assertExactPrintAnns :: Anns -> Anns -> IO ()
-- assertExactPrintAnns annsPreGraft annsPostGraft =
-- forM_ newKeys $ \(AnnKey ss _) ->
-- #if __GLASGOW_HASKELL__ < 900
-- assertGoodSrcSpan ss
-- #else
-- assertGoodRealSrcSpan ss
-- #endif
-- where
-- newKeys :: S.Set AnnKey
-- newKeys = M.keysSet annsPostGraft `S.difference` M.keysSet annsPreGraft
assertGoodSrcSpan :: SrcSpan -> IO ()
assertGoodSrcSpan srcSpan =
case getRealSpan srcSpan of
Just rss -> assertGoodRealSrcSpan rss
Nothing ->
assertFailure "only real src spans should be generated"
assertGoodRealSrcSpan :: RealSrcSpan -> IO ()
assertGoodRealSrcSpan rss = do
assertGoodSrcLoc (realSrcSpanStart rss)
assertGoodSrcLoc (realSrcSpanEnd rss)
assertGoodSrcLoc :: RealSrcLoc -> IO ()
assertGoodSrcLoc srcLoc = do
let
file = unpackFS $ srcLocFile srcLoc
line = srcLocLine srcLoc
assertEqual "srcLoc file is correctly named" "ghc-exactprint" file
assertEqual "srcLoc line is correctly placed" (-1) line