liquidhaskell-0.7.0.0: 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 qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.Builder as Builder
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 = 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)