zephyr-0.2.0: test/TestDCECoreFn.hs
module TestDCECoreFn where
import Prelude ()
import Prelude.Compat
import Data.List (concatMap, foldl', intersect)
import qualified Data.List as L
import Language.PureScript.AST.Literals
import Language.PureScript.AST.SourcePos
import Language.PureScript.CoreFn
import Language.PureScript.DCE
import Language.PureScript.Names
import Language.PureScript.PSString
import Test.Hspec
import Test.QuickCheck
import Generators hiding (ann)
main :: IO ()
main = hspec spec
getNames :: Bind a -> [Ident]
getNames (NonRec _ i _) = [i]
getNames (Rec l) = (\((_, i), _) -> i) `map` l
hasIdent :: Ident -> [Bind Ann] -> Bool
hasIdent i = (i `elem`) . concatMap getNames
ann :: Ann
ann = ssAnn (SourceSpan "src/Test.purs" (SourcePos 0 0) (SourcePos 0 0))
prop_exprDepth :: PSExpr Ann -> Property
prop_exprDepth (PSExpr e) =
let b = NonRec ann (Ident "x") e
NonRec _ _ e' = dceExpr b
d = exprDepth e
d' = exprDepth e'
in collect (10 * (d' * 100 `div` (10 * d)))
$ counterexample (show e)
$ d' <= d
prop_lets :: PSExpr Ann -> Property
prop_lets (PSExpr f) =
let b = NonRec ann (Ident "x") f
NonRec _ _ f' = dceExpr b
d = countLets f
d' = countLets f'
idents = findBindIdents f'
in label ((if d > 0 then show (10 * ((d' * 100 `div` d) `div` 10)) ++ "%" else "-") ++ " of removed let bindings")
$ counterexample (show f)
$ d' <= d
&& L.null (intersect idents unusedIdents)
where
countLets :: Expr a -> Int
countLets (Literal _ (ArrayLiteral es)) = foldl' (\x e -> x + countLets e) 0 es
countLets (Literal _ (ObjectLiteral o)) = foldl' (\x (_, e) -> x + countLets e) 0 o
countLets Literal{} = 0
countLets Constructor{} = 0
countLets (Accessor _ _ e) = countLets e
countLets (ObjectUpdate _ e o) = countLets e + foldl' (\x (_, e') -> x + countLets e') 0 o
countLets (Abs _ _ e) = countLets e
countLets (App _ e f') = countLets e + countLets f'
countLets Var{} = 0
countLets (Case _ es cs) = foldl' (\x e -> x + countLets e) 0 es + foldl countLetsInCaseAlternative 0 cs
where
countLetsInCaseAlternative x (CaseAlternative _ r) =
x + either (foldl' (\y (g, e) -> y + countLets g + countLets e) 0) countLets r
countLets (Let _ _ e) = 1 + countLets e
findBindIdents :: Expr a -> [Ident]
findBindIdents (Literal _ (ArrayLiteral es)) = concatMap findBindIdents es
findBindIdents (Literal _ (ObjectLiteral o)) = concatMap (findBindIdents . snd) o
findBindIdents Literal{} = []
findBindIdents Constructor{} = []
findBindIdents (Accessor _ _ e) = findBindIdents e
findBindIdents (ObjectUpdate _ e o) = findBindIdents e ++ concatMap (findBindIdents . snd) o
findBindIdents (Abs _ _ e) = findBindIdents e
findBindIdents (App _ e f') = findBindIdents e ++ findBindIdents f'
findBindIdents Var{} = []
findBindIdents (Case _ es cs) = concatMap findBindIdents es ++ concatMap countLetsInCaseAlternative cs
where
countLetsInCaseAlternative (CaseAlternative _ r) =
either (concatMap (\(g, e1) -> findBindIdents g ++ findBindIdents e1)) findBindIdents r
findBindIdents (Let _ bs e) = concatMap fn bs ++ findBindIdents e
where
fn (NonRec _ i e1) = i : findBindIdents e1
fn (Rec as) = foldl' (\acc ((_, i), e1) -> i : findBindIdents e1 ++ acc) [] as
spec :: Spec
spec = do
context "generators" $ do
specify "should generate Expr" $ property $ prop_exprDistribution
context "dceExpr" $ do
specify "should reduce the depth of the tree" $ property $ withMaxSuccess 10000 prop_exprDepth
specify "should reduce the number of let bindings" $ property $ withMaxSuccess 10000 prop_lets
specify "should remove unused identifier" $ do
let e :: Expr Ann
e = Let ann
[ NonRec ann (Ident "notUsed") (Literal ann (CharLiteral 'a'))
, NonRec ann (Ident "used") (Literal ann (CharLiteral 'b'))
]
(Var ann (Qualified Nothing (Ident "used")))
case dceExpr (NonRec ann (Ident "v") e) of
NonRec _ _ (Let _ bs _) -> do
bs `shouldSatisfy` not . hasIdent (Ident "notUsed")
bs `shouldSatisfy` hasIdent (Ident "used")
_ -> return ()
specify "should not remove transitive dependency" $ do
let e :: Expr Ann
e = Let ann
[ NonRec ann (Ident "used") (Abs ann (Ident "x") (Var ann (Qualified Nothing (Ident "trDep"))))
, NonRec ann (Ident "trDep") (Literal ann (CharLiteral 'a'))
]
(Var ann (Qualified Nothing (Ident "used")))
case dceExpr (NonRec ann (Ident "v") e) of
NonRec _ _ (Let _ bs _) -> do
bs `shouldSatisfy` hasIdent (Ident "trDep")
bs `shouldSatisfy` hasIdent (Ident "used")
_ -> return ()
specify "should include all used recursive binds" $ do
let e :: Expr Ann
e = Let ann
[ NonRec ann (Ident "entry") (Abs ann (Ident "x") (Var ann (Qualified Nothing (Ident "mutDep1"))))
, Rec
[ ((ann, Ident "mutDep1"), Abs ann (Ident "x") (Var ann (Qualified Nothing (Ident "mutDep2"))))
, ((ann, Ident "mutDep2"), Abs ann (Ident "x") (Var ann (Qualified Nothing (Ident "mutDep1"))))
]
]
(App ann (Var ann (Qualified Nothing (Ident "entry"))) (Literal ann (CharLiteral 'a')))
case dceExpr (NonRec ann (Ident "v") e) of
NonRec _ _ (Let _ bs _) -> do
bs `shouldSatisfy` hasIdent (Ident "entry")
bs `shouldSatisfy` hasIdent (Ident "mutDep1")
bs `shouldSatisfy` hasIdent (Ident "mutDep2")
_ -> return ()
specify "should dce case expressions" $ do
let e :: Expr Ann
e = Let ann
[ NonRec ann (Ident "usedInExpr") (Literal ann (CharLiteral 'a'))
, NonRec ann (Ident "notUsed") (Literal ann (CharLiteral 'a'))
, NonRec ann (Ident "usedInGuard") (Literal ann (CharLiteral 'a'))
, NonRec ann (Ident "usedInResult1") (Literal ann (CharLiteral 'a'))
, NonRec ann (Ident "usedInResult2") (Literal ann (CharLiteral 'a'))
]
(Case ann
[Var ann (Qualified Nothing (Ident "usedInExpr"))]
[ CaseAlternative
[NullBinder ann]
(Left
[ ( Var ann (Qualified Nothing (Ident "usedInGuard"))
, Var ann (Qualified Nothing (Ident "usedInResult1"))
)
])
, CaseAlternative
[NullBinder ann]
(Right $ Var ann (Qualified Nothing (Ident "usedInResult2")))
])
case dceExpr (NonRec ann (Ident "v") e) of
NonRec _ _ (Let _ bs _) -> do
bs `shouldSatisfy` hasIdent (Ident "usedInExpr")
bs `shouldSatisfy` not . hasIdent (Ident "notUsed")
bs `shouldSatisfy` hasIdent (Ident "usedInGuard")
bs `shouldSatisfy` hasIdent (Ident "usedInResult1")
bs `shouldSatisfy` hasIdent (Ident "usedInResult2")
_ -> return ()
specify "should not remove shadowed identifiers" $ do
let e :: Expr Ann
e = Let ann
[ NonRec ann (Ident "shadow") (Literal ann (CharLiteral 'a'))
, NonRec ann (Ident "sunny") (Literal ann (CharLiteral 'a'))
]
$ Let ann
[ NonRec ann (Ident "shadow") (Literal ann (CharLiteral 'a')) ]
$ Literal ann
$ ObjectLiteral
[ ( mkString "a", Var ann (Qualified Nothing (Ident "shadow")) )
, ( mkString "b", Var ann (Qualified Nothing (Ident "sunny")) )
]
case dceExpr (NonRec ann (Ident "v") e) of
NonRec _ _ (Let _ bs (Let _ cs _)) -> do
bs `shouldSatisfy` hasIdent (Ident "sunny")
bs `shouldSatisfy` not . hasIdent (Ident "shadow")
cs `shouldSatisfy` hasIdent (Ident "shadow")
_ -> undefined