{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE LambdaCase #-} module Arbitrary where import Data.Maybe import Data.List import Data.Map.Strict ((!)) import qualified Data.Map.Strict as Map import Test.QuickCheck import Language.Haskell.TH import Language.Haskell.TH.Syntax as TH import Megadeth import TypeInfo import Prediction customListGen :: Arbitrary t => Int -> Int -> Gen [t] customListGen fnil fcons = sized go where go 0 = return [] go n = frequency [ (fnil, return []) , (fcons, (:) <$> resize (n-1) arbitrary <*> go (n-1)) ] customMaybeGen :: Arbitrary t => Int -> Int -> Gen (Maybe t) customMaybeGen fnothing fjust = sized go where go 0 = return Nothing go n = frequency [ (fnothing, return Nothing) , (fjust, Just <$> resize (n-1) arbitrary) ] chooseExpQ :: FreqMap -> Name -> Name -> Name -> Bool -> TH.Type -> ExpQ chooseExpQ freqs goName nName target recursive ty | not recursive = [| arbitrary |] | headOf ty == target = [| $(varE goName) (max 0 ($(varE nName) - 1)) |] -- For the case of lists we use a custom generator, since its Arbitrary -- instance does not preserve generation frequencies on each iteration. -- Dirty hack to dodge Haskell's typeclasses system. This should be -- generalized to any type having this issue. | headOf ty == ''[] = let (fnil, fcons) = (freqs ! '[], freqs ! '(:)) in [| resize (max 0 ($(varE nName) - 1)) (customListGen fnil fcons) |] -- We need to do this hack for Maybe too. Clearly, this does not scale at -- all and we should refactor the code making it independent of the -- Arbitrary typeclass. | headOf ty == ''Maybe = let (fnothing, fjust) = (freqs ! 'Nothing, freqs ! 'Just) in [| resize (max 0 ($(varE nName) - 1)) (customMaybeGen fnothing fjust) |] | otherwise = [| resize (max 0 ($(varE nName) - 1)) arbitrary |] makeArbExpsQ :: FreqMap -> Name -> Name -> Name -> [ConView] -> [ExpQ] makeArbExpsQ freqs goName nName targetName cons = map (fmap fixAppl) [ foldl (applyTParam rec) (conE conName) conArgs | SimpleCon conName rec conArgs <- cons ] where applyTParam rec rem param = rem `infixAppE` (chooseExp rec param) chooseExp rec = chooseExpQ freqs goName nName targetName rec infixAppE l r = uInfixE l (varE '(<*>)) r frequencyExpQ :: FreqMap -> Name -> Name -> Name -> [ConView] -> ExpQ frequencyExpQ freqs goName nName target cons = [| frequency $(listE tuples) |] where tuples = map (\(f,g) -> tupE [f,g]) (zip freqExpsQ arbExpsQ) freqExpsQ = map getFreqExpQ cons arbExpsQ = makeArbExpsQ freqs goName nName target cons getFreqExpQ con = maybe [|1|] (\f->[|f|]) (Map.lookup (nm con) freqs) genTupleArbs :: Int -> ExpQ genTupleArbs n = doE $ map (\x -> bindS (varP x) (varE 'arbitrary)) vars ++ [ noBindS $ appE (varE 'return) (tupE (map varE vars))] where vars = take n varNames isMutRec :: TypeEnv -> ConView -> Bool isMutRec env con = nm con `elem` recs where recs = map cname (getRecursives env) updateMutRec :: TypeEnv -> ConView -> ConView updateMutRec env con | isMutRec env con = con { recursive = True } | otherwise = con deriveArbitraryInstance :: TypeEnv -> FreqMap -> Name -> Q [Dec] deriveArbitraryInstance env freqs target = reify target >>= \case {- data T {...} = C1 {...} | C2 {...} | ... -} TyConI (DataD _ _ params _ cons _) -> do let paramExps = map varT (paramNames params) allCons = map (updateMutRec env . simpleConView target) cons (recCons, termCons) = partition recursive allCons mkGo goName nName | length allCons == 1 = head (makeArbExpsQ freqs goName nName target allCons) | length recCons == length allCons = frequencyExpQ freqs goName nName target recCons | length termCons == 1 = condE [| $(varE nName) == 0 |] (head (makeArbExpsQ freqs goName nName target termCons)) (frequencyExpQ freqs goName nName target allCons) | otherwise = condE [| $(varE nName) == 0 |] (frequencyExpQ freqs goName nName target termCons) (frequencyExpQ freqs goName nName target allCons) if not (null paramExps) then [d| instance $(applyTo (tupleT (length paramExps)) (map (appT (conT ''Arbitrary)) paramExps)) => Arbitrary $(applyTo (conT target) paramExps) where arbitrary = sized go where go n = $(mkGo 'go 'n) |] else [d| instance Arbitrary $(applyTo (conT target) paramExps) where arbitrary = sized go where go n = $(mkGo 'go 'n) |] {- newtype T {...} = SingleCon {...} -} TyConI (NewtypeD _ _ params _ con _) -> do let paramExps = map varT (paramNames params) singleCon = simpleConView target con if not (null paramExps) then [d| instance $(applyTo (tupleT (length paramExps)) (map (appT (conT ''Arbitrary)) paramExps)) => Arbitrary $(applyTo (conT target) paramExps) where arbitrary = sized go where go n = $(head (makeArbExpsQ freqs 'go 'n target [singleCon])) |] else [d| instance Arbitrary $(applyTo (conT target) paramExps) where arbitrary = sized go where go n = $(head (makeArbExpsQ freqs 'go 'n target [singleCon])) |] {- type T {...} = U {...} -} TyConI (TySynD _ params ty) -> case (getTy ty) of {- type T {...} = ({...}, {...}, ...) -} (TupleT n) -> do let paramExps = map varT (paramNames params) if not (null paramExps) then [d| instance $(applyTo (tupleT (length paramExps)) (map (appT (conT ''Arbitrary)) paramExps)) => Arbitrary $(applyTo (conT target) paramExps) where arbitrary = $(genTupleArbs n) |] else [d| instance Arbitrary $(applyTo (conT target) paramExps) where arbitrary = $(genTupleArbs n) |] -- This type should had been derived already, It is clearly a -- dependency and it should be put before in the topsort. (ConT n) -> return [] _ -> runIO (putStrLn ("IGNORING: " ++ show ty)) >> return [] {- Int#, Bool#, ... -} PrimTyConI {} -> return [] {- Not supported yet. ([], (,), ...) -} x -> error ("Case not defined: " ++ show x) devArbitrary :: TypeEnv -> FreqMap -> Name -> Q [Dec] devArbitrary env freqs = megaderive (deriveArbitraryInstance env freqs) (isInsName ''Arbitrary)