g2-0.1.0.0: src/G2/Liquid/ConvertCurrExpr.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TupleSections #-}
module G2.Liquid.ConvertCurrExpr (convertCurrExpr) 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.Map as M
import Data.Maybe
convertCurrExpr :: Id -> Bindings -> LHStateM [Name]
convertCurrExpr ifi bindings = do
ifi' <- modifyInputExpr ifi
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 [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 ns
Nothing -> return []
-- 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'
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
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 = modifyAppTopE letLiftFuncs'
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
-- 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
assumpt <- lookupAssumptionM (idName ifi)
-- fi <- fixedInputs
eenv <- exprEnv
inames <- inputNames
lh <- mapM (lhTCDict' M.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' -> do
let appAssumpt = mkApp $ assumpt':typs ++ lh ++ ars
let ce' = Assume Nothing appAssumpt ce
putCurrExpr (CurrExpr er ce')
Nothing -> return ()
isType :: Expr -> Bool
isType (Type _) = True
isType _ = False
typeType :: Expr -> Maybe Type
typeType (Type t) = Just t
typeType _ = Nothing