packages feed

g2-0.2.0.0: src/G2/Liquid/Inference/Sygus/SpecInfo.hs

{-# LANGUAGE OverloadedStrings #-}

module G2.Liquid.Inference.Sygus.SpecInfo where

import G2.Language as G2
import qualified G2.Language.ExprEnv as E
import G2.Liquid.Conversion
import G2.Liquid.Helpers
import G2.Liquid.Types hiding (SP)
import G2.Liquid.Inference.Config
import G2.Liquid.Inference.FuncConstraint
import G2.Liquid.Inference.G2Calls
import G2.Liquid.Inference.GeneratedSpecs
import G2.Liquid.Inference.PolyRef
import G2.Liquid.Inference.UnionPoly
import G2.Solver as Solver

import Language.Haskell.Liquid.Types as LH hiding (SP, ms, isBool)
import Language.Fixpoint.Types.Refinements as LH hiding (pAnd, pOr)
import qualified Language.Fixpoint.Types as LH

import Control.Monad
import qualified Control.Monad.State.Lazy as SM
import Data.Coerce
import qualified Data.HashMap.Lazy as HM
import qualified Data.List as L
import qualified Data.Map as M
import Data.Maybe
import qualified Data.Text as T
import Data.Semigroup (Semigroup (..))

type NMExprEnv = HM.HashMap (T.Text, Maybe T.Text) G2.Expr

-- A list of functions that still must have specifications synthesized at a lower level
type ToBeNames = [Name]

-- A list of functions to synthesize a the current level
type ToSynthNames = [Name]


data Forms = LIA { -- LIA formulas
                   c_active :: SMTName

                 , c_op_branch1 :: SMTName
                 , c_op_branch2 :: SMTName

                 , b0 :: SMTName
                 , ars_coeffs :: [SMTName]
                 , rets_coeffs :: [SMTName] }
           | Set { c_active :: SMTName
                 , c_op_branch1 :: SMTName
                 , c_op_branch2 :: SMTName

                 , int_sing_set_bools_lhs :: [[SMTName]]
                 , int_sing_set_bools_rhs :: [[SMTName]]

                 , ars_bools_lhs :: [[SMTName]]
                 , rets_bools_lhs :: [[SMTName]]

                 , ars_bools_rhs :: [[SMTName]]
                 , rets_bools_rhs :: [[SMTName]]
                 }
           | BoolForm { c_active :: SMTName
                      , c_op_branch1 :: SMTName
                      , c_op_branch2 :: SMTName

                      , ars_bools :: [SMTName]
                      , rets_bools :: [SMTName]

                      , forms :: [Forms]
                      }
                 deriving Show

coeffs :: Forms -> [SMTName]
coeffs cf@(LIA {}) = b0 cf:ars_coeffs cf ++ rets_coeffs cf
coeffs (Set {}) = []
coeffs cf@(BoolForm {}) = concatMap coeffs (forms cf)

coeffsNoB :: Forms -> [SMTName]
coeffsNoB cf@(LIA {}) = ars_coeffs cf ++ rets_coeffs cf
coeffsNoB (Set {}) = []
coeffsNoB cf@(BoolForm {}) = concatMap coeffsNoB (forms cf)

setBools :: Forms -> [SMTName]
setBools (LIA {}) = []
setBools s@(Set {}) =
    concat $ int_sing_set_bools_lhs s ++ int_sing_set_bools_rhs s ++ ars_bools_lhs s ++ rets_bools_lhs s ++ ars_bools_rhs s ++ rets_bools_rhs s
setBools cf@(BoolForm {}) = concatMap setBools (forms cf)

boolBools :: Forms -> [SMTName]
boolBools (LIA {}) = []
boolBools (Set {}) = []
boolBools cf@(BoolForm {}) = ars_bools cf ++ rets_bools cf ++ concatMap boolBools (forms cf)


type Clause = (SMTName, [Forms])
type CNF = [Clause]

data SpecInfo = SI { s_max_coeff :: Integer

                   -- A function that is used to record the value of the function at known points,
                   -- i.e. points that occur in the FuncConstraints
                   , s_known_pre :: FixedSpec
                   , s_known_post :: FixedSpec

                   -- A function specification that must be synthesized in the future, at a lower level
                   , s_to_be_pre :: ToBeSpec
                   , s_to_be_post :: ToBeSpec

                   -- Functions that capture the pre and post condition.
                   -- We have one precondition function per argument
                   , s_syn_pre :: [PolyBound SynthSpec]
                   , s_syn_post :: PolyBound SynthSpec

                   , s_type_pre :: [Type]
                   , s_type_post :: Type

                   , s_status :: Status }
                   deriving (Show)

s_known_pre_name :: SpecInfo -> SMTName
s_known_pre_name = fs_name . s_known_pre

s_known_post_name :: SpecInfo -> SMTName
s_known_post_name = fs_name . s_known_post

s_to_be_pre_name :: SpecInfo -> SMTName
s_to_be_pre_name = tb_name . s_to_be_pre

s_to_be_post_name :: SpecInfo -> SMTName
s_to_be_post_name = tb_name . s_to_be_post

data FixedSpec = FixedSpec { fs_name :: SMTName
                           , fs_args :: [SpecArg] }
                           deriving (Show)

data ToBeSpec = ToBeSpec { tb_name :: SMTName
                         , tb_args :: [SpecArg] }
                         deriving (Show)

data SynthSpec = SynthSpec { sy_name :: SMTName
                           , sy_args :: [SpecArg]
                           , sy_rets :: [SpecArg]
                           , sy_coeffs :: CNF }
                           deriving (Show)

sy_args_and_ret :: SynthSpec -> [SpecArg]
sy_args_and_ret si = sy_args si ++ sy_rets si

int_sy_args :: SynthSpec -> [SpecArg]
int_sy_args = filter (\a -> smt_sort a == SortInt) . sy_args

int_sy_rets :: SynthSpec -> [SpecArg]
int_sy_rets = filter (\a -> smt_sort a == SortInt) . sy_rets

set_sy_args :: SynthSpec -> [SpecArg]
set_sy_args = filter (isSet . smt_sort) . sy_args

set_sy_rets :: SynthSpec -> [SpecArg]
set_sy_rets = filter (isSet . smt_sort) . sy_rets

bool_sy_args :: SynthSpec -> [SpecArg]
bool_sy_args = filter (\a -> smt_sort a == SortBool) . sy_args

bool_sy_rets :: SynthSpec -> [SpecArg]
bool_sy_rets = filter (\a -> smt_sort a == SortBool) . sy_rets

isSet :: Sort -> Bool
isSet (SortArray _ _) = True
isSet _ = False

int_sy_args_and_ret :: SynthSpec -> [SpecArg]
int_sy_args_and_ret si = int_sy_args si ++ int_sy_rets si

set_sy_args_and_ret :: SynthSpec -> [SpecArg]
set_sy_args_and_ret si = set_sy_args si ++ set_sy_rets si

bool_sy_args_and_ret :: SynthSpec -> [SpecArg]
bool_sy_args_and_ret si = bool_sy_args si ++ bool_sy_rets si

all_sy_args_and_ret :: SynthSpec -> [SpecArg]
all_sy_args_and_ret si = int_sy_args_and_ret si ++ set_sy_args_and_ret si ++ bool_sy_args_and_ret si

data SpecArg = SpecArg { lh_rep :: LH.Expr
                       , smt_var :: SMTName
                       , smt_sort :: Sort}
                       deriving (Show)

data Status = Synth -- ^ A specification should be synthesized
            | ToBeSynthed -- ^ The specification will be synthesized at some lower level
            | Known -- ^ The specification is completely fixed
            deriving (Eq, Show)

buildNMExprEnv :: ExprEnv -> NMExprEnv
buildNMExprEnv = HM.fromList . map (\(n, e) -> ((nameOcc n, nameModule n), e)) . E.toExprList

buildSpecInfo :: (InfConfigM m, ProgresserM m) =>
                 NMExprEnv
              -> TypeEnv
              -> TypeClasses
              -> Measures
              -> [GhcInfo]
              -> FuncConstraints
              -> UnionedTypes
              -> ToBeNames
              -> ToSynthNames
              -> m (M.Map Name SpecInfo)
buildSpecInfo eenv tenv tc meas ghci fc ut to_be_ns ns_synth = do
    -- Compensate for zeroed out names in FuncConstraints
    let ns_synth' = map zeroOutName ns_synth

    -- Figure out what the other functions relevant to the current spec are
    let all_calls = concatMap allCallNames $ toListFC fc
        non_ns = filter (`notElem` ns_synth') all_calls

    -- Form tuples of:
    -- (1) Func Names
    -- (2) Function Argument Types
    -- (3) Function Known Types
    -- to be used in forming SpecInfo's
    let ns_aty_rty = map (relNameTypePairs eenv) ns_synth'

        other_aty_rty = map (relNameTypePairs eenv) non_ns
        to_be_ns' = map zeroOutName to_be_ns
        (to_be_ns_aty_rty, known_ns_aty_rty) = L.partition (\(n, _) -> n `elem` to_be_ns') other_aty_rty


    si <- foldM (\m (n, (at, rt)) -> do
        s <- buildSI tenv tc meas ut Synth ghci n at rt
        return $ M.insert n s m) M.empty ns_aty_rty
    si' <- foldM (\m (n, (at, rt)) -> do
        s <- buildSI tenv tc meas ut ToBeSynthed ghci n at rt
        return $ M.insert n s m) si to_be_ns_aty_rty
    si'' <- foldM (\m (n, (at, rt)) -> do
        s <- buildSI tenv tc meas ut Known ghci n at rt
        return $ M.insert n s m) si' known_ns_aty_rty

    inf_con <- infConfigM

    let si''' = if use_invs inf_con
                    then allCondsKnown . conflateLoopNames . elimSyArgs $ si''
                    else si''

    return si'''
    where
      zeroOutName (Name n m _ l) = Name n m 0 l

relNameTypePairs :: NMExprEnv -> Name -> (Name, ([Type], Type))
relNameTypePairs eenv n =
    case HM.lookup (nameOcc n, nameModule n) eenv of
        Just e' -> (n, generateRelTypesFromExpr e')
        Nothing -> error $ "synthesize: No expr found"

generateRelTypesFromExpr :: G2.Expr -> ([Type], Type)
generateRelTypesFromExpr = generateRelTypes . inTyForAlls . typeOf

generateRelTypes :: Type -> ([Type], Type)
generateRelTypes t =
    let
        ty_e = PresType t
        arg_ty_c = filter (not . isTYPE)
                 . filter (notLH)
                 $ argumentTypes ty_e
        ret_ty_c = returnType ty_e
    in
    (arg_ty_c, ret_ty_c)

------------------------------------
-- Building SpecInfos
------------------------------------

buildSI :: (InfConfigM m, ProgresserM m) => TypeEnv -> TypeClasses -> Measures -> UnionedTypes -> Status -> [GhcInfo] ->  Name -> [Type] -> Type -> m SpecInfo
buildSI tenv tc meas uts stat ghci f aty rty = do
    let smt_f = nameToStr f
        fspec = case genSpec ghci f of
                Just spec' -> spec'
                _ -> error $ "synthesize: No spec found for " ++ show f

    (outer_ars_pb, ret_pb) <- argsAndRetFromSpec tenv tc ghci meas [] aty rty fspec
    let outer_ars = map fst outer_ars_pb
        ret_v = headValue ret_pb

        arg_ns = zipWith (\a i -> a { smt_var = "x_" ++ show i } ) (concat outer_ars) ([1..] :: [Integer])
        ret_ns = zipWith (\r i -> r { smt_var = "x_r_" ++ show i }) (aar_r ret_v) ([1..] :: [Integer])

    let ut = case lookupUT f uts of
                Just _ut -> _ut
                Nothing -> error $ "buildSI: Missing UnionedType " ++ show f
        (ut_a, ut_r) = generateRelTypes ut
        ut_a' = reverse . take (length outer_ars_pb) $ reverse ut_a
        -- smt_names = assignNamesAndArgCount ut
        smt_names = assignNamesAndArgCount $ mkTyFun (ut_a' ++ [ut_r])

        pre_specs =
            zipWith3 (\ars_pb ut_ar i ->
                        let
                            ut_pb = extractTypeAppAndFuncPolyBound ut_ar

                            ars = map fst (init ars_pb)
                            r_pb = snd (last ars_pb)
                        in
                        mapPB (\(AAndR { aar_a = ex_a, aar_r = rets }, t, j) ->
                            case unTyApp t of
                                TyVar (Id ut_n _):_ ->
                                    let
                                        (nme, count) = fromMaybe
                                            ("_synth_pre_filler_" ++ show i ++ "_" ++ show j, 0)
                                            (HM.lookup ut_n smt_names)
                                    in
                                    SynthSpec { sy_name = smt_f ++ "_spec" ++ nme
                                                , sy_args = zipWith (\a k -> a { smt_var = "x_" ++ show k}) (concat (take count ars) ++ ex_a) ([1..] :: [Integer])
                                                , sy_rets = zipWith (\r k -> r { smt_var = "x_r_" ++ show k}) rets ([1..] :: [Integer])
                                                , sy_coeffs = []}
                                TyFun _ _:_ ->
                                        SynthSpec { sy_name = smt_f ++ "_synth_pre_filler_" ++ show i ++ "_" ++ show j
                                                , sy_args = []
                                                , sy_rets = []
                                                , sy_coeffs = []}
                                _ -> error "buildSI: unsupported Type"
                                )  $ zip3PB r_pb ut_pb (uniqueIds r_pb)
                    ) (filter (not . null) $ L.inits outer_ars_pb) ut_a' ([1..] :: [Integer])

    return
        SI { s_max_coeff = 0
           , s_known_pre = FixedSpec { fs_name = smt_f ++ "_known_pre"
                                     , fs_args = arg_ns }
           , s_known_post = FixedSpec { fs_name = smt_f ++ "_known_post"
                                      , fs_args = arg_ns ++ ret_ns }
           , s_to_be_pre = ToBeSpec { tb_name = smt_f ++ "_to_be_pre"
                                    , tb_args = arg_ns }
           , s_to_be_post = ToBeSpec { tb_name = smt_f ++ "_to_be_post"
                                     , tb_args = arg_ns ++ ret_ns }
           , s_syn_pre = pre_specs
           , s_syn_post = mkSynSpecPB smt_f outer_ars (mapPB aar_r ret_pb) smt_names ut_r

           , s_type_pre = aty
           , s_type_post = rty

           , s_status = stat }

data ArgsAndRet = AAndR { aar_a :: [SpecArg], aar_r :: [SpecArg] } deriving Show

instance Semigroup ArgsAndRet where
    AAndR { aar_a = a1, aar_r = r1 } <> AAndR { aar_a = a2, aar_r = r2 } = AAndR { aar_a = a1 <> a2, aar_r = r1 <> r2 }

instance Monoid ArgsAndRet where
    mempty = AAndR { aar_a = [], aar_r = [] }

argsAndRetFromSpec :: (InfConfigM m, ProgresserM m) => TypeEnv -> TypeClasses -> [GhcInfo] -> Measures -> [([SpecArg], PolyBound ArgsAndRet)] -> [Type] -> Type -> SpecType
                                                    -> m ([([SpecArg], PolyBound ArgsAndRet)], PolyBound ArgsAndRet)
argsAndRetFromSpec tenv tc ghci meas ars ts rty (RAllT { rt_ty = out }) =
    argsAndRetFromSpec tenv tc ghci meas ars ts rty out
argsAndRetFromSpec tenv tc ghci meas ars (t:ts) rty (RFun { rt_in = rfun@(RFun {}), rt_out = out}) = do
    let (ts', rty') = generateRelTypes t
    (f_ars, f_ret) <- argsAndRetFromSpec tenv tc ghci meas ars ts' rty' rfun
    let f_ret_list = case f_ret of
                        PolyBound (AAndR { aar_a = [], aar_r = [] }) [] -> []
                        _ -> [f_ret]
        f_ars_sa = map fst f_ars
        f_ars' = zipWith (\sa pb -> mapPB (AAndR (concat sa)) pb) (L.inits f_ars_sa) (map (mapPB aar_r) $ map snd f_ars ++ f_ret_list)
    argsAndRetFromSpec tenv tc ghci meas (([], PolyBound mempty f_ars'):ars) ts rty out
argsAndRetFromSpec tenv tc ghci meas ars (t:ts) rty rfun@(RFun { rt_in = i, rt_out = out}) = do
    (m_out_symb, sa) <- mkSpecArgPB ghci tenv meas t rfun
    let out_symb = case m_out_symb of
                      Just os -> os
                      Nothing -> error "argsAndRetFromSpec: out_symb is Nothing"
    case i of
        RVar {} -> argsAndRetFromSpec tenv tc ghci meas ars ts rty out
        RFun {} -> argsAndRetFromSpec tenv tc ghci meas ars ts rty out
        _ -> argsAndRetFromSpec tenv tc ghci meas ((out_symb, sa):ars) ts rty out
argsAndRetFromSpec tenv _ ghci meas ars _ rty rapp@(RApp {}) = do
    (_, sa) <- mkSpecArgPB ghci tenv meas rty rapp
    return (reverse ars, sa)
argsAndRetFromSpec tenv _ ghci meas ars _ rty rvar@(RVar {}) = do
    (_, sa) <- mkSpecArgPB ghci tenv meas rty rvar
    return (reverse ars, sa)
argsAndRetFromSpec _ _ _ _ _ [] _ st@(RFun {}) = error $ "argsAndRetFromSpec: RFun with empty type list " ++ show st
argsAndRetFromSpec _ _ _ _ _ _ _ st = error $ "argsAndRetFromSpec: unhandled SpecType " ++ show st

mkSpecArgPB :: (InfConfigM m, ProgresserM m) => [GhcInfo] -> TypeEnv -> Measures -> Type -> SpecType -> m (Maybe [SpecArg], PolyBound ArgsAndRet)
mkSpecArgPB ghci tenv meas t st = do
    MaxSize mx_meas <- maxSynthFormSizeM
    let t_pb = extractTypePolyBound t

        sy_pb = specTypeSymbolPB st
        in_sy_pb = mapPB inner sy_pb

        t_sy_pb = filterPBByType snd $ zipPB in_sy_pb t_pb
        t_sy_pb' = case t_sy_pb of
                    Just pb -> pb
                    Nothing -> PolyBound (inner $ headValue sy_pb, t) []

        out_symb = outer $ headValue sy_pb
        out_spec_arg = fmap (\os -> mkSpecArg (fromInteger mx_meas) ghci tenv meas os t) out_symb

    return (out_spec_arg, mapPB (ret . uncurry (mkSpecArg (fromInteger mx_meas) ghci tenv meas)) t_sy_pb')


mkSpecArg :: Int -> [GhcInfo] -> TypeEnv -> Measures -> LH.Symbol -> Type -> [SpecArg]
mkSpecArg mx_meas ghci tenv meas symb t =
    let
        srt = typeToSort t
    in
    case srt of
        Just srt' ->
            [SpecArg { lh_rep = EVar symb
                     , smt_var = "tbd"
                     , smt_sort = srt' }]
        Nothing ->
            let
                app_meas = applicableMeasuresType mx_meas tenv meas t
                app_meas' = L.sortBy (\(n1, _) (n2, _) -> compare n1 n2) app_meas
            in
            mapMaybe
                (\(mn, (at, rt)) ->
                    let vm = t `specializes` at in
                    case vm of
                        Just vm' ->
                            let

                                rt' = applyTypeMap vm' rt
                            in
                            fmap (\srt' ->
                                    let
                                        lh_mn = map (getLHMeasureName ghci) mn
                                    in
                                    SpecArg { lh_rep = foldr EApp (EVar symb) $ map EVar lh_mn
                                            , smt_var = "tbd"
                                            , smt_sort = srt'}) $ typeToSort rt'
                        Nothing -> Nothing) app_meas'

ret :: [SpecArg] -> ArgsAndRet
ret sa = AAndR { aar_a = [], aar_r = sa }

mkSynSpecPB :: String -> [[SpecArg]] -> PolyBound [SpecArg] -> HM.HashMap Name (SMTName, Int) -> Type -> PolyBound SynthSpec
mkSynSpecPB smt_f ars pb_sa smt_names ut =
    let
        ut_pb = extractTypePolyBound ut
    in
    mapPB (\(sa, t, ui) ->
            let
                TyVar (Id ut_n _):_ = unTyApp t
                (nme, count) = fromMaybe
                    ("_synth_post_filler", 0)
                    (HM.lookup ut_n smt_names)
                arg_ns = zipWith (\a i -> a { smt_var = "x_" ++ show i } ) (concat . take count $ ars) ([1..] :: [Integer])
                ret_ns = map (\(r, i) -> r { smt_var = "x_r_" ++ show ui ++ "_" ++ show i }) $ zip sa ([1..] :: [Integer])
            in
            SynthSpec { sy_name = smt_f ++ "_spec" ++ nme -- smt_f ++ show ui
                      , sy_args = arg_ns
                      , sy_rets = ret_ns
                      , sy_coeffs = [] }
        )
        $ zip3PB pb_sa ut_pb (uniqueIds pb_sa)

filterPBByType :: (v -> Type) -> PolyBound v -> Maybe (PolyBound v)
filterPBByType f = filterPB (\(PolyBound v _) ->
                                let
                                    t = f v
                                in
                                not (isTyVar t))

assignNamesAndArgCount :: Type -> HM.HashMap Name (SMTName, Int)
assignNamesAndArgCount t = SM.evalState (assignNamesAndArgCount' 0 HM.empty t) 0

assignNamesAndArgCount' :: Int -> HM.HashMap Name (SMTName, Int) -> Type -> SM.State Int (HM.HashMap Name (SMTName, Int))
assignNamesAndArgCount' ar_count hm (TyVar (Id n k))
    | Nothing <- HM.lookup n hm = do
        pos <- SM.get
        SM.modify' (+ 1)
        return $ HM.insert n ("_synth_" ++ show ar_count ++ "_" ++ show pos, ar_count) hm
    | otherwise = return hm
assignNamesAndArgCount' ar_count hm (TyFun t1 t2) = do
    hm' <- assignNamesAndArgCount' ar_count hm t1
    assignNamesAndArgCount' (ar_count + 1) hm' t2
assignNamesAndArgCount' ar_count hm (TyApp t1 t2) = do
    hm' <- assignNamesAndArgCount' ar_count hm t1
    assignNamesAndArgCount' ar_count hm' t2
assignNamesAndArgCount' ar_count hm (TyForAll _ t) = assignNamesAndArgCount' ar_count hm t
assignNamesAndArgCount' _ hm _ = return hm

----------------------------------------------------------------------------
-- Manipulate Evals
----------------------------------------------------------------------------

-- We assign a unique id to each function call, and use these as the arguments
-- to the known functions, rather than somehow using the arguments directly.
-- This means we can get away with needing only a single known function
-- for the pre, and a single known function for the post, as opposed
-- to needing individual known functions/function calls for polymorphic refinements.
assignIds :: Evals Bool -> Evals (Integer, Bool)
assignIds = snd . mapAccumLEvals (\i b -> (i + 1, (i, b))) 0

----------------------------------------------------------------------------
-- Manipulate SpecTypes
----------------------------------------------------------------------------

data SymbolPair = SP { inner :: LH.Symbol, outer :: Maybe LH.Symbol }

specTypeSymbolPB :: SpecType -> PolyBound SymbolPair
specTypeSymbolPB (RFun { rt_bind = b, rt_in = i }) =
    case specTypeSymbolPB i of
        PolyBound sp ps -> PolyBound (sp { outer = Just b}) ps
specTypeSymbolPB (RApp { rt_reft = ref, rt_args = ars }) =
    PolyBound (SP { inner = reftSymbol $ ur_reft ref, outer = Nothing}) $ map specTypeSymbolPB ars
specTypeSymbolPB (RVar {rt_reft = ref}) =
    PolyBound (SP { inner = reftSymbol $ ur_reft ref, outer = Nothing }) []
specTypeSymbolPB r = error $ "specTypeSymbolPB: Unexpected SpecType" ++ "\n" ++ show r

reftSymbol :: Reft -> LH.Symbol
reftSymbol = fst . unpackReft

unpackReft :: Reft -> (LH.Symbol, LH.Expr)
unpackReft = coerce

----------------------------------------------------------------------------
-- Invariant configuration
----------------------------------------------------------------------------

elimPolyArgSpecs :: M.Map a SpecInfo -> M.Map a SpecInfo
elimPolyArgSpecs = M.map elimPolyArgSpecs'

elimPolyArgSpecs' :: SpecInfo -> SpecInfo
elimPolyArgSpecs' si = si { s_syn_pre = map (\(PolyBound a _) -> PolyBound a []) (s_syn_pre si)
                          , s_syn_post = (\(PolyBound a _) -> PolyBound a []) (s_syn_post si)}

elimSyArgs :: M.Map Name SpecInfo -> M.Map Name SpecInfo
elimSyArgs = M.mapWithKey (\n si -> if specialFunction n then elimSyArgs' si else si)

elimSyArgs' :: SpecInfo -> SpecInfo
elimSyArgs' si = si { s_syn_pre = map (mapPB dropNonDirectInt) (s_syn_pre si)
                    , s_syn_post = mapPB dropNonDirectInt (s_syn_post si)}

dropNonDirectInt :: SynthSpec -> SynthSpec
dropNonDirectInt sy = sy { sy_args = filter dropNonDirectInt' (sy_args sy) }

dropNonDirectInt' :: SpecArg -> Bool
dropNonDirectInt' sy =
    case lh_rep sy of
        EVar {} -> True
        _ -> False

specialFunction :: Name -> Bool
specialFunction (Name n _ _ _) | T.take 4 n == "loop" = True
                               | T.take 5 n == "while" = True
                               | T.take 5 n == "breakWhile" = True
                               | otherwise = False

conflateLoopNames ::  M.Map a SpecInfo -> M.Map a SpecInfo
conflateLoopNames = M.map conflateLoopNames'

conflateLoopNames' :: SpecInfo -> SpecInfo
conflateLoopNames' si@(SI { s_syn_pre = pb_sy_pre@(_:_)
                          , s_syn_post = pb_post@(PolyBound sy_post _) })
    | take 4 (sy_name sy_post) == "loop" =
        let
            pb_pre = last pb_sy_pre
        in
        si { s_syn_pre = init pb_sy_pre ++ [conflateLoopNames'' pb_pre pb_post] }
conflateLoopNames' si = si

conflateLoopNames'' :: PolyBound SynthSpec -> PolyBound SynthSpec -> PolyBound SynthSpec
conflateLoopNames'' pb1 = mapPB (\(sy1, sy2) -> sy1 { sy_name = sy_name sy2} ) . zipPB pb1

allCondsKnown ::  M.Map a SpecInfo -> M.Map a SpecInfo
allCondsKnown = M.map allCondsKnown'

allCondsKnown' :: SpecInfo -> SpecInfo
allCondsKnown' si@(SI { s_syn_post = (PolyBound sy_post _) })
    | take 4 (sy_name sy_post) == "cond" = si { s_status = Known }
allCondsKnown' si = si

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

typeToSort :: Type -> Maybe Sort
typeToSort (TyApp (TyCon (Name n _ _ _) _) t)
    | n == "Set"
    , Just s <- typeToSort t = Just (SortArray s SortBool)
typeToSort (TyCon (Name n _ _ _) _)
    | n == "Int"  = Just SortInt
    | n == "Bool" = Just SortBool
typeToSort _ = Nothing

getLHMeasureName :: [GhcInfo] -> Name -> LH.Symbol
getLHMeasureName ghci (Name n m _ l) =
    let
        MeasureSymbols meas_symb = measureSymbols ghci
        zn = Name n m 0 l
    in
    case L.find (\meas -> symbolName meas == zn) meas_symb of
        Just meas -> meas
        Nothing -> error "getLHMeasureName: unhandled measure"

applicableMeasuresType :: Int -> TypeEnv -> Measures -> Type -> [([Name], (Type, Type))]
applicableMeasuresType mx_meas tenv meas t =
    HM.toList . HM.map (\es -> case filter notLH . anonArgumentTypes $ last es of
                                [at]
                                  | Just (rt, _) <- chainReturnType t es -> (at, rt)
                                _ -> error $ "applicableMeasuresType: too many arguments" ++ "\n" ++ show es)
              $ applicableMeasures mx_meas tenv meas t

applicableMeasures :: Int -> TypeEnv -> Measures -> Type -> HM.HashMap [Name] [G2.Expr]
applicableMeasures mx_meas tenv meas t =
    HM.fromList . map unzip
                . filter repFstSnd
                . filter (maybe False (isJust . typeToSort . fst) . chainReturnType t . map snd)
                $ formMeasureComps mx_meas tenv t meas

-- LiquidHaskell includes measures to extract from tuples of various sizes.
-- It includes redundant pairs (fst and x_Tuple21) and (snd and x_Tuple22).
-- Including both measures slows down the synthesis, so we eliminates
-- chains involving x_Tuple21 and x_Tuple22.
repFstSnd :: [(Name, a)] -> Bool
repFstSnd = all (\(Name n _ _ _) -> n /= "x_Tuple21" && n /= "x_Tuple22") . map fst

notLH :: Type -> Bool
notLH ty
    | TyCon (Name n _ _ _) _ <- tyAppCenter ty = n /= "lh"
    | otherwise = True