packages feed

g2-0.2.0.0: src/G2/Liquid/Inference/Interface.hs

{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}

module G2.Liquid.Inference.Interface ( inferenceCheck
                                     , inference
                                     , getInitState
                                     , getNameLevels ) where

import G2.Config.Config as G2
import G2.Data.Timer
import G2.Interface hiding (violated)
import G2.Language.CallGraph
import G2.Language.Expr
import qualified G2.Language.ExprEnv as E
import G2.Language.Naming
import G2.Language.Support
import G2.Language.Syntax
import G2.Language.Typing
import G2.Liquid.Config
import G2.Liquid.ConvertCurrExpr
import G2.Liquid.Helpers
import G2.Liquid.Inference.Config
import G2.Liquid.Inference.FuncConstraint as FC
import G2.Liquid.Inference.G2Calls
import G2.Liquid.Inference.InfStack
import G2.Liquid.Inference.Initalization
import G2.Liquid.Inference.PolyRef
import G2.Liquid.Inference.Sygus
import G2.Liquid.Inference.UnionPoly
import G2.Liquid.Inference.GeneratedSpecs
import G2.Liquid.Inference.Verify
import G2.Liquid.Interface
import G2.Liquid.Types hiding (state)
import qualified G2.Liquid.Types as T
import qualified G2.Liquid.Types as G2LH
import G2.Solver
import G2.Translation

import Language.Haskell.Liquid.Types as LH

import Control.Monad.IO.Class 
import Control.Monad.Reader
import Data.Either
import qualified Data.HashSet as S
import qualified Data.HashMap.Lazy as HM
import Data.List
import Data.Maybe
import qualified Data.Text as T
import qualified Data.Text.IO as T

-- Run inference, with an extra, final check of correctness at the end.
-- Assuming inference is working correctly, this check should neve fail.
inferenceCheck :: InferenceConfig -> G2.Config -> LHConfig ->  [FilePath] -> [FilePath] -> IO (State [FuncCall], Either [CounterExample] GeneratedSpecs)
inferenceCheck infconfig config g2lhconfig proj fp = do
    (ghci, lhconfig) <- getGHCI infconfig proj fp
    (s, res, _, loops) <- inference' infconfig config g2lhconfig lhconfig ghci proj fp
    print $ loop_count loops
    print . sum . HM.elems $ loop_count loops
    print $ backtracks loops
    print $ searched_below loops
    print $ negated_models loops
    case res of
        Right gs -> do
            check_res <- checkGSCorrect infconfig lhconfig ghci gs
            case check_res of
                Safe -> return (s, res)
                _ -> error "inferenceCheck: Check failed"
        _ -> return (s, res)

inference :: InferenceConfig -> G2.Config -> LHConfig ->  [FilePath] -> [FilePath] -> IO (State [FuncCall], Either [CounterExample] GeneratedSpecs)
inference infconfig config g2lhconfig proj fp = do
    -- Initialize LiquidHaskell
    (ghci, lhconfig) <- getGHCI infconfig proj fp
    (s, res, timer, _) <- inference' infconfig config g2lhconfig lhconfig ghci proj fp
    print . logToSecs . sumLog . getLog $ timer
    return (s, res)

inference' :: InferenceConfig
           -> G2.Config
           -> LHConfig
           -> LH.Config
           -> [GhcInfo]
           -> [FilePath]
           -> [FilePath]
           -> IO (State [FuncCall], Either [CounterExample] GeneratedSpecs, Timer (Event Name), Counters)
inference' infconfig config g2lhconfig lhconfig ghci proj fp = do
    mapM_ (print . getQualifiers) ghci

    (lrs, g2config', g2lhconfig', infconfig', main_mod) <- getInitState proj fp ghci infconfig config g2lhconfig
    let nls = getNameLevels main_mod lrs

    let ut = sharedTyConsEE (concat nls) (expr_env . G2LH.state . lr_state $ lrs)

    let configs = Configs { g2_config = g2config', g2lh_config = g2lhconfig', lh_config = lhconfig, inf_config = infconfig'}
        prog = newProgress

    SomeSMTSolver solver <- getSMT g2config'
    let infL = iterativeInference solver ghci main_mod lrs nls HM.empty emptyGS emptyFC ut

    (res, ev_timer, lvl_timer, loops) <- runInfStack configs prog infL -- runProgresser (runConfigs (runTimer infL timer) configs) prog

    print . logToSecs . orderLogBySpeed . sumLog . getLog $ lvl_timer

    print . logToSecs . orderLogBySpeed . sumLog . mapLabels (mapEvent nameOcc) . getLog $ ev_timer
    print . logToSecs . orderLogBySpeed . sumLog . mapLabels (mapEvent (const ())) . getLog $ ev_timer
    return (G2LH.state . lr_state $ lrs, res, ev_timer, loops)

getInitState :: [FilePath]
             -> [FilePath]
             -> [GhcInfo]
             -> InferenceConfig
             -> G2.Config
             -> LHConfig
             -> IO (LiquidReadyState, G2.Config, LHConfig, InferenceConfig, Maybe T.Text)
getInitState proj fp ghci infconfig config lhconfig = do
    let g2config = config { mode = Liquid
                          , steps = 2000 }
        transConfig = simplTranslationConfig { simpl = False }
    (main_mod, exg2) <- translateLoaded proj fp transConfig g2config

    let (lrs, g2config', lhconfig', infconfig') = initStateAndConfig exg2 main_mod g2config lhconfig infconfig ghci
    return (lrs, g2config', lhconfig', infconfig', main_mod)

getNameLevels :: Maybe T.Text -> LiquidReadyState -> NameLevels
getNameLevels main_mod =
    filter (not . null)
       . map nub
       . nameLevels
       . getCallGraph
       . E.filterWithKey (\(Name _ m _ _) _ -> m == main_mod)
       . expr_env . G2LH.state . lr_state


data InferenceRes = CEx [CounterExample]
                  | Env GeneratedSpecs FuncConstraints MaxSizeConstraints MeasureExs
                  | Raise MeasureExs FuncConstraints MaxSizeConstraints
                  deriving (Show)

type NameLevels = [[Name]]

type MaxSizeConstraints = FuncConstraints

iterativeInference :: (MonadIO m, SMTConverter con)
                   => con
                   -> [GhcInfo]
                   -> Maybe T.Text
                   -> LiquidReadyState
                   -> NameLevels
                   -> MeasureExs
                   -> GeneratedSpecs
                   -> FuncConstraints
                   -> UnionedTypes
                   -> InfStack m (Either [CounterExample] GeneratedSpecs)
iterativeInference con ghci m_modname lrs nls meas_ex gs fc ut = do
    res <- inferenceL con FirstRound ghci m_modname lrs nls emptyEvals meas_ex gs fc emptyFC ut emptyBlockedModels
    case res of
        CEx cex -> return $ Left cex
        Env n_gs _ _ _ -> return $ Right n_gs
        Raise _ r_fc _ -> do
            incrMaxDepthI
            -- We might be missing some internal GHC types from our deep_seq walkers
            -- We filter them out to avoid an error
            let eenv = expr_env . G2LH.state $ lr_state lrs
                chck = filter (\n -> 
                                  case E.lookup n eenv of
                                      Just e -> isJust $ 
                                              mkStrict_maybe 
                                              (deepseq_walkers $ lr_binding lrs) 
                                              (Var (Id (Name "" Nothing 0 Nothing) (returnType e)))
                                      Nothing -> False) (head nls)
            liftIO . putStrLn $ "head nls =  " ++ show (head nls)

            logEventStartM CExSE
            ref <- getCEx ghci m_modname lrs gs chck
            logEventEndM
            case ref of
                Left cex -> return $ Left cex
                Right fc' -> do
                    logEventStartM UpdateMeasures
                    logEventEndM
                    incrMaxSynthSizeI
                    r_meas_ex' <- lift . lift . lift $ updateMeasureExs {- r_meas_ex -} HM.empty lrs ghci {- fc' -} (unionFC fc' r_fc)
                    iterativeInference con ghci m_modname lrs nls r_meas_ex' gs (unionFC fc' r_fc) ut


inferenceL :: (MonadIO m, SMTConverter con)
           => con
           -> Iteration
           -> [GhcInfo]
           -> Maybe T.Text
           -> LiquidReadyState
           -> NameLevels
           -> Evals Bool
           -> MeasureExs
           -> GeneratedSpecs
           -> FuncConstraints
           -> MaxSizeConstraints
           -> UnionedTypes
           -> BlockedModels
           -> InfStack m InferenceRes
inferenceL con iter ghci m_modname lrs nls evals meas_ex senv fc max_fc ut blk_mdls = do
    let sf = case nls of
                        (_:sf_:_) -> sf_
                        ([_])-> []
                        [] -> []

    startLevelTimer (case nls of fs_:_ -> fs_; [] -> [])
    (resAtL, evals') <- inferenceB con iter ghci m_modname lrs nls evals meas_ex senv fc max_fc ut blk_mdls
    endLevelTimer

    liftIO $ do
        putStrLn "-------"
        putStrLn $ "lengths = " ++ show (HM.map (length . nub) (blockedHashMap blk_mdls))
        putStrLn "-------"

    case resAtL of
        Env senv' n_fc n_mfc meas_ex' -> 
            case nls of
                [] -> return resAtL
                (_:nls') -> do
                    liftIO $ putStrLn "Down a level!"
                    let evals'' = foldr deleteEvalsForFunc evals' sf
                    inf_res <- inferenceL con FirstRound ghci m_modname lrs nls' evals'' meas_ex' senv'
                                                        (unionFC fc n_fc) (unionFC max_fc n_mfc) ut emptyBlockedModels
                    case inf_res of
                        Raise r_meas_ex r_fc r_max_fc -> do
                            liftIO $ putStrLn "Up a level!"
                            
                            inferenceL con AfterFirstRound ghci m_modname lrs nls evals' r_meas_ex senv r_fc r_max_fc ut blk_mdls
                        _ -> return inf_res
        _ -> return resAtL

inferenceB :: (MonadIO m, SMTConverter con)
           => con
           -> Iteration
           -> [GhcInfo]
           -> Maybe T.Text
           -> LiquidReadyState
           -> NameLevels
           -> Evals Bool
           -> MeasureExs
           -> GeneratedSpecs
           -> FuncConstraints
           -> MaxSizeConstraints
           -> UnionedTypes
           -> BlockedModels
           -> InfStack m (InferenceRes, Evals Bool)
inferenceB con iter ghci m_modname lrs nls evals meas_ex gs fc max_fc ut blk_mdls = do
    let (fs, sf, below_sf) = case nls of
                        (fs_:sf_:be) -> (fs_, sf_, be)
                        ([fs_])-> (fs_, [], [])
                        [] -> ([], [], [])

    incrLoopCountLog fs

    let curr_ghci = addSpecsToGhcInfos ghci gs
    logEventStartM UpdateEvals
    evals' <- updateEvals curr_ghci lrs fc evals
    logEventEndM
    logEventStartM Synth
    synth_gs <- lift . lift . lift $ synthesize con iter curr_ghci lrs evals' meas_ex (unionFC max_fc fc) ut blk_mdls (concat below_sf) sf
    logEventEndM

    liftIO $ do
        putStrLn "-------"
        putStrLn $ "lengths = " ++ show (HM.map (length . nub) (blockedHashMap blk_mdls))
        putStrLn "-------"

    case synth_gs of
        SynthEnv envN sz smt_mdl blk_mdls' -> do
            let gs' = unionDroppingGS gs envN
                ghci' = addSpecsToGhcInfos ghci gs'
            liftIO $ do
                putStrLn "inferenceB"
                putStrLn $ "fs = " ++ show fs
                putStrLn $ "init gs' = " ++ show gs'

            logEventStartM Verify
            res <- tryToVerify ghci'
            logEventEndM
            let res' = filterNamesTo fs res

            case res' of
                Safe -> return $ (Env gs' fc max_fc meas_ex, evals')
                Unsafe bad -> do
                    inf_con <- infConfigM
                    ref <- tryToGen (nub bad) ((emptyFC, emptyBlockedModels), emptyFC)
                              (\(fc1, bm1) (fc2, bm2) -> (fc1 `unionFC` fc2, bm1 `unionBlockedModels` bm2))
                              unionFC
                              [ (\n -> do
                                    logEventStartM (InfSE n)
                                    return $ Right (Nothing, emptyFC))
                              , refineUnsafe ghci m_modname lrs gs'
                              , if use_level_dec inf_con then searchBelowLevel ghci m_modname lrs res sf gs' else genEmp
                              , if use_negated_models inf_con then adjModel lrs sz smt_mdl else incrCExAndTime ]
                              logEventEndM

                    case ref of
                        Left cex -> return $ (CEx cex, evals')
                        Right ((viol_fc, new_blk_mdls), no_viol_fc) -> do
                            let fc' = viol_fc `unionFC` no_viol_fc
                                blk_mdls'' = blk_mdls' `unionBlockedModels` new_blk_mdls
                            liftIO $ putStrLn "Before genMeasureExs"
                            logEventStartM UpdateMeasures
                            meas_ex' <- lift . lift . lift $ updateMeasureExs meas_ex lrs ghci fc'
                            logEventEndM
                            liftIO $ putStrLn "After genMeasureExs"

                            inferenceB con AfterFirstRound ghci m_modname lrs nls evals' meas_ex' gs (unionFC fc fc') max_fc ut blk_mdls''

                Crash e1 e2 -> error $ "inferenceB: LiquidHaskell crashed" ++ "\n" ++ show e1 ++ "\n" ++ e2
        SynthFail sf_fc -> do
            liftIO . T.putStrLn $ "synthfail fc = " <> (printFCs lrs sf_fc)
            incrBackTrackLog
            return $ (Raise meas_ex fc (unionFC max_fc sf_fc), evals')

tryToGen :: Monad m =>
            [n] -- ^ A list of values to produce results for
         -> (r, ex) -- ^ A default result, in case none of the strategies work, or we are passed an empty [n]
         -> (r -> r -> r) -- ^ Some way of combining results
         -> (ex -> ex -> ex) -- ^ Some way of joining extra results
         -> [n -> m (Either err (Maybe r, ex))] -- ^ A list of strategies, in order, to try and produce a result
         -> m () -- ^ A monadic action to run after each n is processed
         -> m (Either err (r, ex))
tryToGen [] def _ _ _ _= return $ Right def
tryToGen (n:ns) def join_r join_ex fs final_m = do
    gen1 <- tryToGen' n def join_ex fs
    final_m
    case gen1 of
        Left err -> return $ Left err
        Right (r1, ex1) -> do
            gen2 <- tryToGen ns def join_r join_ex fs final_m
            case gen2 of
                Left err -> return $ Left err
                Right (r2, ex2) -> return $ Right (r1 `join_r` r2, ex1 `join_ex` ex2) 

tryToGen' :: Monad m =>
             n
          -> (r, ex)
          -> (ex -> ex -> ex)
          -> [n -> m (Either err (Maybe r, ex))]
          -> m (Either err (r, ex))
tryToGen' _ def _ [] = return $ Right (def)
tryToGen' n def join_ex (f:fs) = do
    gen1 <- f n
    case gen1 of
        Left err -> return $ Left err
        Right (Just r, ex) -> return $ Right (r, ex)
        Right (Nothing, ex1) -> do
            gen2 <- tryToGen' n def join_ex fs
            case gen2 of
                Left err -> return $ Left err
                Right (r, ex2) -> return $ Right (r, ex1 `join_ex` ex2)

genEmp :: Monad m => Name -> InfStack m (Either [CounterExample] (Maybe a, FuncConstraints))
genEmp _ = return $ Right (Nothing, emptyFC)

refineUnsafeAll :: MonadIO m => 
                    [GhcInfo]
                -> Maybe T.Text
                -> LiquidReadyState
                -> GeneratedSpecs
                -> [Name]
                -> InfStack m (Either [CounterExample] (Maybe FuncConstraints, FuncConstraints))
refineUnsafeAll ghci m_modname lrs gs bad = do
    res <- mapM (refineUnsafe ghci m_modname lrs gs) (nub bad)

    case fmap unzip $ partitionEithers res of
        (cex@(_:_), _) -> return . Left $ concat cex
        ([], (new_fcs, no_viol_fcs)) -> 
            let
                new_fcs' = unionsFC . map fst $ catMaybes new_fcs
            in
            return . Right $ (if nullFC new_fcs' then Nothing else Just new_fcs', unionsFC no_viol_fcs)

refineUnsafe :: MonadIO m => 
                [GhcInfo]
             -> Maybe T.Text
             -> LiquidReadyState
             -> GeneratedSpecs
             -> Name
             -> InfStack m (Either [CounterExample] (Maybe (FuncConstraints, BlockedModels), FuncConstraints))
refineUnsafe ghci m_modname lrs gs bad = do
    let merged_se_ghci = addSpecsToGhcInfos ghci gs

    (res, no_viol) <- genNewConstraints merged_se_ghci m_modname lrs (nameOcc bad)

    liftIO $ do
        putStrLn $ "--- Generated Counterexamples and Constraints for " ++ show bad ++ " ---"
        putStrLn "res = "
        printCE (T.state $ lr_state lrs) res

    let res' = filter (not . hasAbstractedArgError) res

    -- Either converts counterexamples to FuncConstraints, or returns them as errors to
    -- show to the user.
    new_fc <- checkNewConstraints ghci lrs res'

    case new_fc of
        Left cex -> return $ Left cex
        Right new_fc' -> do
            liftIO . T.putStrLn $ "new_fc' = " <> printFCs lrs new_fc'
            return $ Right (if nullFC new_fc'
                                    then Nothing
                                    else Just (new_fc', emptyBlockedModels), fromListFC no_viol)

searchBelowLevel :: MonadIO m =>
                    [GhcInfo]
                 -> Maybe T.Text
                 -> LiquidReadyState
                 -> VerifyResult Name
                 -> [Name]
                 -> GeneratedSpecs
                 -> Name
                 -> InfStack m (Either [CounterExample] (Maybe (FuncConstraints, BlockedModels), FuncConstraints))
searchBelowLevel ghci m_modname lrs verify_res lev_below gs bad = do
    incrSearchBelowLog
    let called_by_res = calledByFunc lrs bad
    case filterNamesTo called_by_res $ filterNamesTo lev_below verify_res of
        Unsafe bad_sf -> do
            liftIO $ putStrLn "About to run second run of CEx generation"
            ref_sf <- withConfigs (limitedCounterfactual $ namesGS gs) $ refineUnsafeAll ghci m_modname lrs gs bad_sf
            case ref_sf of
                Left cex -> return $ Left cex
                Right (viol_fc_sf, no_viol_fc_sf) ->
                    return $ Right (fmap (, emptyBlockedModels) viol_fc_sf, no_viol_fc_sf)
        Safe -> return $ Right (Nothing, emptyFC)
        Crash _ _ -> error "inferenceB: LiquidHaskell crashed"

adjModel :: MonadIO m => 
            LiquidReadyState
         -> Size
         -> SMTModel
         -> Name
         -> InfStack m (Either a (Maybe (FuncConstraints, BlockedModels), FuncConstraints))
adjModel lrs sz smt_mdl n = do
    incrNegatedModelLog
    liftIO $ putStrLn "adjModel repeated_fc"
    let clls = calledByFunc lrs n
        blk_mdls' = insertBlockedModel sz (MNOnly (n:clls)) smt_mdl emptyBlockedModels

    liftIO . putStrLn $ "blocked models = " ++ show blk_mdls'

    _ <- incrCExAndTime n
    return . Right $ (Just (emptyFC, blk_mdls'), emptyFC)

incrCExAndTime :: Monad m => Name -> InfStack m (Either a (Maybe b, FuncConstraints))
incrCExAndTime (Name n m _ _) = do
    incrMaxCExI (n, m)
    incrMaxTimeI (n, m)
    return $ Right (Nothing, emptyFC) 

calledByFunc :: LiquidReadyState -> Name -> [Name]
calledByFunc lrs n = 
    let
        eenv = expr_env . G2LH.state $ lr_state lrs
    in
    map zeroOutUnq
        . filter (isJust . flip E.lookup eenv)
        . maybe [] id
        . fmap varNames
        . fmap snd
        $ E.lookupNameMod (nameOcc n) (nameModule n) eenv

filterNamesTo ::  [Name] -> VerifyResult Name -> VerifyResult Name
filterNamesTo ns (Unsafe unsafe) = 
    case filter (\n -> toOccMod n `elem` ns_nm) unsafe of
        [] -> Safe
        unsafe' -> do
          Unsafe unsafe'
    where
        ns_nm = map toOccMod ns
        toOccMod (Name n m _ _) = (n, m)
filterNamesTo _ vr = vr

limitedCounterfactual :: [Name] -> Configs -> Configs
limitedCounterfactual ns cfgs@(Configs { g2lh_config = g2lh_c }) =
    cfgs { g2lh_config = g2lh_c { counterfactual = Counterfactual
                                                 . CFOnly
                                                 . S.fromList
                                                 $ map (\(Name n m _ _) -> (n, m)) ns } }

genNewConstraints :: MonadIO m => 
                     [GhcInfo]
                  -> Maybe T.Text
                  -> LiquidReadyState
                  -> T.Text
                  -> InfStack m ([CounterExample], [FuncConstraint])
genNewConstraints ghci m lrs n = do
    liftIO . putStrLn $ "Generating constraints for " ++ T.unpack n
    infconfig <- infConfigM

    ((exec_res, _), i) <- runLHInferenceCore n m lrs ghci
    let (exec_res', no_viol) = partition (true_assert . final_state) exec_res
        
        allCCons = noAbsStatesToCons i $ exec_res' ++ if use_extra_fcs infconfig then no_viol else []
    return $ (filter (not . hasPreArgError) $ map lhStateToCE exec_res', allCCons)

getCEx :: MonadIO m =>
          [GhcInfo]
       -> Maybe T.Text
       -> LiquidReadyState
       -> GeneratedSpecs
       -> [Name]
       -> InfStack m (Either [CounterExample] FuncConstraints)
getCEx ghci m_modname lrs gs bad = do
    let merged_se_ghci = addSpecsToGhcInfos ghci gs

    liftIO $ mapM_ (print . getTySigs) merged_se_ghci

    let bad' = nub $ map nameOcc bad

    res <- mapM (checkForCEx merged_se_ghci m_modname lrs) bad'

    liftIO $ do
        putStrLn $ "getCEx res = "
        printCE (T.state $ lr_state lrs) $ concat res

    let res' = concat res

    -- Either converts counterexamples to FuncConstraints, or returns them as errors to
    -- show to the user.
    new_fc <- checkNewConstraints ghci lrs res'

    case new_fc of
        Left cex -> return $ Left cex
        Right new_fc' -> do
            liftIO . T.putStrLn $ "new_fc' = " <> printFCs lrs new_fc'
            return $ Right new_fc'

checkForCEx :: MonadIO m =>
               [GhcInfo]
            -> Maybe T.Text
            -> LiquidReadyState
            -> T.Text
            -> InfStack m [CounterExample]
checkForCEx ghci m lrs n = do
    liftIO . putStrLn $ "Checking CEx for " ++ T.unpack n
    ((exec_res, _), _) <- runLHCExSearch n m lrs ghci
    let exec_res' = filter (true_assert . final_state) exec_res
    return $ map lhStateToCE exec_res'

checkNewConstraints :: (InfConfigM m, MonadIO m) => [GhcInfo] -> LiquidReadyState -> [CounterExample] -> m (Either [CounterExample] FuncConstraints)
checkNewConstraints ghci lrs cexs = do
    infconfig <- infConfigM
    res <- mapM (cexsToBlockingFC lrs ghci) cexs
    res2 <- return . concat =<< mapM cexsToExtraFC cexs
    case lefts res of
        res'@(_:_) -> return . Left $ res'
        _ -> return . Right . unionsFC . map fromSingletonFC $ (rights res) ++ if use_extra_fcs infconfig then res2 else []

updateMeasureExs :: (InfConfigM m, ProgresserM m, MonadIO m) => MeasureExs -> LiquidReadyState -> [GhcInfo] -> FuncConstraints -> m MeasureExs
updateMeasureExs meas_ex lrs ghci fcs =
    let
        es = concatMap (\fc ->
                    let
                        clls = concatMap (\(mfc, hfc) -> mfc:hfc) $ allCalls fc
                        vls = concatMap (\c -> returns c:arguments c) clls 
                        ex_poly = concat . concatMap extractValues . concatMap extractExprPolyBound $ vls
                    in
                    vls ++ ex_poly
                ) (toListFC fcs)
    in
    evalMeasures meas_ex lrs ghci es

synthesize :: (InfConfigM m, ProgresserM m, MonadIO m, SMTConverter con)
           => con -> Iteration -> [GhcInfo] -> LiquidReadyState -> Evals Bool -> MeasureExs
           -> FuncConstraints -> UnionedTypes -> BlockedModels -> [Name] -> [Name] -> m SynthRes
synthesize con iter ghci lrs evals meas_ex fc ut blk_mdls to_be for_funcs = do
    liaSynth con iter ghci lrs evals meas_ex fc ut blk_mdls to_be for_funcs

updateEvals :: (InfConfigM m, MonadIO m) => [GhcInfo] -> LiquidReadyState -> FuncConstraints -> Evals Bool -> m (Evals Bool)
updateEvals ghci lrs fc evals = do
    let cs = allCallsFC fc

    liftIO $ putStrLn "Before check func calls"
    evals' <- preEvals evals lrs ghci cs
    liftIO $ putStrLn "After pre"
    evals'' <- postEvals evals' lrs ghci cs
    liftIO $ putStrLn "After check func calls"

    return evals''

-- | Converts counterexamples into constraints that block the current specification set
cexsToBlockingFC :: (InfConfigM m, MonadIO m) => LiquidReadyState -> [GhcInfo] -> CounterExample -> m (Either CounterExample FuncConstraint)
cexsToBlockingFC _ _ (DirectCounter dfc fcs@(_:_) higher)
    | (_:_, _) <- partition (hasArgError . abstract) fcs = undefined
    | isError (returns (abstract dfc)) = do
        infconfig <- infConfigM
        let fcs' = filter (\fc -> abstractedMod fc `S.member` modules infconfig) fcs

        let rhs = OrFC $ map (\(Abstracted { abstract = fc }) -> 
                        ImpliesFC (Call Pre fc higher) (NotFC (Call Post fc higher))) fcs'

        return . Right $ ImpliesFC (Call Pre (abstract dfc) higher) rhs
    | otherwise = do
        infconfig <- infConfigM
        let fcs' = filter (\fc -> abstractedMod fc `S.member` modules infconfig) fcs

        let lhs = AndFC [Call Pre (abstract dfc) higher, NotFC (Call Post (abstract dfc) higher)]
            rhs = OrFC $ map (\(Abstracted { abstract = fc }) -> 
                        ImpliesFC (Call Pre fc higher) (NotFC (Call Post fc higher))) fcs'

        if not . null $ fcs'
            then return . Right $ ImpliesFC lhs rhs
            else error "cexsToBlockingFC: Unhandled"
cexsToBlockingFC _ _ (CallsCounter dfc cfc fcs@(_:_) higher)
    | (_:_, _) <- partition (hasArgError . abstract) fcs = undefined
    | isError (returns (abstract cfc)) = do
        infconfig <- infConfigM
        let fcs' = filter (\fc -> abstractedMod fc `S.member` modules infconfig) fcs

        let lhs = Call Pre (abstract dfc) higher
            rhs = OrFC $ map (\(Abstracted { abstract = fc }) -> 
                                ImpliesFC (Call Pre fc higher) (NotFC (Call Post fc higher))) fcs'

        if not . null $ fcs' 
            then return . Right $ ImpliesFC lhs rhs
            else error "cexsToBlockingFC: Should be unreachable! Non-refinable function abstracted!"    

    | otherwise = do
        infconfig <- infConfigM
        let fcs' = filter (\fc -> abstractedMod fc `S.member` modules infconfig) fcs

        let lhs = AndFC [Call Pre (abstract dfc) higher, NotFC (Call Pre (abstract cfc) higher)]
            rhs = OrFC $ map (\(Abstracted { abstract = fc }) -> 
                                ImpliesFC (Call Pre fc higher) (NotFC (Call Post fc higher))) fcs'

        if not . null $ fcs' 
            then return . Right $ ImpliesFC lhs rhs
            else error "cexsToBlockingFC: Should be unreachable! Non-refinable function abstracted!"    
cexsToBlockingFC lrs ghci cex@(DirectCounter dfc [] higher)
    | isError (returns (real dfc)) = do
        if isExported lrs (funcName (real dfc))
            then return . Left $ cex
            else return . Right . NotFC $ Call Pre (real dfc) higher
    | isExported lrs (funcName (real dfc)) = do
        post_ref <- checkPost ghci lrs (real dfc) higher
        case post_ref of
            True -> return $ Right (Call All (real dfc) higher)
            False -> return . Left $ cex
    | otherwise = return $ Right (Call All (real dfc) higher)
cexsToBlockingFC lrs ghci cex@(CallsCounter dfc cfc [] higher)
    | any isError (arguments (abstract cfc)) = do
        if
            | isExported lrs (funcName (real dfc))
            , isExported lrs (funcName (real cfc)) -> do
                called_pr <- checkPre ghci lrs (real cfc) higher -- TODO: Shouldn't be changing this?
                case called_pr of
                    True -> return . Right $ NotFC (Call Pre (real dfc) higher)
                    False -> return . Left $ cex
            | isExported lrs (funcName (real dfc)) -> do
                called_pr <- checkPre ghci lrs (real cfc) higher
                case called_pr of
                    True -> return . Right $ NotFC (Call Pre (real dfc) higher)
                    False -> return . Left $ cex
            | otherwise -> return . Right $ NotFC (Call Pre (real dfc) higher)
    | otherwise = do
        if
            | isExported lrs (funcName (real dfc))
            , isExported lrs (funcName (real cfc)) -> do
                called_pr <- checkPre ghci lrs (real cfc) higher -- TODO: Shouldn't be changing this?
                case called_pr of
                    True -> return . Right $ ImpliesFC (Call Pre (real dfc) higher) (Call Pre (real cfc) higher)
                    False -> return . Left $ cex
            | isExported lrs (funcName (real dfc)) -> do
                called_pr <- checkPre ghci lrs (real cfc) higher
                case called_pr of
                    True -> return . Right $ ImpliesFC (Call Pre (real dfc) higher) (Call Pre (real cfc) higher)
                    False -> return . Left $ cex
            | otherwise -> do
                return . Right $ ImpliesFC (Call Pre (real dfc) higher) (Call Pre (real cfc) higher)

-- Function constraints that don't block the current specification set, but which must be true
-- (i.e. the actual input and output for abstracted functions)
cexsToExtraFC :: InfConfigM m => CounterExample -> m [FuncConstraint]
cexsToExtraFC (DirectCounter dfc fcs@(_:_) higher) = do
    infconfig <- infConfigM
    let some_pre = ImpliesFC (Call Pre (real dfc) higher) $  OrFC (map (\fc -> Call Pre (real fc) higher) fcs)
        fcs' = filter (\fc -> abstractedMod fc `S.member` modules infconfig) fcs
    return $ some_pre:mapMaybe (realToMaybeFC higher) fcs'
cexsToExtraFC (CallsCounter dfc cfc fcs@(_:_) higher) = do
    infconfig <- infConfigM
    let some_pre = ImpliesFC (Call Pre (real dfc) higher) $  OrFC (map (\fc -> Call Pre (real fc) higher) fcs)
    let fcs' = filter (\fc -> abstractedMod fc `S.member` modules infconfig) fcs

    let pre_real = maybeToList $ (realToMaybeFC higher) cfc
        as = mapMaybe (realToMaybeFC higher) fcs'
        clls = if not . isError . returns . real $ cfc
                  then [Call All (real cfc) higher]
                  else []

    return $ some_pre:clls ++ pre_real ++ as
cexsToExtraFC (DirectCounter _ [] _) = return []
cexsToExtraFC (CallsCounter dfc cfc [] higher)
    | isError (returns (real dfc)) = return []
    | isError (returns (real cfc)) = return []
    | any isError (arguments (real cfc)) = return []
    | otherwise =
        let
            call_all_dfc = Call All (real dfc) higher
            call_all_cfc = Call All (real cfc) higher
            imp_fc = ImpliesFC (Call Pre (real dfc) higher) (Call Pre (real cfc) higher)
        in
        return $ [call_all_dfc, call_all_cfc, imp_fc]

noAbsStatesToCons :: Id -> [ExecRes AbstractedInfo] -> [FuncConstraint]
noAbsStatesToCons i = concatMap (noAbsStatesToCons' i) -- . filter (null . abs_calls . track . final_state)

noAbsStatesToCons' :: Id -> ExecRes AbstractedInfo -> [FuncConstraint]
noAbsStatesToCons' i@(Id (Name _ m _ _) _) er =
    let
        higher_calls = erHigherOrder er

        pre_s = lhStateToPreFC i er
        clls = filter (\fc -> nameModule (funcName fc) == m) 
             . map (switchName (idName i))
             . filter (not . hasArgError)
             . func_calls_in_real
             . init_call
             . track
             $ final_state er

        preCons = map (ImpliesFC pre_s . flip (Call Pre) higher_calls) clls
        -- A function may return error because it was passed an erroring higher order function.
        -- In this case, it would be incorrect to add a constraint that the function itself calls error.
        -- Thus, we simply get rid of constraints that call error. 
        callsCons = mapMaybe (\fc -> case isError (returns fc) of
                                      True -> Nothing -- NotFC (Call Pre fc)
                                      False -> Just (Call All fc higher_calls)) clls
        callsCons' = if hits_lib_err_in_real (init_call . track . final_state $ er)
                                    then []
                                    else callsCons
    in
    preCons ++ callsCons'

switchName :: Name -> FuncCall -> FuncCall
switchName n fc = if funcName fc == initiallyCalledFuncName then fc { funcName = n } else fc

--------------------------------------------------------------------

realToMaybeFC :: [HigherOrderFuncCall] -> Abstracted -> Maybe FuncConstraint
realToMaybeFC higher a@(Abstracted { real = fc }) 
    | hits_lib_err_in_real a = Nothing
    | isError (returns fc) = Just $ NotFC (Call Pre fc higher)
    | otherwise = Just $ ImpliesFC (Call Pre fc higher) (Call Post fc higher)

isExported :: LiquidReadyState -> Name -> Bool
isExported lrs (Name n m _ _) =
    (n, m) `elem` map (\(Name n' m' _ _) -> (n', m')) (exported_funcs (lr_binding lrs))

lhStateToPreFC :: Id -> ExecRes AbstractedInfo -> FuncConstraint
lhStateToPreFC i er@(ExecRes { conc_args = inArg
                             , conc_out = ex}) = Call Pre (FuncCall (idName i) inArg ex) (erHigherOrder er)

abstractedMod :: Abstracted -> Maybe T.Text
abstractedMod = nameModule . funcName . abstract

hasPreArgError :: CounterExample -> Bool
hasPreArgError (DirectCounter _ _ _) = False
hasPreArgError (CallsCounter _ calls_f _ _) = hasArgError $ real calls_f

hasAbstractedArgError :: CounterExample -> Bool
hasAbstractedArgError (DirectCounter _ as _) = any (hasArgError . real) as
hasAbstractedArgError (CallsCounter _ _ as _) = any (hasArgError . real) as

hasArgError :: FuncCall -> Bool
hasArgError = any isError . arguments

isError :: Expr -> Bool
isError (Prim Error _) = True
isError (Prim Undefined _) = True
isError _ = False

erHigherOrder :: ExecRes AbstractedInfo -> [HigherOrderFuncCall]
erHigherOrder = ai_higher_order_calls . track . final_state