g2-0.2.0.0: src/G2/Liquid/ConvertCurrExpr.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
module G2.Liquid.ConvertCurrExpr ( convertCurrExpr
, initiallyCalledFuncName) where
import G2.Language
import G2.Language.Monad
import qualified G2.Language.ExprEnv as E
import G2.Liquid.Conversion
import G2.Liquid.Types
import Control.Monad.Extra
import qualified Data.HashMap.Lazy as HM
import Data.Maybe
-- | Returns (1) the Id of the new main function and (2) the functions that need counterfactual variants
convertCurrExpr :: Id -> Bindings -> LHStateM (Id, [Name])
convertCurrExpr ifi bindings = do
ifi' <- modifyInputExpr ifi
mapWithKeyME (\(Name _ m _ _) e -> if isJust m then letLiftHigherOrder e else return e)
addCurrExprAssumption ifi bindings
return ifi'
-- We create a copy of the input function which is modified to:
-- (1) Call a different copy of each of it's internal functions.
-- This allows us to only nondeterministically branch into abstract
-- counterexamples from the initial function call
--
-- (2) Call all functions in let bindings. I.e., the following:
-- Just (f x)
-- would be changed to:
-- let fx = f x in Just fx
-- This way, if we reference the output in both the refinement and the body,
-- it'll only be computed once. This is NOT just for efficiency.
-- Since the choice is nondeterministic, this is the only way to ensure that
-- we don't make two different choices, and get two different values.
modifyInputExpr :: Id -> LHStateM (Id, [Name])
modifyInputExpr i@(Id n _) = do
(CurrExpr er ce) <- currExpr
e <- lookupE n
case e of
Just je -> do
(newI, ns) <- modifyInputExpr' i je
let ce' = replaceVarWithName (idName i) (Var newI) ce
putCurrExpr (CurrExpr er ce')
return (newI, ns)
Nothing -> return (error "Name not found", [])
-- Actually does the work of modify the function for modifyInputExpr
-- Inserts the new function in the ExprEnv, and returns the Id
modifyInputExpr' :: Id -> Expr -> LHStateM (Id, [Name])
modifyInputExpr' i e = do
(e', ns) <- rebindFuncs e
e'' <- letLiftFuncs e'
e''' <- replaceLocalAssert i e''
newI <- freshSeededIdN (idName i) (typeOf i)
insertE (idName newI) e'''
return (newI, ns)
rebindFuncs :: Expr -> LHStateM (Expr, [Name])
rebindFuncs e = do
vs <- mapMaybeM (\i -> fmap (i,) <$> lookupE (idName i)) $ varIds e
nvs <- mapM (\(Id n t, _) -> freshSeededIdN n t) vs
mapM_ (\(n, e_) -> insertE n (rewriteAssertName n e_)) $ zip (map idName nvs) (map snd vs)
let e' = foldr (uncurry replaceASTs) e $ zip (map (Var . fst) vs) (map Var nvs)
return (e', map idName nvs)
where
rewriteAssertName :: Name -> Expr -> Expr
rewriteAssertName n (Assert (Just fc) e1 e2) = Assert (Just $ fc {funcName = n}) e1 e2
rewriteAssertName n e1 = modifyChildren (rewriteAssertName n) e1
-- | We are assuming the precondiiton holds, so we only have to check the postcondition!
-- We also replace the name of the assert so we can recognize it as the inital call later.
replaceLocalAssert :: Id -> Expr -> LHStateM Expr
replaceLocalAssert (Id n _) ce = do
n_assert <- lookupPostM n
-- Replace the initial function assertion with one that only checks the postcondition,
-- but is careful to not replace assertions tied to higher order functions.
let ce' = insertInLams
(\_ e -> case e of
Let b (Assert (Just fc) e1 e2) ->
let ars = arguments fc ++ [returns fc]
assrt = case n_assert of
Just a -> mkApp (a:ars)
Nothing -> e1
in
Let b $ Assert (Just fc) assrt e2
_ -> e) ce
ce'' = modifyASTs
(\e -> case e of
Assert (Just fc) e1 e2 ->
Assert (Just $ fc { funcName = initiallyCalledFuncName}) e1 e2
_ -> e) ce'
return ce''
initiallyCalledFuncName :: Name
initiallyCalledFuncName = Name "INITIALLY_CALLED_FUNC" Nothing 0 Nothing
replaceVarWithName :: Name -> Expr -> Expr -> Expr
replaceVarWithName n new = modify (replaceVarWithName' n new)
replaceVarWithName' :: Name -> Expr -> Expr -> Expr
replaceVarWithName' n new v@(Var (Id n' _)) = if n == n' then new else v
replaceVarWithName' _ _ e = e
-- We want to get all function calls into Let Bindings.
-- This is a bit tricky- we can't just get all calls at once,
-- stick them in a let binding, and then rewrite, because the calls may be nested.
-- So we gather them up, one by one, and rewrite as we go.
-- Furthermore, we have to be careful to not move bindings from Lambdas/other Let's
-- out of scope.
letLiftFuncs :: Expr -> LHStateM Expr
letLiftFuncs e = do
e' <- modifyAppTopE letLiftFuncs' e
return $ flattenLets e'
letLiftFuncs' :: Expr -> LHStateM Expr
letLiftFuncs' e
| ars <- passedArgs e
, any (\case { Var _ -> False; _ -> True }) ars = do
let c = appCenter e
is <- freshIdsN $ map typeOf ars
return . Let (zip is ars) . mkApp $ c:map Var is
| otherwise = return e
-- | Tries to be more selective then liftLetFuncs, doesn't really work yet...
letLiftHigherOrder :: Expr -> LHStateM Expr
letLiftHigherOrder e = return . shiftLetsOutOfApps =<< insertInLamsE letLiftHigherOrder' e
letLiftHigherOrder' :: [Id] -> Expr -> LHStateM Expr
letLiftHigherOrder' is e@(App _ _)
| Var i <- appCenter e
, i `elem` is = do
ni <- freshIdN (typeOf e)
e' <- modifyAppRHSE (letLiftHigherOrder' is) e
return $ Let [(ni, e')] (Var ni)
| d@(Data _) <- appCenter e = do
let ars = passedArgs e
f_is <- freshIdsN $ map typeOf ars
ars' <- mapM (letLiftHigherOrder' f_is) ars
return . Let (zip f_is ars') . mkApp $ d:map Var f_is
letLiftHigherOrder' is e@(Lam _ _ _) = insertInLamsE (\is' -> letLiftHigherOrder' (is ++ is')) e
letLiftHigherOrder' is e = modifyChildrenM (letLiftHigherOrder' is) e
shiftLetsOutOfApps :: Expr -> Expr
shiftLetsOutOfApps e@(App _ _) =
case shiftLetsOutOfApps' e of
Let b e' -> Let b . modifyBottomApp shiftLetsOutOfApps $ e'
e' -> modifyBottomApp shiftLetsOutOfApps $ e'
shiftLetsOutOfApps e = modifyChildren shiftLetsOutOfApps e
shiftLetsOutOfApps' :: Expr -> Expr
shiftLetsOutOfApps' a@(App _ _) =
let
b = getLetsInApp a
in
case b of
[] -> a
_ -> Let b $ elimLetsInApp a
shiftLetsOutOfApps' _ = error "shiftLetsOutOfApps': must be passed an App"
getLetsInApp :: Expr -> Binds
getLetsInApp (Let b e) = b ++ getLetsInApp e
getLetsInApp (App e e') = getLetsInApp e ++ getLetsInApp e'
getLetsInApp _ = []
elimLetsInApp :: Expr -> Expr
elimLetsInApp (Let _ e) = elimLetsInApp e
elimLetsInApp (App e e') = App (elimLetsInApp e) (elimLetsInApp e')
elimLetsInApp e = e
modifyBottomApp :: (Expr -> Expr) -> Expr -> Expr
modifyBottomApp f (App e e') = App (modifyBottomApp f e) (modifyBottomApp f e')
modifyBottomApp f e = f e
-- We add an assumption about the inputs to the current expression
-- This prevents us from finding a violation of the output refinement type
-- that requires a violation of the input refinement type
addCurrExprAssumption :: Id -> Bindings -> LHStateM ()
addCurrExprAssumption ifi (Bindings {fixed_inputs = fi}) = do
(CurrExpr er ce) <- currExpr
lh_tc_n <- lhTCM
let lh_tc = TyCon lh_tc_n (TyFun TYPE TYPE)
let fi' = filter (\e -> tyAppCenter (typeOf e) /= lh_tc) fi
assumpt <- lookupAssumptionM (idName ifi)
-- fi <- fixedInputs
eenv <- exprEnv
inames <- inputNames
lh <- mapM (lhTCDict' HM.empty) $ mapMaybe typeType fi'
let is = catMaybes (map (E.getIdFromName eenv) inames)
let (typs, ars) = span isType $ fi' ++ map Var is
case assumpt of
Just (assumpt_is, higher_is, assumpt') -> do
let all_args = typs ++ lh ++ ars
appAssumpt = mkApp $ assumpt':all_args
inputs <- inputNames
let matching = zipWith (\n (i, hi) -> (n, i, hi)) inputs $ drop (length higher_is - length inputs) $ zip assumpt_is higher_is
matching_higher = mapMaybe (\(n, i, hi) -> maybe Nothing (Just . (n, i,)) hi) matching
let_expr = Let (map (\(n, i, _) -> (snd i, Var (Id n . typeOf $ snd i))) matching_higher)
let ce' = let_expr
. flip (foldr (uncurry replaceAssumeFC)) (map (\(n, (_, i), _) -> (idName i, n)) matching_higher)
$ foldr (uncurry replaceVar) ce (map (\(n, _, hi) -> (n, hi)) matching_higher)
assume_ce = Assume Nothing appAssumpt ce'
putCurrExpr (CurrExpr er assume_ce)
Nothing -> return ()
replaceAssumeFC :: ASTContainer m Expr => Name -> Name -> m -> m
replaceAssumeFC old new = modifyASTs (replaceAssumeFC' old new)
replaceAssumeFC' :: Name -> Name -> Expr -> Expr
replaceAssumeFC' old new e@(Assume (Just fc) e1 e2) =
if funcName fc == old then Assume (Just (fc { funcName = new })) e1 e2 else e
replaceAssumeFC' _ _ e = e
isType :: Expr -> Bool
isType (Type _) = True
isType _ = False
typeType :: Expr -> Maybe Type
typeType (Type t) = Just t
typeType _ = Nothing