liquidhaskell-0.8.0.2: src/Test/Target/Monad.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ViewPatterns #-}
module Test.Target.Monad
( whenVerbose
, noteUsed
, addDep
, addConstraint
, addConstructor
, addSort
, addVariable
, inModule
, making
, lookupCtor
, guarded
, fresh
, freshChoice
, freshInt
, getValue
, Target, runTarget
, TargetState(..), initState
, TargetOpts(..), defaultOpts
) where
import Control.Applicative
import Control.Arrow (first, second, (***))
import qualified Control.Exception as Ex
import Control.Monad
import Control.Monad.Catch
import Control.Monad.Reader
import Control.Monad.State
import qualified Data.HashMap.Strict as M
import qualified Data.HashSet as S
import Data.IORef
import Data.List hiding (sort)
import qualified Data.Text as ST
import System.IO.Unsafe
-- import Text.Printf
import Language.Fixpoint.Smt.Interface hiding (SMTLIB2(..))
import Language.Fixpoint.Types
import Language.Fixpoint.Types.Config (SMTSolver(..))
import Language.Haskell.Liquid.Types.PredType
import Language.Haskell.Liquid.Types.RefType
import Language.Haskell.Liquid.Types hiding (var, Target)
import qualified GHC
import qualified Type as GHC
-- import Test.Target.Serialize
import Test.Target.Types
import Test.Target.Util
-- import Debug.Trace
newtype Target a = Target (StateT TargetState (ReaderT TargetOpts IO) a)
deriving ( Functor, Applicative, Monad, MonadIO, Alternative
, MonadState TargetState, MonadCatch, MonadReader TargetOpts )
instance MonadThrow Target where
throwM = Ex.throw
runTarget :: TargetOpts -> TargetState -> Target a -> IO a
runTarget opts st (Target x) = runReaderT (evalStateT x st) opts
-- evalTarget :: TargetOpts -> TargetState -> Target a -> IO a
-- evalTarget o s (Target x) = runReaderT (evalStateT x s) o
-- execTarget :: GhcSpec -> Target a -> IO TargetState
-- execTarget e (Target x) = execStateT x (initGS e)
seed :: IORef Int
seed = unsafePerformIO $ newIORef 0
{-# NOINLINE seed #-}
freshInt :: Target Int
freshInt = liftIO $ do
n <- readIORef seed
modifyIORef' seed (+1)
return n
data TargetOpts = TargetOpts
{ depth :: !Int
, solver :: !SMTSolver
, verbose :: !Bool
, logging :: !Bool
, keepGoing :: !Bool
-- ^ whether to keep going after finding a counter-example, useful for
-- checking coverage
, maxSuccess :: !(Maybe Int)
-- ^ whether to stop after a certain number of successful tests, or
-- enumerate the whole input space
, scDepth :: !Bool
-- ^ whether to use SmallCheck's notion of depth
, ghcOpts :: ![String]
-- ^ extra options to pass to GHC
}
defaultOpts :: TargetOpts
defaultOpts = TargetOpts
{ depth = 3
, solver = Z3
, verbose = False
, logging = True
, keepGoing = False
, maxSuccess = Nothing
, scDepth = True
, ghcOpts = []
}
data TargetState = TargetState
{ variables :: ![Variable]
, choices :: ![Variable]
, constraints :: !Constraint
, deps :: !(M.HashMap Symbol [Symbol])
, realized :: ![(Symbol, Value)]
-- , dconEnv :: ![(Symbol, DataConP)]
, ctorEnv :: !DataConEnv
, measEnv :: !MeasureEnv
, embEnv :: !(TCEmb GHC.TyCon)
, tyconInfo :: !(M.HashMap GHC.TyCon RTyCon)
, freesyms :: ![(Symbol,Symbol)]
, constructors :: ![Variable] -- (S.HashSet Variable) --[(String, String)]
, sigs :: ![(Symbol, SpecType)]
, chosen :: !(Maybe Symbol)
, sorts :: !(S.HashSet Sort)
, modName :: !Symbol
, filePath :: !FilePath
, makingTy :: !Sort
, smtContext :: !Context
}
initState :: FilePath -> GhcSpec -> Context -> TargetState
initState fp sp ctx = TargetState
{ variables = []
, choices = []
, constraints = []
, deps = mempty
, realized = []
-- , dconEnv = dcons
, ctorEnv = cts
, measEnv = meas
, embEnv = gsTcEmbeds sp
, tyconInfo = tyi
, freesyms = free
, constructors = []
, sigs = sigs
, chosen = Nothing
, sorts = S.empty
, modName = ""
, filePath = fp
, makingTy = FObj ""
, smtContext = ctx
}
where
-- FIXME: can we NOT tidy???
-- dcons = tidyF $ map (first symbol) (gsDconsP sp)
-- NOTE: we want to tidy all occurrences of nullary datacons in the signatures
cts = subst su $ tidyF $ map (symbol *** val) (gsCtors sp)
sigs = subst su $ tidyF $ map (symbol *** val) $ gsTySigs sp
tyi = makeTyConInfo (gsTconsP sp)
free = tidyS $ map (second symbol)
$ gsFreeSyms sp ++ map (\(c,_) -> (symbol c, c)) (gsCtors sp)
meas = gsMeasures sp
tidyF = map (first tidySymbol)
tidyS = map (second tidySymbol)
su = mkSubst (map (second eVar) free)
whenVerbose :: Target () -> Target ()
whenVerbose x
= do v <- asks verbose
when v x
noteUsed :: (Symbol, Value) -> Target ()
noteUsed (v,x) = modify $ \s@(TargetState {..}) -> s { realized = (v,x) : realized }
-- TODO: does this type make sense? should it be Symbol -> Symbol -> Target ()?
addDep :: Symbol -> Expr -> Target ()
addDep from (EVar to) = modify $ \s@(TargetState {..}) ->
s { deps = M.insertWith (flip (++)) from [to] deps }
addDep _ _ = return ()
addConstraint :: Expr -> Target ()
addConstraint p = modify $ \s@(TargetState {..}) -> s { constraints = p:constraints }
addConstructor :: Variable -> Target ()
addConstructor c
= modify $ \s@(TargetState {..}) -> s { constructors = nub $ c:constructors }
inModule :: Symbol -> Target a -> Target a
inModule m act
= do m' <- gets modName
modify $ \s -> s { modName = m }
r <- act
modify $ \s -> s { modName = m' }
return r
making :: Sort -> Target a -> Target a
making ty act
= do ty' <- gets makingTy
modify $ \s -> s { makingTy = ty }
r <- act
modify $ \s -> s { makingTy = ty' }
return r
-- | Find the refined type of a data constructor.
lookupCtor :: Symbol -> SpecType -> Target SpecType
lookupCtor c (toType -> t)
-- FIXME: WTF, how do two symbols share a Text
-- without being equal??
= do mt <- find (\(c', _) -> symbolText c == symbolText c')
<$> gets ctorEnv
case mt of
Just (_, t) -> return t
Nothing -> do
-- m <- gets filePath
-- o <- asks ghcOpts
let tc = GHC.tyConAppTyCon t
let dcs = GHC.tyConDataCons tc
let Just dc = find (\d -> c == symbol (GHC.getName d)) dcs
let t = ofType (GHC.dataConUserType dc)
-- t <- io $ runGhc o $ do
-- _ <- loadModule m
-- traceShowM c
-- t <- GHC.exprType (printf "(%s)" (symbolString c))
-- return (ofType t)
modify $ \s@(TargetState {..}) -> s { ctorEnv = (c,t) : ctorEnv }
return t
-- | Given a data constructor @d@ and an action, create a new choice variable
-- @c@ and execute the action while guarding any generated constraints with
-- @c@. Returns @(action-result, c)@.
guarded :: String -> Target Expr -> Target (Expr, Expr)
guarded cn act
= do c <- freshChoice cn
mc <- gets chosen
modify $ \s -> s { chosen = Just c }
x <- act
modify $ \s -> s { chosen = mc }
return (x, EVar c)
-- | Generate a fresh variable of the given 'Sort'.
fresh :: Sort -> Target Symbol
fresh sort
= do n <- freshInt
let sorts' = sortTys sort
let x = symbol $ ST.unpack (ST.intercalate "->" $ map (symbolText.unObj) sorts') ++ show n
addVariable (x, sort)
return x
addSort :: Sort -> Target ()
addSort sort = do
let sorts' = sortTys sort
modify $ \s@(TargetState {..}) -> s { sorts = S.union (S.fromList (arrowize sort : sorts')) sorts }
addVariable :: Variable -> Target ()
addVariable (v, sort) = do
addSort sort
modify $ \s@(TargetState {..}) -> s { variables = (v, sort) : variables }
sortTys :: Sort -> [Sort]
--sortTys (FFunc _ ts) = concatMap sortTys ts
sortTys t
| Just (_, ts, t) <- functionSort t
= concatMap sortTys ts ++ [t]
| otherwise
= [t]
arrowize :: Sort -> Sort
arrowize = FObj . symbol . ST.intercalate "->" . map (symbolText . unObj) . sortTys
unObj :: Sort -> Symbol
unObj FInt = "Int"
unObj (FObj s) = s
unObj s = error $ "unObj: " ++ show s
-- | Given a data constructor @d@, create a new choice variable corresponding to
-- @d@.
freshChoice :: String -> Target Symbol
freshChoice cn
= do n <- freshInt
let x = intSymbol (unObj choicesort) (cn ++ "-" ++ show n)
-- ... symbol $ T.unpack (Builder.toLazyText $ smt2 choicesort)
-- ++ "-" ++ cn ++ "-" ++ show n
modify $ \s@(TargetState {..}) -> s { variables = (x, choicesort) : variables }
return x
-- | Ask the SMT solver for the 'Value' of the given variable.
getValue :: Symbol -> Target Value
getValue v = do
ctx <- gets smtContext
Values [x] <- io $ ensureValues $ command ctx (GetValue [v])
noteUsed x
return (snd x)